[JAVA] Comment réaliser un téléchargement de fichiers volumineux avec Rest Template of Spring

1.Tout d'abord

Cette fois, je vais vous expliquer comment télécharger un énorme fichier en utilisant RestTemplate de Spring Framework. Le but est d'utiliser «RequestCallback». Pour l'implémentation côté serveur du téléchargement de fichiers volumineux, reportez-vous à "Comment réaliser un téléchargement de fichiers volumineux avec TERASOLUNA 5.x (= Spring MVC)".

(point)

2. Code source

LargeFileRequestCallback.java


package todo.app.largefile;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.web.client.RequestCallback;

//★ Point 1
public class LargeFileRequestCallback implements RequestCallback {

    /**
     * LOGGER
     */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(LargeFileRequestCallback.class);

    /**
     *★ Point 2
     * buffer size 1MB
     */
    private static final int BUFFER_SIZE = 1 * 1024 * 1024;

    /**
     *★ Point 3
     *Informations sur le fichier
     */
    private final FileInfo fileInfo;

    /**
     *★ Point 3
     *constructeur
     * @param fileInfo Informations sur le fichier
     */
    public LargeFileRequestCallback(FileInfo fileInfo) {
        this.fileInfo = fileInfo;
    }

    /**
     *★ Point 4
     * @see org.springframework.web.client.RequestCallback#doWithRequest(org.springframework.http.client.ClientHttpRequest)
     */
    @Override
    public void doWithRequest(ClientHttpRequest request) throws IOException {

        LOGGER.debug("doWithRequest : start");
        //★ Point 5
        // 1. set Content-type
        request.getHeaders().add("Content-type", fileInfo.getContentType());
        request.getHeaders().add("Content-Length",
                Long.toString(fileInfo.getContentLength()));
        request.getHeaders().add("X-SHA1-CheckSum", fileInfo.getCheckSum());
        request.getHeaders().add("X-FILE-NAME", fileInfo.getDownloadFileName());

        //★ Point 6
        // 2. copy contents with buffering
        try (InputStream input = new BufferedInputStream(
                new FileInputStream(fileInfo.getFilePath()));
                OutputStream out = request.getBody();) {
            byte[] buffer = new byte[BUFFER_SIZE];
            long total = 0;
            int len = 0;
            while ((len = input.read(buffer)) != -1) {
                out.write(buffer, 0, len);
                out.flush();
                total = total + len;
                // LOGGER.debug("writed : " + total);
            }
        }

        LOGGER.debug("doWithRequest : end");
    }

}

** ★ Point 1 ** ʻDéfinissez une classeRequestCallback qui implémente l'interface org.springframework.web.client.RequestCallback. Cette classe peut manipuler l'en-tête de la requête lors de l'émission d'une requête HTTP pour RestTemplate` et écrire des données dans la requête BODY. Cette fois, il sera utilisé pour écrire les données du fichier dans la requête BODY. Cette fois, elle a été définie comme une classe distincte afin qu'elle puisse être utilisée par d'autres, mais il est également possible de l'écrire comme (1) opérateur de flèche (expression lambda) et (2) classe interne (classe anonyme) où elle est utilisée.

** ★ Point 2 ** Définit la taille du tampon lors de l'écriture des données de fichier. Cette fois, c'était 1 Mo.

** ★ Point 3 ** Les informations de fichier à télécharger sont liées à la classe RequestCallback en tant qu'argument du constructeur.

** ★ Point 4 ** Tel est le but de cet article. Implémentez le processus d'écriture des données du fichier de téléchargement dans la requête HTTP en remplaçant la méthode doWithRequest. L'objet ClientHttpRequest passé en argument est la requête HTTP émise par RestTemplate. Manipulez cela directement pour définir l'en-tête de la demande ou écrire des données dans le BODY de la demande.

** ★ Point 5 ** Lors de la définition de l'en-tête de la requête, récupérez l'objet HttpHeaders avec la méthode getHeaders et ajoutez-le avec la méthode ʻadd`.

** ★ Point 6 ** Écrire pour demander BODY lors de la mise en mémoire tampon des données du fichier. La requête BODY est accédée en tant que flux de sortie en appelant la méthode getBody de l'objet ClientHttpRequest.

RestTemplateCallbackExtend.java


package todo.app.largefile;

import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;

//★ Point 7
public class RestTemplateCallbackExtend extends RestTemplate {

    public RestTemplateCallbackExtend() {
        super();
    }

    /**
     *★ Point 8
     * Extend the postForEntity method so that callback can be used
     * @param url the URL
     * @param responseType the type of the return value
     * @param requestCallback object that prepares the request
     * @return the converted object
     */
    public <T> ResponseEntity<T> postForEntityWithCallback(String url,
            Class<T> responseType, RequestCallback requestCallback) {
        
        // 1. create ResponseExtractor object
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(
                responseType);
        
        // 2. send request using RequestCallback and default ResponseExtractor
        return super.execute(url, HttpMethod.POST, requestCallback,
                responseExtractor);
    }
}

