sirve responsebody requestbody que para mvc form example docs java spring rest resttemplate

java - responsebody - spring mvc crud ajax



Descargue el archivo grande del servidor utilizando la plantilla REST Java Spring MVC (5)

Tengo un servicio REST que me envía un archivo ISO grande, no hay problemas en el servicio REST. Ahora he escrito una aplicación web que llama al servicio de descanso para obtener el archivo, en el lado del cliente (aplicación web) recibo una excepción de memoria insuficiente. A continuación está mi código

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

Recibo una excepción de memoria insuficiente en la línea 7, supongo que tendré que almacenar en búfer y obtener partes, pero no sé cómo puedo obtener este archivo del servidor, el tamaño del archivo es de alrededor de 500 a 700 MB. ¿Alguien puede ayudarme?

Pila de excepción:

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)

Mi código de servicio REST del lado del servidor que funciona bien es

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


Así es como lo hago. Basado en sugerencias de este tema de 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);

De la cuestión antes mencionada de Jira:

Tenga en cuenta que no puede simplemente devolver InputStream desde el extractor, porque para cuando regrese el método de ejecución, la conexión y la secuencia subyacente ya están cerradas.

Actualización para la primavera 5

Spring 5 introdujo la clase WebClient que permite solicitudes http asíncronas (por ejemplo, sin bloqueo). Del documento:

En comparación con RestTemplate, el WebClient es:

  • sin bloqueo, reactivo y admite una mayor concurrencia con menos recursos de hardware.
  • proporciona una API funcional que aprovecha Java 8 lambdas.
  • admite escenarios sincrónicos y asincrónicos.
  • admite la transmisión ascendente o descendente desde un servidor.

Como mencionó, puede usar WebClient para lograr esto:

public void downloadFileUrl( HttpServletResponse response ) throws IOException { WebClient webClient = WebClient.create(); // Request service to get file data Flux<DataBuffer> fileDataStream = webClient.get() .uri( this.fileUrl ) .accept( MediaType.APPLICATION_OCTET_STREAM ) .retrieve() .bodyToFlux( DataBuffer.class ); // Streams the stream from response instead of loading it all in memory DataBufferUtils.write( fileDataStream, response.getOutputStream() ) .map( DataBufferUtils::release ) .then() .block(); }

Todavía puede usar WebClient incluso si no tiene una pila de Reactive Server: Rossen Stoyanchev (miembro del equipo de Spring Framework) lo explica bastante bien en la Guía de "Reactivo" para la presentación de Spring MVC Developers . Durante esta presentación, Rossen Stoyanchev mencionó que pensaban en despreciar RestTemplate , pero decidieron posponerlo, ¡pero aún puede suceder en el futuro !

La principal desventaja de usar WebClient hasta ahora es una curva de aprendizaje bastante pronunciada (programación reactiva), pero creo que no hay forma de evitarla en el futuro, por lo que es mejor echarle un vistazo antes que después.


Debe usar un archivo adjunto de varias partes, de modo que la secuencia del archivo no se cargue en la memoria. En este ejemplo, uso un servicio de descanso implementado con 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();


Esto evita cargar toda la solicitud en la memoria.

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

Para java.lang.OutOfMemoryError: el espacio de almacenamiento dinámico de Java se puede resolver agregando más memoria a la JVM:

-Xmxn Especifica el tamaño máximo, en bytes, del grupo de asignación de memoria. Este valor debe ser un múltiplo de 1024 mayor que 2 MB. Agregue la letra k o K para indicar kilobytes, o mo M para indicar megabytes. El valor predeterminado se elige en tiempo de ejecución en función de la configuración del sistema.

Para implementaciones de servidor, -Xms y -Xmx a menudo se configuran con el mismo valor. Consulte Ergonomía del recolector de basura en http://docs.oracle.com/javase/7/docs/technotes/guides/vm/gc-ergonomics.html

Ejemplos:

-Xmx83886080
-Xmx81920k
-Xmx80m

Probablemente el problema que tenga no esté estrictamente relacionado con la solicitud que está intentando ejecutar (descargar un archivo grande), pero la memoria asignada para el proceso no es suficiente.


Una mejor versión de la respuesta correcta anterior podría ser el siguiente código. Este método enviará una solicitud de descarga a otra aplicación o servicio que actúe como fuente real de verdad para la información descargada.

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; } }