首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何使用Java服务和数据流下载文件

如何使用Java服务和数据流下载文件
EN

Stack Overflow用户
提问于 2015-04-18 03:29:50
回答 3查看 160.5K关注 0票数 31

我有三台机器:

  1. 文件所在的服务器
  2. 正在运行REST服务的服务器(泽西岛)
  3. 客户端(浏览器)访问第二服务器,但不能访问第一服务器

我如何才能直接(不保存在第二服务器上的文件)下载文件从第一服务器到客户的机器?

在第二台服务器上,我可以从第一台服务器获得一个ByteArrayOutputStream文件,我可以使用REST服务将这个流进一步传递给客户端吗?

它会这样工作吗?

基本上,我想要实现的是允许客户端使用第二服务器上的REST服务从第一服务器下载一个文件(因为没有客户端到第一服务器的直接访问),只使用数据流(所以没有数据与第二服务器的文件系统接触)。

现在我尝试使用EasyStream库:

代码语言:javascript
运行
复制
final FTDClient client = FTDClient.getInstance();

try {
    final InputStreamFromOutputStream <String> isOs = new InputStreamFromOutputStream <String>() {
        @Override
        public String produce(final OutputStream dataSink) throws Exception {
            return client.downloadFile2(location, Integer.valueOf(spaceId), URLDecoder.decode(filePath, "UTF-8"), dataSink);
        }
    };
    try {
        String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);

        StreamingOutput output = new StreamingOutput() {
            @Override
            public void write(OutputStream outputStream) throws IOException, WebApplicationException {
                int length;
                byte[] buffer = new byte[1024];
                while ((length = isOs.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                }
                outputStream.flush();
            }
        };
        return Response.ok(output, MediaType.APPLICATION_OCTET_STREAM)
            .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
            .build();
    }
}

UPDATE2

因此,现在使用自定义MessageBodyWriter的代码看起来很简单:

代码语言:javascript
运行
复制
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048) ;
client.downloadFile(location, spaceId, filePath, baos);
return Response.ok(baos).build();

但是,在尝试处理大型文件时,我也会遇到相同的堆错误。

UPDATE3终于成功了!StreamingOutput成功了。

谢谢你@peeskillet!非常感谢!

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-04-18 05:05:18

“我如何直接(不将文件保存在第二服务器上)将文件从第一服务器下载到客户端的计算机?”

只需使用Client API并从响应中获取InputStream

代码语言:javascript
运行
复制
Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);

有两种口味可以买到InputStream。您也可以使用

代码语言:javascript
运行
复制
Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();

哪一个更有效率?我不确定,但是返回的InputStream是不同的类,所以如果您愿意的话,您可能想了解一下。

在第二台服务器上,我可以从第一台服务器获得一个ByteArrayOutputStream文件,我可以使用REST服务将这个流进一步传递给客户端吗?

因此,您将在@GradyGCooper提供的链接中看到的大多数答案似乎都支持使用StreamingOutput。一个示例实现可能类似于

代码语言:javascript
运行
复制
final InputStream responseStream = client.target(url).request().get(InputStream.class);
System.out.println(responseStream.getClass());
StreamingOutput output = new StreamingOutput() {
    @Override
    public void write(OutputStream out) throws IOException, WebApplicationException {  
        int length;
        byte[] buffer = new byte[1024];
        while((length = responseStream.read(buffer)) != -1) {
            out.write(buffer, 0, length);
        }
        out.flush();
        responseStream.close();
    }   
};
return Response.ok(output).header(
        "Content-Disposition", "attachment, filename=\"...\"").build();

但是,如果我们查看StreamingOutputProvider的源代码,您将在writeTo中看到,它只是将数据从一个流写入另一个流。因此,在上面的实现中,我们必须写两次。

我们怎么能只写一篇?简单地将InputStream作为Response返回

代码语言:javascript
运行
复制
final InputStream responseStream = client.target(url).request().get(InputStream.class);
return Response.ok(responseStream).header(
        "Content-Disposition", "attachment, filename=\"...\"").build();

如果我们查看InputStreamProvider的源代码,它只是将它委托给ReadWriter.writeTo(in, out),而ReadWriter.writeTo(in, out)只是执行我们在StreamingOutput实现中所做的工作。

代码语言:javascript
运行
复制
 public static void writeTo(InputStream in, OutputStream out) throws IOException {
    int read;
    final byte[] data = new byte[BUFFER_SIZE];
    while ((read = in.read(data)) != -1) {
        out.write(data, 0, read);
    }
}

Asides:

  • Client对象是昂贵的资源。您可能希望为请求重用相同的Client。您可以为每个请求从客户端提取一个WebTarget。 WebTarget target = client.target(url);InputStream is = target.request().get(InputStream.class); 我认为WebTarget甚至可以共享。我在泽西岛2.x文件中找不到任何东西(只是因为它是一个更大的文档,而且我现在太懒于浏览它:-),但是在泽西岛1.x文件中,它说ClientWebResource (相当于2.x中的WebTarget )可以在线程之间共享。所以我猜泽西的2.x也是一样的。但你可能想自己确认一下。
  • 您不必使用Client API。通过java.net包API可以轻松地下载。但是既然你已经在使用泽西,使用它的API也没什么坏处
  • 以上假设是泽西岛2.x。对于泽西岛1.x,一个简单的Google搜索应该会让你在使用API (或我上面链接到的文档)时获得大量点击。

