Return 来自 Spring @Controller 的文件具有 OutputStream
Return file from Spring @Controller having OutputStream
我想 return 来自 Spring 控制器的文件。我已经有 API 可以给我任何 OutputStream 的实现,然后我需要将它发送给用户。
所以流程是这样的:
获取输出流 -> 服务将此输出流传递给控制器 -> 控制器必须将其发送给用户
我想我需要输入流来完成它,我还发现 Apache Commons api 功能如下所示:
IOUtils.copy(InputStream is, OutputStream os)
但问题是,它将它转换到另一边 -> 不是从 os 到 is,而是从 [=52] =]是到os.
编辑
说清楚,因为我看到答案不正确:
我使用 Dropbox api 并在 OutputStream 中接收文件,我希望在输入一些 URL
时将此输出流发送给用户
FileOutputStream outputStream = new FileOutputStream(); //can be any instance of OutputStream
DbxEntry.File downloadedFile = client.getFile("/fileName.mp3", null, outputStream);
这就是为什么我在谈论将 outputstream 转换为 inputstream,但不知道如何去做。此外,我想有更好的方法来解决这个问题(也许 return 字节数组以某种方式来自输出流)
我试图通过参数将 servlet outputstream [response.getOutputstream()] 传递给从 dropbox 下载文件的方法,但它根本不起作用
编辑 2
我的应用程序的 "flow" 是这样的:@Joeblade
用户输入url:/download/{file_name}
Spring 控制器捕获 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[=71 下载文件=],并将其全部放入 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;
}
Controler 收到文件(我根本不知道我上面说的是哪种形式)然后将其传递给 用户
所以事情是通过调用 dropboxClient.getFile()
方法接收 OutputStream
和下载的文件,然后必须将包含下载文件的 OutputStream
发送给用户,怎么做?
从 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();
...
}
确保在出现异常时使用 try/catch 关闭流。
我推荐阅读 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);
}
michal.kreuzman回答
我本来打算自己写一些类似的东西,但当然已经有人回答了。
如果你只想传递流而不是首先将所有内容都放在内存中,你可以使用 this answer
我没有测试过这个(不是在工作中)但它看起来是合法的:)
@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?
}
事实是,这与 IOUtils.copy / IOUtils.copyLarge 几乎相同。线路:2128
你说的那个抄错方向了
但是首先要确保你理解你的问题。如果您想从输出流(用于写入的对象)读取并写入输入流(要读取的对象),那么我认为您真正想要的是写入一个也提供读取选项的对象。
为此,您可以使用 PipedInputStream 和 PipedOutputStream。它们连接在一起,以便写入输出流的字节可以从相应的输入流中读取。
所以在您接收字节的位置,我假设您正在将字节写入输出流。
这样做:
// 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]);
如果你使用IOUtils.copy它将继续读取直到输出流关闭。所以确保它在开始之前已经关闭(如果你 运行 write/read 在同一个线程上)或者使用另一个线程写入输出缓冲区并在最后关闭它。
如果这仍然不是您要查找的内容,那么您必须完善您的问题。
您可以使用 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);
您可以对其他 流对 执行相同的操作。例如。文件流:
// 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");
并且,当使用 Spring MVC 时,您绝对可以 return 包含您的文件的 byte[]
。只需确保使用 @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();
}
写入响应输出流时要记住的一件事是,对您定期包装它的任何编写器调用 flush()
是一个非常好的主意。这样做的原因是断开的连接(例如由用户取消下载引起)可能不会在很长一段时间内抛出异常,如果有的话。这实际上可能是您容器上的资源泄漏。
最可取的解决方案是使用 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);
}
在您的情况下,最节省内存的解决方案是将响应 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());
}
API读取的数据将直接发送给用户。不需要任何类型的额外字节缓冲区。
至于PipedInputStream
/PipedOutputStream
,它们是用于2个线程之间的阻塞通信。 PipedOutputStream
在 1024 字节后(默认情况下)阻止写入线程,直到其他线程开始从管道末端读取 (PipedInputStream
)。
我想 return 来自 Spring 控制器的文件。我已经有 API 可以给我任何 OutputStream 的实现,然后我需要将它发送给用户。
所以流程是这样的:
获取输出流 -> 服务将此输出流传递给控制器 -> 控制器必须将其发送给用户
我想我需要输入流来完成它,我还发现 Apache Commons api 功能如下所示:
IOUtils.copy(InputStream is, OutputStream os)
但问题是,它将它转换到另一边 -> 不是从 os 到 is,而是从 [=52] =]是到os.
编辑
说清楚,因为我看到答案不正确:
我使用 Dropbox api 并在 OutputStream 中接收文件,我希望在输入一些 URL
FileOutputStream outputStream = new FileOutputStream(); //can be any instance of OutputStream
DbxEntry.File downloadedFile = client.getFile("/fileName.mp3", null, outputStream);
这就是为什么我在谈论将 outputstream 转换为 inputstream,但不知道如何去做。此外,我想有更好的方法来解决这个问题(也许 return 字节数组以某种方式来自输出流)
我试图通过参数将 servlet outputstream [response.getOutputstream()] 传递给从 dropbox 下载文件的方法,但它根本不起作用
编辑 2
我的应用程序的 "flow" 是这样的:@Joeblade
用户输入url:/download/{file_name}
Spring 控制器捕获 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[=71 下载文件=],并将其全部放入 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; }
Controler 收到文件(我根本不知道我上面说的是哪种形式)然后将其传递给 用户
所以事情是通过调用 dropboxClient.getFile()
方法接收 OutputStream
和下载的文件,然后必须将包含下载文件的 OutputStream
发送给用户,怎么做?
从 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();
...
}
确保在出现异常时使用 try/catch 关闭流。
我推荐阅读 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);
}
michal.kreuzman回答
我本来打算自己写一些类似的东西,但当然已经有人回答了。
如果你只想传递流而不是首先将所有内容都放在内存中,你可以使用 this answer 我没有测试过这个(不是在工作中)但它看起来是合法的:)
@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?
}
事实是,这与 IOUtils.copy / IOUtils.copyLarge 几乎相同。线路:2128 你说的那个抄错方向了
但是首先要确保你理解你的问题。如果您想从输出流(用于写入的对象)读取并写入输入流(要读取的对象),那么我认为您真正想要的是写入一个也提供读取选项的对象。
为此,您可以使用 PipedInputStream 和 PipedOutputStream。它们连接在一起,以便写入输出流的字节可以从相应的输入流中读取。
所以在您接收字节的位置,我假设您正在将字节写入输出流。 这样做:
// 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]);
如果你使用IOUtils.copy它将继续读取直到输出流关闭。所以确保它在开始之前已经关闭(如果你 运行 write/read 在同一个线程上)或者使用另一个线程写入输出缓冲区并在最后关闭它。
如果这仍然不是您要查找的内容,那么您必须完善您的问题。
您可以使用 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);
您可以对其他 流对 执行相同的操作。例如。文件流:
// 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");
并且,当使用 Spring MVC 时,您绝对可以 return 包含您的文件的 byte[]
。只需确保使用 @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();
}
写入响应输出流时要记住的一件事是,对您定期包装它的任何编写器调用 flush()
是一个非常好的主意。这样做的原因是断开的连接(例如由用户取消下载引起)可能不会在很长一段时间内抛出异常,如果有的话。这实际上可能是您容器上的资源泄漏。
最可取的解决方案是使用 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);
}
在您的情况下,最节省内存的解决方案是将响应 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());
}
API读取的数据将直接发送给用户。不需要任何类型的额外字节缓冲区。
至于PipedInputStream
/PipedOutputStream
,它们是用于2个线程之间的阻塞通信。 PipedOutputStream
在 1024 字节后(默认情况下)阻止写入线程,直到其他线程开始从管道末端读取 (PipedInputStream
)。