** ★ Point 7 ** Par défaut, RestTemplate n'a que trois méthodes qui utilisent RequestCallback, et dans ce cas, vous devez également spécifier ResponseExtractor. J'ai implémenté RequestCallback, mais ResponseExtractor veut utiliser la fonctionnalité par défaut, donc je vais étendre un peu RestTemplate.

(référence) Comme il était difficile de créer ResponseExtractor <ResponseEntity <String >> de ResponseEntity <String>, cette fois j'ai étendu RestTemplate. Si vous pouvez facilement créer cet objet ResponseExtractor, vous devez appeler directement la méthode ʻexecute de RestTemplate.

Liste des méthodes qui prennent RequestCallback comme argument dans RestTemplate


public <T> T execute(String url,
                     HttpMethod method,
                     RequestCallback requestCallback,
                     ResponseExtractor<T> responseExtractor,
                     Object... urlVariables)
              throws RestClientException

public <T> T execute(String url,
                     HttpMethod method,
                     RequestCallback requestCallback,
                     ResponseExtractor<T> responseExtractor,
                     Map<String,?> urlVariables)
              throws RestClientException

public <T> T execute(URI url,
                     HttpMethod method,
                     RequestCallback requestCallback,
                     ResponseExtractor<T> responseExtractor)
              throws RestClientException

** ★ Point 8 ** Cette fois, je voulais une méthode postForEntity qui puisse spécifier RequestCallback comme argument dans POST, j'ai donc ajouté la méthode postForEntityWithCallback. Appelez la méthode protected responseEntityExtractorpour utiliser la méthode par défautResponseExtractor, et RequestCallback utilise celle spécifiée par l'argument pour exécuter la méthode ʻexecute.

FileUploadControllerTest.java


package todo.app.largefile;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.HttpClientErrorException;

public class FileUploadControllerTest {

    /**
     * LOGGER
     */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(FileUploadControllerTest.class);

    @Test
    public void test01() {
        //★ Point 9
        // 1. prepared data
        String targetUrl = "http://localhost:8090/todo-rest/upload/chunked";
        FileInfo fileInfo = getFileInfo();

        //★ Point 10
        // 2. create RestTemplate object
        RestTemplateCallbackExtend restTemplate = new RestTemplateCallbackExtend();

        //★ Point 11
        // 3. setting SimpleClientHttpRequestFactory for timeout, streaming
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setBufferRequestBody(false);
        requestFactory.setConnectTimeout(1000 * 120);
        requestFactory.setReadTimeout(1000 * 120);
        requestFactory.setChunkSize(1 * 1024 * 1024);
        restTemplate.setRequestFactory(requestFactory);

        try {
            //★ Point 12
            // 4. send request using RequestCallback
            ResponseEntity<String> responseEntity = restTemplate
                    .postForEntityWithCallback(targetUrl, String.class,
                            new LargeFileRequestCallback(fileInfo));

            //★ Point 13
            // 5. assert
            assertThat(responseEntity.getStatusCode(), is(HttpStatus.CREATED));
            assertThat(responseEntity.getBody(), is("success!"));

        } catch (HttpClientErrorException e) {
            LOGGER.error(e.getMessage()); //Seul "403 Forbidden" est inclus.
            LOGGER.error(e.getResponseBodyAsString());
            throw e;
        }

    }

    /**
     *★ Point 9
     *Obtenir des informations sur les fichiers pour les tests
     * @retourner les informations du fichier
     */
    private FileInfo getFileInfo() {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFilePath("C:/tmp/spring-tool-suite-3.8.2.RELEASE-e4.6.1-win32-x86_64.zip");
        fileInfo.setContentType("application/zip");
        fileInfo.setCheckSum("ff1a62872fb62edb52c1e040fdf9c720e051f9e8");
        fileInfo.setContentLength(418945672);
        fileInfo.setDownloadFileName("STS3.8.2.zip");
        return fileInfo;
    }
}

** ★ Point 9 ** Préparez les données à envoyer. Cette fois, j'ai décidé de télécharger le fichier STS Zip (un peu moins de 400 Mo) que j'avais. La somme de contrôle (valeur de hachage) a été calculée dans "Comment obtenir la valeur de hachage (somme de contrôle) d'un fichier énorme en Python".

** ★ Point 10 ** Créez une instance de la classe RestTemplateCallbackExtend définie au point 7 au lieu de RestTemplate.

