J'ai un service REST qui m'envoie un gros fichier ISO, il n'y a aucun problème dans le service REST. Maintenant, j'ai écrit une application Web qui appelle le service de repos pour récupérer le fichier, côté client (application web), je reçois une exception de mémoire insuffisante. Voici mon code
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
Je reçois une exception de mémoire insuffisante à 7 lignes, je suppose que je vais devoir mettre en mémoire tampon et obtenir des pièces, mais je ne sais pas comment puis-je obtenir ce fichier du serveur, la taille du fichier est d'environ 500 à 700 Mo. Quelqu'un peut-il s'il vous plaît aider.
Pile d'exception:
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)
Mon côté serveur REST Code de service qui fonctionne bien est
@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();
}
Voici comment je le fais. Basé sur des indices de ce problème 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);
Du numéro Jira susmentionné:
Notez que vous ne pouvez pas simplement renvoyer le InputStream de l'extracteur, car au moment où la méthode d'exécution revient, la connexion et le flux sous-jacents sont déjà fermés.
Spring 5 a introduit la classe WebClient
qui permet les requêtes http asynchrones (par exemple non bloquantes). Du doc:
Par rapport au RestTemplate, le WebClient est:
- non bloquant, réactif et prend en charge une concurrence plus élevée avec moins de ressources matérielles.
- fournit une API fonctionnelle qui tire parti de Java 8 lambdas.
- prend en charge les scénarios synchrones et asynchrones.
- prend en charge le streaming vers le haut ou vers le bas à partir d'un serveur.
Comme @ bernie mentionné, vous pouvez utiliser WebClient pour y parvenir:
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();
}
Vous pouvez toujours utiliser WebClient même si vous n'avez pas de pile Reactive Server - Rossen Stoyanchev (membre de l'équipe Spring Framework) l'explique assez bien dans la présentation Guide de "Reactive" pour Spring MVC Developers . Au cours de cette présentation, Rossen Stoyanchev a mentionné qu'ils pensaient à déprécier RestTemplate , mais ils ont décidé de le reporter après tout, mais cela peut encore arriver à l'avenir !
Le principal inconvénient de l'utilisation de WebClient jusqu'à présent est une courbe d'apprentissage assez abrupte (programmation réactive), mais je pense qu'il n'y a aucun moyen d'éviter à l'avenir, il vaut donc mieux y jeter un œil plus tôt que ce dernier.
Une meilleure version de la réponse correcte ci-dessus pourrait être le code ci-dessous. Cette méthode enverra une demande de téléchargement à une autre application ou service agissant comme une véritable source de vérité pour les informations téléchargées.
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;
}
}
Cela empêche de charger l'intégralité de la demande en mémoire.
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
RestTemplate rest = new RestTemplate(requestFactory);
Pour Java.lang.OutOfMemoryError: Java espace de tas peut être résolu en ajoutant plus de mémoire à la JVM:
-Xmxn Spécifie la taille maximale, en octets, du pool d'allocation de mémoire. Cette valeur doit être un multiple de 1024 supérieur à 2 Mo. Ajoutez la lettre k ou K pour indiquer les kilo-octets, ou m ou M pour indiquer les mégaoctets. La valeur par défaut est choisie lors de l'exécution en fonction de la configuration du système.
Pour les déploiements de serveurs, -Xms et -Xmx sont souvent définis sur la même valeur. Voir l'ergonomie du garbage collector sur http://docs.Oracle.com/javase/7/docs/technotes/guides/vm/gc-ergonomics.html
Exemples:
-Xmx83886080
- Xmx81920k
- Xmx80m
Le problème que vous rencontrez n'est probablement pas strictement lié à la demande que vous essayez d'exécuter (télécharger un fichier volumineux) mais la mémoire allouée au processus n'est pas suffisante.