如何使用 Java REST 服务和数据流下载文件
Posted
技术标签:
【中文标题】如何使用 Java REST 服务和数据流下载文件【英文标题】:How to download a file using a Java REST service and a data stream 【发布时间】:2015-06-25 02:13:53 【问题描述】:我有 3 台机器:
文件所在的服务器 运行 REST 服务的服务器(泽西岛) 客户端(浏览器)可以访问第二台服务器,但无法访问第一台服务器
如何直接(不将文件保存在第二台服务器上)将文件从第一台服务器下载到客户端计算机? 从第二台服务器我可以得到一个 ByteArrayOutputStream 来从第一台服务器获取文件,我可以使用 REST 服务将该流进一步传递给客户端吗?
它会这样工作吗?
所以基本上我想要实现的是允许客户端使用第二台服务器上的 REST 服务从第一台服务器下载文件(因为没有从客户端直接访问第一台服务器)只使用数据流(所以没有数据触摸第二台服务器的文件系统)。
我现在尝试使用 EasyStream 库:
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();
更新2
所以我现在使用自定义 MessageBodyWriter 的代码看起来很简单:
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048) ;
client.downloadFile(location, spaceId, filePath, baos);
return Response.ok(baos).build();
但我在尝试处理大文件时遇到同样的堆错误。
更新3 终于设法让它工作了! StreamingOutput 成功了。
谢谢@peeskillet!非常感谢!
【问题讨论】:
【参考方案1】:“我怎样才能直接(不将文件保存在第二台服务器上)将文件从第一台服务器下载到客户端的机器上?”
只需使用Client
API 并从响应中获取InputStream
Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);
有两种方式可以获得InputStream
。你也可以使用
Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();
哪个效率更高?我不确定,但返回的 InputStream
s 是不同的类,所以如果你愿意的话,你可能想研究一下。
我可以从第二台服务器获取 ByteArrayOutputStream 以从第一台服务器获取文件,我可以使用 REST 服务将此流进一步传递给客户端吗?
因此,您将在link provided by @GradyGCooper 中看到的大多数答案似乎都倾向于使用StreamingOutput
。示例实现可能类似于
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();
但是,如果我们查看source code for StreamingOutputProvider,您会在writeTo
中看到,它只是将数据从一个流写入另一个流。所以对于我们上面的实现,我们必须写两次。
我们怎样才能只写一次呢?简单地将InputStream
作为Response
返回
final InputStream responseStream = client.target(url).request().get(InputStream.class);
return Response.ok(responseStream).header(
"Content-Disposition", "attachment, filename=\"...\"").build();
如果我们查看source code for InputStreamProvider,它只是委托给ReadWriter.writeTo(in, out)
,这只是我们在上面的StreamingOutput
实现中所做的事情
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);
旁白:
Client
对象是昂贵的资源。您可能希望重复使用相同的 Client
请求。您可以为每个请求从客户端提取WebTarget
。
WebTarget target = client.target(url);
InputStream is = target.request().get(InputStream.class);
我认为WebTarget
甚至可以共享。我在Jersey 2.x documentation 中找不到任何东西(只是因为它是一个较大的文档,我现在懒得扫描它:-),但在Jersey 1.x documentation 中,它显示Client
和WebResource
(相当于 2.x 中的 WebTarget
)可以在线程之间共享。所以我猜Jersey 2.x 会是一样的。但您可能需要自己确认。
您不必使用Client
API。使用 java.net
包 API 可以轻松实现下载。但是由于您已经在使用 Jersey,所以使用它的 API 并没有什么坏处
以上假设 Jersey 2.x。对于 Jersey 1.x,一个简单的 Google 搜索应该会为您提供大量使用 API(或我上面链接到的文档)的点击率
更新
我真是个笨蛋。虽然我和 OP 正在考虑将ByteArrayOutputStream
转换为InputStream
的方法,但我错过了最简单的解决方案,即为ByteArrayOutputStream
编写MessageBodyWriter
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
return Response.ok(baos).build();
D'OH!
更新 2
这是我使用的测试(
资源类
@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();
客户端测试
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 从StreamingOutput
的write
方法中简单地传递OutputStream
。似乎是第三方 API,需要 OutputStream
作为参数。
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
所做的那样,这是内存实现的来源。
因此,通过这种方式,我们直接写入为我们提供的响应流。 write
方法实际上直到 writeTo
方法(在 MessageBodyWriter
中)才被调用,其中 OutputStream
被传递给它。
看我写的MessageBodyWriter
你可以得到更好的图片。基本上在writeTo
方法中,将ByteArrayOutputStream
替换为StreamingOutput
,然后在方法内部,调用streamingOutput.write(entityStream)
。您可以看到我在答案的前面部分提供的链接,其中我链接到StreamingOutputProvider
。这正是发生的事情
【讨论】:
评论不用于扩展讨论;这个对话是moved to chat。 你不是傻逼!【参考方案2】:参考这个:
@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
【讨论】:
没有帮助!详细信息页面也缺少信息【参考方案3】:在此处查看示例:Input and Output binary streams using JERSEY?
伪代码是这样的(上面提到的帖子中还有一些其他类似的选项):
@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
【讨论】:
while(以上是关于如何使用 Java REST 服务和数据流下载文件的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Google Drive 上传和下载文件(使用 Rest Api v3)
如何使用 spring rest 服务/spring boot 下载 Excel