** ★ Point 11 ** Lors du téléchargement d'un fichier volumineux, il est nécessaire de prendre en compte les effets du délai d'expiration, de la taille des données de la mémoire tampon, etc. ʻConfigurez avec la classe ʻorg.springframework.http.client.SimpleClientHttpRequestFactoryet activez-la avec la méthodesetRequestFactory de RestTemplate`.

Comme expliqué ci-dessous, cette fois, nous enverrons une requête avec une grande taille de données, définissez donc false

Default is true. When sending large amounts of data via POST or PUT, it is recommended to change this property to false, so as not to run out of memory.

** ★ Point 12 ** ★ Appelez la méthode postForEntityWithCallback implémentée au point 8. À ce moment, spécifiez l'objet LargeFileRequestCallback comme argument. Maintenant, les données du fichier seront écrites dans le BODY de requête en utilisant le RequestCallback implémenté cette fois.

** ★ Point 13 ** Après avoir exécuté la requête HTTP, ce sera la même chose qu'un RestTemplate normal. Utilisez la méthode getStatusCode pour accéder au code d'état de la réponse HTTP et la méthode getBody pour accéder à la réponse HTTP BODY. Si HttpClientErrorException se produit, la méthode getMessage obtient un résumé de l'exception. Si vous avez besoin de plus de détails, vérifiez la réponse BODY sur exception avec la méthode getResponseBodyAsString.

3. Enfin

Cette fois, j'ai expliqué comment télécharger un fichier énorme en utilisant RestTemplate et RequestCallback. Comme je l'ai découvert en essayant cette fois, c'était un peu gênant qu'il n'y ait pas de méthode qui puisse spécifier RequestCallback indépendamment dans RestTemplate (il y a un ensemble avec ResponseExtractor).

Recommended Posts

Comment réaliser un téléchargement de fichiers volumineux avec Rest Template of Spring
Comment réaliser un téléchargement de fichiers volumineux avec TERASOLUNA 5.x (= Spring MVC)
Comment réaliser le téléchargement de fichiers avec Feign
J'ai essayé d'implémenter le téléchargement de fichiers avec Spring MVC
Téléchargement de fichiers avec Spring Boot
Comment tester l'écran de téléchargement de fichiers avec Spring + Selenium
Comment créer un formulaire Excel à l'aide d'un fichier modèle avec Spring MVC
Comment démarrer par environnement avec Spring Boot de Maven
Découpez SQL en fichier de propriété avec jdbcTemplate of spring boot
Découvrons comment recevoir avec Request Body avec l'API REST de Spring Boot
Comment charger un fichier de téléchargement Spring et afficher son contenu
Comment réaliser le téléchargement de fichiers avec Feign
Comment diviser un fichier de message Spring Boot
Comment lire le corps de la requête plusieurs fois avec Spring Boot + Spring Security
Je veux afficher des images avec REST Controller de Java et Spring!
Comment accéder directement à Socket avec la fonction TCP de Spring Integration
Comment utiliser MyBatis2 (iBatis) avec Spring Boot 1.4 (Spring 4)
Comment utiliser h2db intégré avec Spring Boot
Comment se lier avec un fichier de propriétés dans Spring Boot
[Spring Boot] Comment se référer au fichier de propriétés
Comment définir des variables d'environnement dans le fichier de propriétés de l'application Spring Boot
Si vous utilisez SQLite avec VSCode, utilisez l'extension (comment voir le fichier binaire de sqlite3)
[Java] Comment omettre l'injection de constructeur de ressort avec Lombok
Comment traiter Aucun modèle de demande interactive
Comment utiliser CommandLineRunner dans Spring Batch of Spring Boot
Téléchargement de fichiers avec Spring Boot (ne pas utiliser de fichier en plusieurs parties)
Comment inverser la compilation du fichier apk en code source Java avec MAC
Comment utiliser le modèle entraîné Tensorflow 2.0 avec Kotlin / Java
[Facile] Comment formater automatiquement les fichiers Ruby erb avec vsCode
Comment déplacer une autre classe avec une action de bouton d'une autre classe.
Comment modifier le contenu du fichier jar sans décompresser
Comment gérer différentes versions de rbenv et Ruby
Comment numéroter (nombre) avec html.erb
Comment mettre à jour avec activerecord-import
Comment gérer l'événement où Committee :: InvalidRequest se produit en comité pendant le test de téléchargement de fichier Rspec
Comment créer un fichier jar sans dépendances dans Maven
J'ai essayé de me connecter à MySQL en utilisant le modèle JDBC avec Spring MVC
[Avec un exemple de code] Les bases de Spring JDBC apprises avec l'application Blog
Comment ouvrir un fichier de script à partir d'Ubuntu avec du code VS
[chown] Comment changer le propriétaire d'un fichier ou d'un répertoire
Écraser le téléchargement du fichier avec le même nom avec BOX SDK (java)
[Débutant] Essayez d'écrire l'API REST pour l'application Todo avec Spring Boot
Téléchargement automatique de fichiers avec l'ancienne gemme Ruby Que faire avec Watir
Comment utiliser Lombok au printemps
Spring avec Kotorin - 4 Conception d'API REST
Comment démarrer avec Slim
Remarques sur l'utilisation de Spring Data JDBC
Comment entourer n'importe quel caractère avec "~"
[Comment installer Spring Data Jpa]
Comment configurer Spring Boot + PostgreSQL
Comment convertir un fichier erb en haml
Comment utiliser setDefaultCloseOperation () de JFrame
Implémenter le téléchargement de fichiers avec Spring MVC
Comment utiliser mssql-tools avec Alpine
Comment utiliser ModelMapper (Spring boot)
Comment démarrer Camunda avec Docker
Comment appliquer immédiatement les modifications de Thymeleaf au navigateur avec #Spring Boot + maven
Comment décorer CSS sur les boutons radio de rails6 form_with (helper)