问题 使用REST模板Java Spring MVC从服务器下载大文件


我有一个REST服务,它向我发送一个大的ISO文件,REST服务中没有问题。 现在我已经编写了一个Web应用程序,它调用其余服务来获取文件,在客户端(Web应用程序)端我收到Out Of memory Exception.Below是我的代码

HttpHeaders headers = new HttpHeaders();//1 Line

    headers.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));//2 Line
    headers.set("Content-Type","application/json");//3 Line
    headers.set("Cookie", "session=abc");//4 Line
    HttpEntity statusEntity=new HttpEntity(headers);//5 Line
    String uri_status=new String("http://"+ip+":8080/pcap/file?fileName={name}");//6 Line

    ResponseEntity<byte[]>resp_status=rt.exchange(uri_status, HttpMethod.GET, statusEntity, byte[].class,"File5.iso");//7 Line

我在7行收到内存异常,我想我必须缓冲并获取部分内容,但不知道如何从服务器获取此文件,文件大小约为500到700 MB。 谁能请你帮忙。

异常堆栈:

  org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Java heap space
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:972)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
root cause

java.lang.OutOfMemoryError: Java heap space
    java.util.Arrays.copyOf(Arrays.java:3236)
    java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
    java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
    java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
    org.springframework.util.FileCopyUtils.copy(FileCopyUtils.java:113)
    org.springframework.util.FileCopyUtils.copyToByteArray(FileCopyUtils.java:164)
    org.springframework.http.converter.ByteArrayHttpMessageConverter.readInternal(ByteArrayHttpMessageConverter.java:58)
    org.springframework.http.converter.ByteArrayHttpMessageConverter.readInternal(ByteArrayHttpMessageConverter.java:1)
    org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:153)
    org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:81)
    org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:627)
    org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1)
    org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:454)
    org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409)
    org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:385)
    com.pcap.webapp.HomeController.getPcapFile(HomeController.java:186)

我的服务器端REST服务代码工作正常

@RequestMapping(value = URIConstansts.GET_FILE, produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE}, method = RequestMethod.GET)
public void getFile(@RequestParam(value="fileName", required=false) String fileName,HttpServletRequest request,HttpServletResponse response) throws IOException{



    byte[] reportBytes = null;
    File result=new File("/home/arpit/Documents/PCAP/dummyPath/"+fileName);

    if(result.exists()){
        InputStream inputStream = new FileInputStream("/home/arpit/Documents/PCAP/dummyPath/"+fileName); 
        String type=result.toURL().openConnection().guessContentTypeFromName(fileName);
        response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
        response.setHeader("Content-Type",type);

        reportBytes=new byte[100];//New change
        OutputStream os=response.getOutputStream();//New change
        int read=0;
        while((read=inputStream.read(reportBytes))!=-1){
            os.write(reportBytes,0,read);
        }
        os.flush();
        os.close();






    }

9013
2017-10-07 09:16


起源

你可以发布异常(堆栈跟踪)吗? - Andrea Girardi
你试图将整个文件读入内存中的ResponseEntity <byte []> resp_status ...你需要在两端使用缓冲区,看看 stackoverflow.com/questions/15800565/... - freakman
@freakman是的我在服务器端跟着相同的帖子,我在REST代码中内存不足并且跟着相同的帖子并且它得到了解决。但是在客户端我面临的问题 - arpit joshi
是的,你在服务器端做到了这一点,但是客户端仍在读取整个文件并尝试将其放在byte []中。您可以使用url,并将此流直接写入文件 - 请查看 - stackoverflow.com/questions/22244985/... - freakman
你在服务器端有多少ram?您还可以在tomcat中更改connectionTimeout,因此不会发生此问题 - We are Borg


答案:


我就是这样做的。基于此提示 春季Jira问题

RestTemplate restTemplate // = ...;

// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
        .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

// Streams the response instead of loading it all in memory
ResponseExtractor<Void> responseExtractor = response -> {
    // Here I write the response to a file but do what you like
    Path path = Paths.get("some/path");
    Files.copy(response.getBody(), path);
    return null;
};
restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor);

从前面提到的Jira问题:

请注意,您不能简单地从提取器返回InputStream,因为在execute方法返回时,底层连接和流已经关闭。

春季5的更新

春季5推出了 WebClient 允许异步(例如非阻塞)http请求的类。从文档:

与RestTemplate相比,WebClient是:

  • 非阻塞,被动,并支持更高的并发性和更少的硬件资源。
  • 提供了一个利用Java 8 lambda的功能API。
  • 支持同步和异步方案。
  • 支持从服务器向上或向下流式传输。

15
2017-07-29 17:33



为什么输入流关闭?有没有办法返回流而不是路径? - danidacar
@danip看到我的更新。顺便说一句,如果您对此感兴趣,请花点时间 批准我的文档主题 ! - bernie
我为什么比如何更加好奇!我认为答案是RestTemplate需要确保InputStream是关闭的。 responseExtractor甚至会抛出一个IOException,所以它真的建议尝试在本地存储文件。之后,您可以返回文件或路径。 - danidacar
我最终实现了一个自定义的InputStreamWrapper,它接受响应InputStream并在内部使用临时文件来缓存文件并在关闭流后删除它。不是很优雅,但我没有找到更好的解决方案.. - Chris S.
你能举例说明Spring 5 WebClient的这段代码吗? - dukethrash


这可以防止将整个请求加载到内存中。

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
RestTemplate rest = new RestTemplate(requestFactory);

对于java.lang.OutOfMemoryError:可以解决Java堆空间为JVM添加更多内存:

-Xmxn指定内存分配池的最大大小(以字节为单位)。此值必须是1024的倍数,大于2 MB。附加   字母k或K表示千字节,或m或M表示兆字节。   根据系统配置在运行时选择默认值。

对于服务器部署,-Xms和-Xmx通常设置为相同的值。   请参阅垃圾收集器人体工程学    http://docs.oracle.com/javase/7/docs/technotes/guides/vm/gc-ergonomics.html

例子:

-Xmx83886080
  -Xmx81920k
  -Xmx80m

您可能遇到的问题与您尝试执行的请求(下载大文件)并不严格相关,但为该进程分配的内存不足。


2
2017-10-07 09:24



我更新了代码,但收到了相同的字符串uri_status = new String(“http://”+ ip +“:8080 / pcap / file?fileName = {name}”); SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setBufferRequestBody(假); rt.setRequestFactory(requestFactory); ResponseEntity <byte []> resp_status = rt.exchange(uri_status,HttpMethod.GET,statusEntity,byte [] .class,“File5.iso”); - arpit joshi
这是我的STS JVM Config -startup ../Eclipse/plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar --launcher.library ../Eclipse/plugins/org.eclipse.equinox.launcher。 cocoa.macosx.x86_64_1.1.300.v20150602-1417 -product org.springsource.sts.ide --launcher.defaultAction openFile -vmargs -Dosgi.requiredJavaVersion = 1.6 -Xms40m -Xmx768m -XX:MaxPermSize = 256m -Xverify:none -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts -Xdock:icon = .. / Resources / sts.icns - arpit joshi
尝试将Xmx从-Xmx768m增加到-Xmx1024 - Andrea Girardi
“java.lang.OutOfMemoryError:Java堆空间可以解决为JVM添加更多内存”你在开玩笑吗?这不是一个解决方案 - 您只需添加更多内存,但是当更大的文件出现时会发生什么? - nyxz
只是为了解决遭受同样问题的人:这只适用于将文件发送到休息服务,而不是下载它们! - Chris S.


您应该使用多部分文件附件,因此文件流不会加载到内存中。 在此示例中,我使用Apache CXF实现的休息服务。

...
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
...

@Override
@Path("/put")
@Consumes("multipart/form-data")
@Produces({ "application/json" })
@POST
public SyncResponseDTO put( List<Attachment> attachments) {
    SyncResponseDTO response = new SyncResponseDTO();
    try {
        for (Attachment attr : attachments) {
            log.debug("get input filestream: " + new Date());
            InputStream is = attr.getDataHandler().getInputStream();

-1
2017-10-07 09:41



但我在Spring MVC中有我的REST服务,因此我必须使用Rest Template - arpit joshi
您也可以在Rest模板中使用Multipart File Upload。 - Donald
是但是这是下载,我想从服务器获取文件(其余的是用spring mvc编写的) - arpit joshi
对不起,我误解了你的问题,请试试这个: stackoverflow.com/a/7107001/1897196 - Donald
stackoverflow.com/a/7107001/1897196与文件上传有关,即:响应发送文件的服务器端,我的问题是从服务器获取文件 - arpit joshi