Подтвердить что ты не робот

Загрузка большого файла с сервера с использованием шаблона REST Java Spring MVC

У меня есть служба REST, которая отправляет мне большой файл ISO, никаких проблем в службе REST нет. Теперь я написал веб-приложение, которое вызывает службу остального, чтобы получить файл, на стороне клиента (веб-приложение) я получаю 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 МБ. Кто-нибудь может помочь.

Исключительный стек:

  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();






    }
4b9b3361

Ответ 1

Вот как я это делаю. Основано на подсказках из этого номера Spring 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);

Из вышеупомянутого вопроса Джира:

Обратите внимание, что вы не можете просто вернуть InputStream из экстрактора, потому что к моменту возврата метода execute базовое соединение и поток уже закрыты.

Обновление для весны 5

Spring 5 представил класс WebClient который разрешает асинхронные (например, неблокирующие) http-запросы. Из документа:

По сравнению с RestTemplate, WebClient является:

  • не блокирует, реагирует и поддерживает более высокий параллелизм с меньшими аппаратными ресурсами.
  • предоставляет функциональный API, который использует преимущества лямбда-кода Java 8.
  • поддерживает как синхронные, так и асинхронные сценарии.
  • поддерживает потоковую передачу вверх или вниз с сервера.

Ответ 2

Это предотвращает загрузку всего запроса в память.

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

Для java.lang.OutOfMemoryError: Java-кучу пространства можно решить, добавив больше памяти в JVM:

-Xmxn Задает максимальный размер пула распределения памяти в байтах. Это значение должно быть кратно 1024 больше, чем 2 МБ. Добавить буквы k или K, чтобы указать килобайты, или m или M, чтобы указать мегабайты. Значение по умолчанию выбирается во время выполнения на основе конфигурации системы.

Для развертывания сервера, -Xms и -Xmx часто устанавливаются одинаковые значения. См. Эргономику коллекционера мусора в http://docs.oracle.com/javase/7/docs/technotes/guides/vm/gc-ergonomics.html

Примеры:

-Xmx83886080
-Xmx81920k
-Xmx80m

Вероятно, проблема у вас не связана с запросом, который вы пытаетесь выполнить (загрузить большой файл), но недостаточно памяти для этого процесса.

Ответ 3

Лучшей версией вышеуказанного правильного ответа может быть следующий код. Этот метод отправляет запрос на загрузку в другое приложение или службу, действующую в качестве фактического источника правды для загруженной информации.

public void download(HttpServletRequest req, HttpServletResponse res, String url)
            throws ResourceAccessException, GenericException {
        try {
            logger.info("url::" + url);
            if (restTemplate == null)
                logger.info("******* rest template is null***********************");
            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<ResponseEntity<InputStream>> responseExtractor = response -> {

                String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
                if (contentDisposition != null) {
                    // Temporary location for files that will be downloaded from micro service and
                    // act as final source of download to user
                    String filePath = "/home/devuser/fileupload/download_temp/" + contentDisposition.split("=")[1];
                    Path path = Paths.get(filePath);
                    Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING);

                    // Create a new input stream from temporary location and use it for downloading
                    InputStream inputStream = new FileInputStream(filePath);
                    String type = req.getServletContext().getMimeType(filePath);
                    res.setHeader("Content-Disposition", "attachment; filename=" + contentDisposition.split("=")[1]);
                    res.setHeader("Content-Type", type);

                    byte[] outputBytes = new byte[100];
                    OutputStream os = res.getOutputStream();
                    int read = 0;
                    while ((read = inputStream.read(outputBytes)) != -1) {
                        os.write(outputBytes, 0, read);
                    }
                    os.flush();
                    os.close();
                    inputStream.close();
                }
                return null;
            };
            restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor);
        } catch (Exception e) {
            logger.info(e.toString());
            throw e;
        }
    }

Ответ 4

Вы должны использовать вложение многостраничного файла, поэтому поток файлов не загружается в память. В этом примере я использую службу отдыха, реализованную с 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();