更新

我真是个蠢货。当OP和我正在考虑将ByteArrayOutputStream转换为InputStream的方法时,我忽略了最简单的解决方案,那就是为ByteArrayOutputStream编写一个MessageBodyWriter

代码语言:javascript
运行
复制
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

@Provider
public class OutputStreamWriter implements MessageBodyWriter<ByteArrayOutputStream> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return ByteArrayOutputStream.class == type;
    }

    @Override
    public long getSize(ByteArrayOutputStream t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(ByteArrayOutputStream t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException {
        t.writeTo(entityStream);
    }
}

然后,我们可以简单地在响应中返回ByteArrayOutputStream

代码语言:javascript
运行
复制
return Response.ok(baos).build();

哦哦!

更新2

这是我用过的测试(

资源类

代码语言:javascript
运行
复制
@Path("test")
public class TestResource {

    final String path = "some_150_mb_file";

    @GET
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response doTest() throws Exception {
        InputStream is = new FileInputStream(path);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int len;
        byte[] buffer = new byte[4096];
        while ((len = is.read(buffer, 0, buffer.length)) != -1) {
            baos.write(buffer, 0, len);
        }
        System.out.println("Server size: " + baos.size());
        return Response.ok(baos).build();
    }
}

客户端测试

代码语言:javascript
运行
复制
public class Main {
    public static void main(String[] args) throws Exception {
        Client client = ClientBuilder.newClient();
        String url = "http://localhost:8080/api/test";
        Response response = client.target(url).request().get();
        String location = "some_location";
        FileOutputStream out = new FileOutputStream(location);
        InputStream is = (InputStream)response.getEntity();
        int len = 0;
        byte[] buffer = new byte[4096];
        while((len = is.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        out.flush();
        out.close();
        is.close();
    }
}

更新3

因此,这个特定用例的最终解决方案是OP简单地从StreamingOutputwrite方法中传递write,似乎第三方API需要一个OutputStream作为参数。

代码语言:javascript
运行
复制
StreamingOutput output = new StreamingOutput() {
    @Override
    public void write(OutputStream out) {
        thirdPartyApi.downloadFile(.., .., .., out);
    }
}
return Response.ok(output).build();

不太确定,但似乎资源方法中的读写(使用ByteArrayOutputStream` )实现了内存中的一些东西。

downloadFile方法接受OutputStream的要点是,它可以将结果直接写入所提供的OutputStream。例如,一个FileOutputStream,如果您将它写入文件,当下载进来时,它将直接流到文件中。

这并不意味着我们要保留对OutputStream的引用,就像您试图使用baos一样,而这正是内存实现的地方。

因此,按照工作方式,我们将直接写入为我们提供的响应流。方法writewriteTo方法(在MessageBodyWriter中)被传递给它之前实际上不会被调用。

您可以更好地查看我所写的MessageBodyWriter。基本上在writeTo方法中,用StreamingOutput替换ByteArrayOutputStream,然后在方法中调用streamingOutput.write(entityStream)。您可以看到我在答案的前面部分提供的链接,在这里我链接到StreamingOutputProvider。这就是发生的事

票数 38
EN

Stack Overflow用户

发布于 2015-04-18 04:11:19

参见这里的示例:使用泽西输入和输出二进制流?

伪代码应该是这样的(在上面提到的帖子中还有其他一些类似的选项):

代码语言:javascript
运行
复制
@Path("file/")
@GET
@Produces({"application/pdf"})
public StreamingOutput getFileContent() throws Exception {
     public void write(OutputStream output) throws IOException, WebApplicationException {
        try {
          //
          // 1. Get Stream to file from first server
          //
          while(<read stream from first server>) {
              output.write(<bytes read from first server>)
          }
        } catch (Exception e) {
            throw new WebApplicationException(e);
        } finally {
              // close input stream
        }
    }
}
票数 0
EN

Stack Overflow用户

发布于 2016-11-10 10:47:20

请参阅:

代码语言:javascript
运行
复制
@RequestMapping(value="download", method=RequestMethod.GET)
public void getDownload(HttpServletResponse response) {

// Get your file stream from wherever.
InputStream myStream = someClass.returnFile();

// Set the content type and attachment header.
response.addHeader("Content-disposition", "attachment;filename=myfilename.txt");
response.setContentType("txt/plain");

// Copy the stream to the response's output stream.
IOUtils.copy(myStream, response.getOutputStream());
response.flushBuffer();
}

详细信息:https://twilblog.github.io/java/spring/rest/file/stream/2015/08/14/return-a-file-stream-from-spring-rest.html

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29712554

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档