web-dev-qa-db-fra.com

Téléchargez un fichier volumineux à partir du serveur à l'aide de REST template Java Spring MVC

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






    }
14
arpit joshi

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.

Mise à jour pour le printemps 5

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.
22
bernie

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.

0

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

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.

0
Andrea Girardi