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)
RequestCallback
et écrivez les données de téléchargement dans le BODY de la requête tout en le tamponnant.RestTemplate
et ajoutez une méthode qui vous permet de spécifier RequestCallback
en utilisant le ResponseExtractor par défautRequestFactory
pour configurer le téléchargement de fichiers volumineux tels que le délai d'expiration et la taille des données du tampon.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éfaut
ResponseExtractor, 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éthode
setRequestFactory de
RestTemplate`.
setConnectTimeout
: Définit le délai d'expiration de la connexion (l'unité est la milliseconde) de la connexion URLsetReadTimeout
: Définit le délai de lecture (l'unité est la milliseconde) de la connexion URLsetChunkSize
: Définit la taille du bloc (l'unité est en octets) lors de l'écriture de données dans la requête HTTP BODYsetBufferRequestBody
: Définit si la fabrique de requêtes tamponne en interne ( true: default
ou false
)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
.
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