Java 从具有 OutputStream 的 Spring @Controller 返回文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27741283/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Return file from Spring @Controller having OutputStream
提问by azalut
I want to return a file from a Spring controller. I already have API that can give me any implementation of OutputStream and then I need to send it to a user.
我想从 Spring 控制器返回一个文件。我已经有了可以为我提供 OutputStream 的任何实现的 API,然后我需要将它发送给用户。
So the flow is something like that:
所以流程是这样的:
getting outputstream ->service passes this outputstream to controller ->controller has to send it to a user
获取输出流->服务将此输出流传递给控制器->控制器必须将其发送给用户
I think I need inputstream to do it and I have also found Apache Commons api feature that looks like this:
我想我需要输入流来做到这一点,而且我还发现了如下所示的 Apache Commons api 功能:
IOUtils.copy(InputStream is, OutputStream os)
but the problem is, it converts it to the other side -> not from osto is, but from isto os.
但问题是,它将它转换到另一侧 -> 不是从os到is,而是从is到os。
Edit
编辑
to be clear, because I see the answers are not hitting right thing:
I use Dropbox api and recieve file in OutputStream and I want this output stream to be sent to user while entering some URL
说清楚,因为我看到答案没有达到正确的目的:
我使用 Dropbox api 并在 OutputStream 中接收文件,我希望在输入一些 URL 时将此输出流发送给用户
FileOutputStream outputStream = new FileOutputStream(); //can be any instance of OutputStream
DbxEntry.File downloadedFile = client.getFile("/fileName.mp3", null, outputStream);
Thats why i was talking about converting outputstreamto inputstream, but have no idea how to do it. Furthermore, I suppose that there is better way to solve this (maybe return byte array somehow from outputstream)
这就是为什么我在谈论将outputstream转换为inputstream,但不知道如何去做。此外,我想有更好的方法来解决这个问题(也许从输出流以某种方式返回字节数组)
I was trying to pass servlet outputstream [response.getOutputstream()] through parameter to the method that downloads file from dropbox, but it didnt work, at all
我试图通过参数将 servlet outputstream [response.getOutputstream()] 传递给从 dropbox 下载文件的方法,但它根本不起作用
Edit 2
编辑 2
The "flow" of my app is something like this: @Joeblade
我的应用程序的“流程”是这样的:@Joeblade
User enters url: /download/{file_name}
Spring Controller captures the url and calls the @Service layer to downloadthe file and pass it to that controller:
@RequestMapping(value = "download/{name}", method = RequestMethod.GET) public void getFileByName(@PathVariable("name") final String name, HttpServletResponse response) throws IOException { response.setContentType("audio/mpeg3"); response.setHeader("Content-Disposition", "attachment; filename=" + name); service.callSomeMethodAndRecieveDownloadedFileInSomeForm(name); // <- and this file(InputStream/OutputStream/byte[] array/File object/MultipartFile I dont really know..) has to be sent to the user }
Now the @Service calls Dropbox APIand downloads the file by specified file_name, and puts it all to the OutputStream, and then passes it (in some form.. maybe OutputStream, byte[] array or any other object - I dont know which is better to use) to the controller:
public SomeObjectThatContainsFileForExamplePipedInputStream callSomeMethodAndRecieveDownloadedFileInSomeForm(final String name) throws IOException { //here any instance of OutputStream - it needs to be passed to client.getFile lower (for now it is PipedOutputStream) PipedInputStream inputStream = new PipedInputStream(); // for now PipedOutputStream outputStream = new PipedOutputStream(inputStream); //some dropbox client object DbxClient client = new DbxClient(); try { //important part - Dropbox API downloads the file from Dropbox servers to the outputstream object passed as the third parameter client.getFile("/" + name, null, outputStream); } catch (DbxException e){ e.printStackTrace(); } finally { outputStream.close(); } return inputStream; }
Controler recieves the file(I dont know, at all, in which form as I said upper) and passes it then to the user
用户输入网址:/download/{file_name}
Spring Controller 捕获 url 并调用@Service 层下载文件并将其传递给该控制器:
@RequestMapping(value = "download/{name}", method = RequestMethod.GET) public void getFileByName(@PathVariable("name") final String name, HttpServletResponse response) throws IOException { response.setContentType("audio/mpeg3"); response.setHeader("Content-Disposition", "attachment; filename=" + name); service.callSomeMethodAndRecieveDownloadedFileInSomeForm(name); // <- and this file(InputStream/OutputStream/byte[] array/File object/MultipartFile I dont really know..) has to be sent to the user }
现在@Service 调用Dropbox API并通过指定的file_name下载文件,并将其全部放入 OutputStream,然后传递它(以某种形式......也许是 OutputStream、byte[] 数组或任何其他对象 - 我不知道哪个是更好地使用)到控制器:
public SomeObjectThatContainsFileForExamplePipedInputStream callSomeMethodAndRecieveDownloadedFileInSomeForm(final String name) throws IOException { //here any instance of OutputStream - it needs to be passed to client.getFile lower (for now it is PipedOutputStream) PipedInputStream inputStream = new PipedInputStream(); // for now PipedOutputStream outputStream = new PipedOutputStream(inputStream); //some dropbox client object DbxClient client = new DbxClient(); try { //important part - Dropbox API downloads the file from Dropbox servers to the outputstream object passed as the third parameter client.getFile("/" + name, null, outputStream); } catch (DbxException e){ e.printStackTrace(); } finally { outputStream.close(); } return inputStream; }
控制器接收文件(我完全不知道我上面说的是以哪种形式)然后将其传递给用户
So the thing is to recieve OutputStream
with the downloaded file by calling dropboxClient.getFile()
method and then this OutputStream
that contains the downloaded file, has to be sent to the user, how to do this?
所以事情是OutputStream
通过调用dropboxClient.getFile()
方法接收下载的文件,然后这个OutputStream
包含下载的文件,必须发送给用户,如何做到这一点?
采纳答案by wassgren
You could use the ByteArrayOutputStream
and ByteArrayInputStream
. Example:
您可以使用ByteArrayOutputStream
和ByteArrayInputStream
。例子:
// A ByteArrayOutputStream holds the content in memory
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Do stuff with your OutputStream
// To convert it to a byte[] - simply use
final byte[] bytes = outputStream.toByteArray();
// To convert bytes to an InputStream, use a ByteArrayInputStream
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
You can do the same with other stream pairs. E.g. the file streams:
您可以对其他流对执行相同操作。例如文件流:
// Create a FileOutputStream
FileOutputStream fos = new FileOutputStream("filename.txt");
// Write contents to file
// Always close the stream, preferably in a try-with-resources block
fos.close();
// The, convert the file contents to an input stream
final InputStream fileInputStream = new FileInputStream("filename.txt");
And, when using Spring MVC you can definitely return a byte[]
that contains your file. Just make sure that you annotate your response with @ResponseBody
. Something like this:
而且,在使用 Spring MVC 时,您绝对可以返回byte[]
包含您的文件的 a 。只需确保您使用 注释您的响应@ResponseBody
。像这样的东西:
@ResponseBody
@RequestMapping("/myurl/{filename:.*}")
public byte[] serveFile(@PathVariable("file"} String file) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DbxEntry.File downloadedFile = client.getFile("/" + filename, null, outputStream);
return outputStream.toByteArray();
}
回答by John Smith
Get the OutputStream from the HttpServletResponse and write the file to it (in this example using IOUtils from Apache Commons)
从 HttpServletResponse 获取 OutputStream 并将文件写入其中(在本例中使用来自 Apache Commons 的 IOUtils)
@RequestMapping(value = "/download", method = RequestMethod.GET)
public void download(HttpServletResponse response) {
...
InputStream inputStream = new FileInputStream(new File(PATH_TO_FILE)); //load the file
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
...
}
Make sure you use a try/catch to close the streams in case of an exception.
确保在出现异常时使用 try/catch 关闭流。
回答by Joeblade
I recommend reading this answer
我建议阅读这个答案
@ResponseBody
@RequestMapping("/photo2", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public byte[] testphoto() throws IOException {
InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
return IOUtils.toByteArray(in);
}
answered by michal.kreuzman
michal.kreuzman 回答
I was going to write something similar myself but ofcourse it's already been answered.
我打算自己写一些类似的东西,但当然已经有人回答了。
If you want to just pass the stream instead of first getting everything in memory you could use this answerI haven't tested this (not at work) but it looks legit :)
如果您只想传递流而不是首先将所有内容都保存在内存中,您可以使用此答案我还没有测试过这个(不在工作中),但它看起来合法:)
@RequestMapping(value = "report1", method = RequestMethod.GET, produces = "application/pdf")
@ResponseBody
public void getReport1(OutputStream out) {
InputStream in; // retrieve this from wherever you are receiving your stream
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
in.close();
out.flush(); // out.close?
}
The thing is, this is pretty much the same as IOUtils.copy/ IOUtils.copyLarge does. line: 2128 Which you say copies the wrong direction.
问题是,这与IOUtils.copy/ IOUtils.copyLarge几乎相同。行:2128 你说复制的方向错了。
However first make sure you understand what you ask. If you want to read from an outputstream(object for writing) and write to an input stream (object to read from) then I think what you really want is to write to an object that also supplies a read option.
但是,首先要确保您了解您的要求。如果您想从输出流(用于写入的对象)中读取并写入输入流(用于读取的对象),那么我认为您真正想要的是写入一个也提供读取选项的对象。
for that you could use a PipedInputStream and PipedOutputStream. These are connected together so that bytes written to the outputstream are available to be read from the corresponding input stream.
为此,您可以使用 PipedInputStream 和 PipedOutputStream。它们连接在一起,以便写入输出流的字节可以从相应的输入流中读取。
so in the location where you are receiving the bytes I assume you are writing bytes to an outputstream. there do this:
所以在您接收字节的位置,我假设您正在将字节写入输出流。有这样做:
// set up the input/output stream so that bytes written to writeToHere are available to be read from readFromhere
PipedInputStream readFromHere = new PipedInputStream();
PipedOutputStream writeToHere = new PipedOutputStream(readFromHere);
// write to the outputstream as you like
writeToHere.write(...)
// or pass it as an outputstream to an external method
someMather(writeToHere);
// when you're done close this end.
writeToHere.close();
// then whenever you like, read from the inputstream
IOUtils.copy(readFromHere, out, new byte[1024]);
If you use IOUtils.copy it will continue to read until the outputstream is closed. so make sure that it is already closed before starting (if you run write/read on the same thread) or use another thread to write to the output buffer and close it at the end.
如果您使用 IOUtils.copy 它将继续读取,直到输出流关闭。所以确保它在开始之前已经关闭(如果你在同一个线程上运行写入/读取)或使用另一个线程写入输出缓冲区并在最后关闭它。
If this is still not what you're looking for then you'll have to refine your question.
如果这仍然不是您要查找的内容,那么您必须完善您的问题。
回答by seismic
One thing to keep in mind when writing to the response outputstream is that it is a very good idea to call flush() on whatever writer that you've wrapped it with periodically. The reason for this is that a broken connection (for example caused by a user canceling a download) may not end up throwing an exception for a long time, if ever. This can effectively be a resource leak on your container.
在写入响应输出流时要记住的一件事是,在您定期包装它的任何写入器上调用 flush() 是一个非常好的主意。这样做的原因是断开的连接(例如由用户取消下载引起的)可能不会在很长一段时间内引发异常,如果有的话。这实际上可能是容器上的资源泄漏。
回答by Konstantin Konyshev
The most preferable solution is to use InputStreamResourcewith ResponseEntity
. All you need is set Content-Length
manually:
最可取的解决方案是将InputStreamResource与ResponseEntity
. 您只需要Content-Length
手动设置:
@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity download() throws IOException {
String filePath = "PATH_HERE";
InputStream inputStream = new FileInputStream(new File(filePath));
InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(Files.size(Paths.get(filePath));
return new ResponseEntity(inputStreamResource, headers, HttpStatus.OK);
}
回答by Lu55
The most memory-efficient solution in your case would be to pass the response OutputStream
right to the Dropbox API:
在您的情况下,最节省内存的解决方案是将响应OutputStream
权传递给 Dropbox API:
@GetMapping(value = "download/{name}")
public void getFileByName(@PathVariable("name") final String name, HttpServletResponse response)
throws IOException, DbxException {
response.setContentType("audio/mpeg3");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + name + "\"");
response.setContentLength(filesize); // if you know size of the file in advance
new DbxClient().getFile("/" + name, null, response.getOutputStream());
}
Data read by the API will be sent directly to the user. No additional byte buffer of any type is required.
API 读取的数据将直接发送给用户。不需要任何类型的额外字节缓冲区。
As for PipedInputStream
/PipedOutputStream
, they are intended for the blocking communication between 2 threads. PipedOutputStream
blocks writing thread after 1024 bytes (by default) until some other thread start reading from the end of the pipe (PipedInputStream
).
至于PipedInputStream
/ PipedOutputStream
,它们用于阻止 2 个线程之间的通信。PipedOutputStream
在 1024 字节(默认情况下)之后阻止写入线程,直到其他线程从管道 ( PipedInputStream
)的末尾开始读取。