Cette fois, je vais vous expliquer comment télécharger un fichier avec une grande taille de données avec TERASOLUNA 5.x (= Spring MVC)
. Si la taille des données est petite, vous n'avez pas à vous en soucier, mais si elle est grande, vous devez faire attention aux ressources du serveur.
(point)
type de contenu
selon le type de fichier au lieu de multipart / form-data
multipart / form-data
(par exemple, base64).ModelAttributeMethodProcessor
pour masquer HttpServletRequest
du contrôleurPour le téléchargement, veuillez vous référer à "Comment réaliser un téléchargement de fichier énorme avec TERASOLUNA 5.x (= SpringMVC)".
(Code incorrect 1) Lors de la prise d'une requête HTTP comme argument
@RequestMapping(path = "chunked", method = RequestMethod.POST)
public ResponseEntity<String> streamUpload(HttpServletRequest httpRequest) {
// You should be getted 5 parameters from HttpServletRequest and validated it
// omitted
}
Ceci est la méthode à l'ancienne familière avec ʻAction de
Servletet
Struts. Il reçoit
HttpServletRequestcomme argument de la méthode du gestionnaire et lit les données du fichier téléchargé depuis BODY. L'inconvénient de cette méthode est qu'elle gère le
HttpServletRequest` brut et n'est pas testable.
(Code incorrect 2) Lorsque les paramètres sont définis individuellement
@RequestMapping(path = "chunked", method = RequestMethod.POST)
public ResponseEntity<String> streamUpload(InputStream input,
@RequestHeader(name = "Content-Type", required = false) String contentType,
@RequestHeader(name = "Content-Length", required = false) Long contentLength,
@RequestHeader(name = "X-SHA1-CheckSum", required = false) String checkSum,
@RequestHeader(name = "X-FILE-NAME", required = false) String fileName) {
// You should be validated 5 parameters
// omitted
}
Par rapport au code ci-dessus, il utilise maintenant les fonctions de spring-mvc
, comme utiliser @ RequestHeader
, mais cela semble compliqué car il y a de nombreux paramètres.
En outre, cette méthode ne vous permet pas d'utiliser la vérification d'entrée à l'aide de Bean Validation
et BindingResult
.
**(Mise en garde) Si vous spécifiez ʻInputStream` comme argument de la méthode du gestionnaire, vous pouvez recevoir les données BODY de la requête HTTP en tant que flux d'entrée. En d'autres termes, ce seront les données du fichier binaire téléchargé. ** **
** Je voudrais utiliser ModelAttributeMethodProcessor
pour recevoir la classe qui stocke les données requises comme argument de la méthode du gestionnaire.
Il existe une interface HandlerMethodArgumentResolver
comme fonction similaire, mais cela utiliseModelAttributeMethodProcessor
car la vérification d'entrée n'est pas possible. ** **
StreamFile.java
public class StreamFile implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull
private InputStream inputStream;
@Size(max = 200)
@NotEmpty
private String contentType;
@Min(1)
private long contentLength;
@Size(max = 40)
@NotEmpty
private String checkSum;
@Size(max = 200)
@NotEmpty
private String fileName;
// omitted setter, getter
}
Comme pour la vérification d'entrée de la classe Form (bean de support de formulaire)
, une annotation pour la vérification d'entrée de Bean Validation
est ajoutée au champ.
StreamFileModelAttributeMethodProcessor.java
package todo.app.largefile;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
//★ Point 1
public class StreamFileModelAttributeMethodProcessor extends
ModelAttributeMethodProcessor {
//★ Point 2
public StreamFileModelAttributeMethodProcessor() {
super(false);
}
//★ Point 3
@Override
public boolean supportsParameter(MethodParameter parameter) {
return StreamFile.class.equals(parameter.getParameterType());
}
//★ Point 4
@Override
protected void bindRequestParameters(WebDataBinder binder,
NativeWebRequest request) {
//★ Point 5
HttpServletRequest httpRequest = request
.getNativeRequest(HttpServletRequest.class);
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(
httpRequest);
//★ Point 6
pvs.add("contentType", httpRequest.getContentType());
pvs.add("contentLength", httpRequest.getContentLengthLong());
pvs.add("checkSum", httpRequest.getHeader("X-SHA1-CheckSum"));
pvs.add("fileName", httpRequest.getHeader("X-FILE-NAME"));
//★ Point 7
try {
pvs.add("inputStream", httpRequest.getInputStream());
} catch (IOException e) {
pvs.add("inputStream", null);
}
//★ Point 8
binder.bind(pvs);
}
}
** ★ Point 1 **
Définit une classe qui étend ʻorg.springframework.web.method.annotation.ModelAttributeMethodProcessor.
ModelAttributeMethodProcessor est une classe qui implémente l'interface ʻorg.springframework.web.method.support.HandlerMethodArgumentResolver
.
** ★ Point 2 ** [Constructeur de ModelAttributeMethodProcessor](https://docs.spring.io/autorepo/docs/spring-framework/4.3.5.RELEASE/javadoc-api/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.html########################################################################################### Appelez boolean-). Cette fois, nous allons créer un objet avec la valeur par défaut «false».
** ★ Point 3 **
Méthode supportsParameter
de ʻOrg.springframework.web.method.support.HandlerMethodArgumentResolver. Traiter lorsque le type de données de l'argument de la méthode du gestionnaire est
StreamFile.class`.
** ★ Point 4 **
C'est le but de cette méthode. Implémentez le processus de liaison (définition) des données obtenues à partir de la requête HTTP à l'objet argument (cette fois «StreamFile») de la méthode du gestionnaire dans la méthode «bindRequestParameters».
Autrement dit, il obtient le flux d'entrée BODY de la requête HTTP et la valeur de l'en-tête de la requête HTTP et le lie (définit) à WebDataBinder
.
** ★ Point 5 ** org.springframework.beans.PropertyValues Classe d'implémentation [org.springframework.web.bind.ServletRequestParameterPropertyValues](https://docs.spring.io/autorepo/docs/spring-framework/4.3.5.RELEASE/javadoc-api/org/springframework/web/ bind / ServletRequestParameterPropertyValues.html) Crée un objet de classe.
** ★ Point 6 **
Définissez la valeur obtenue à partir de l'en-tête de la requête HTTP avec la méthode ʻadd de
ServletRequestParameterPropertyValues`.
StreamFile
cette fois)** ★ Point 7 **
★ Définissez le flux d'entrée BODY de la requête HTTP de la même manière que le point 6. J'ai décidé de mettre null
lorsque ʻIOException` se produit.
** ★ Point 8 **
Liez (définissez) les PropertyValues
générées au point 5 avec la méthode bind
de WebDataBinder
.
Avec cela, la valeur de ★ points 6 et 7 sera définie dans l'objet argument de la méthode du gestionnaire (StreamFile
cette fois).
FileUploadController.java
package todo.app.largefile;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("upload")
public class FileUploadController {
/**
* LOGGER
*/
private static final Logger LOGGER = LoggerFactory
.getLogger(FileUploadController.class);
/**
*★ Point 9
* define max upload file size
*/
private static final long MAX_FILE_SIZE = 500 * 1024 * 1024;
/**
*★ Point 9
* buffer size 1MB
*/
private static final int BUFFER_SIZE = 1 * 1024 * 1024;
//★ Point 10
@RequestMapping(path = "chunked", method = RequestMethod.POST)
public ResponseEntity<String> streamUpload(
@Validated StreamFile streamFile,
BindingResult result) {
//★ Point 11
if (result.hasErrors()) {
LOGGER.debug("validated error = {}", result.getAllErrors());
return new ResponseEntity<String>("validated error!",
HttpStatus.BAD_REQUEST);
}
//★ Point 12
if (MAX_FILE_SIZE < streamFile.getContentLength()) {
return fileSizeOverEntity();
}
//★ Point 13
try {
File uploadFile = File.createTempFile("upload", null);
InputStream input = streamFile.getInputStream();
try (OutputStream output = new BufferedOutputStream(
new FileOutputStream(uploadFile))) {
byte[] buffer = new byte[BUFFER_SIZE];
long total = 0;
int len = 0;
while ((len = input.read(buffer)) != -1) {
output.write(buffer, 0, len);
output.flush();
total = total + len;
LOGGER.debug("writed : " + total);
//★ Point 12
if (MAX_FILE_SIZE < total) {
return fileSizeOverEntity();
}
}
}
LOGGER.debug(uploadFile.getAbsolutePath());
//★ Point 14
return new ResponseEntity<String>("success!", HttpStatus.CREATED);
} catch (IOException e) {
LOGGER.error(e.getMessage());
//★ Point 15
return new ResponseEntity<String>("error!",
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
*★ Point 12
* @retourne ResponseEntity sur une erreur d'excès de taille de fichier
*/
private ResponseEntity<String> fileSizeOverEntity() {
return new ResponseEntity<String>(
"file size is too large. " + MAX_FILE_SIZE + "(byte) or less",
HttpStatus.BAD_REQUEST);
}
/**
*Afficher l'écran du formulaire de téléchargement
* @retour écran du formulaire de téléchargement
*/
@RequestMapping(path = "form", method = RequestMethod.GET)
public String form() {
return "upload/form";
}
}
** ★ Point 9 ** Définit la limite supérieure de la taille des données du fichier téléchargé et la taille de la mémoire tampon utilisée lors de l'enregistrement dans le fichier.
** ★ Point 10 **
Spécifiez @ Validated
et BindingResult
pour que la vérification d'entrée soit activée comme argument de la méthode de gestionnaire.
★ En traitant StreamFileModelAttributeMethodProcessor
défini au point 1, il est possible de prendre StreamFile
qui a été vérifié comme argument.
** ★ Point 11 **
Comme pour la vérification d'entrée normale, utilisez la méthode hasErrors
de BindingResult
pour vérifier les erreurs de vérification d'entrée.
Cette fois, en cas d'erreur d'entrée, nous retournerons une réponse avec HttpStatus.BAD_REQUEST
, c'est-à-dire le code d'état de réponse HTTP 400.
** ★ Point 12 **
Vérifiez si la taille des données du fichier téléchargé dépasse la limite supérieure définie au ★ Point 9.
Vérifiez la taille des données à deux endroits: (1) la valeur de l'en-tête Content-Length
et (2) la taille des données réellement lues.
Si la limite est dépassée, nous retournerons une réponse avec HttpStatus.BAD_REQUEST
, c'est-à-dire le code d'état de la réponse HTTP 400. ,
** ★ Point 13 ** Lisez les données du flux d'entrée de BODY de la requête HTTP avec la taille de tampon définie au ★ point 9, et enregistrez le fichier téléchargé en tant que fichier. Cette fois, j'ai décidé de l'enregistrer dans un répertoire temporaire. Veuillez modifier ici en fonction des besoins de votre entreprise.
** ★ Point 14 **
Étant donné que le fichier téléchargé peut être enregistré en tant que fichier sur le serveur, nous retournerons une réponse avec HttpStatus.CREATED
, c'est-à-dire le code d'état de réponse HTTP 201.
(Mise en garde) Cette fois, les métadonnées du fichier téléchargé (nom de fichier, type de contenu, taille des données, somme de contrôle (valeur de hachage), etc.) ne sont pas enregistrées. Dans un système réel, lors de l'accès à un fichier téléchargé (téléchargement, ouverture avec une application, etc.), des métadonnées sont nécessaires, de sorte que les métadonnées sont enregistrées dans une base de données ou autre.
** ★ Point 15 **
Si ʻIOException se produit pendant le traitement, nous retournerons cette fois une réponse avec
HttpStatus.INTERNAL_SERVER_ERROR`, c'est-à-dire le code d'état de réponse HTTP 500.
Fichier de définition Spring Bean (spring-mvc.xml)
<!-- omitted -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<!-- omitted -->
<bean class="todo.app.largefile.StreamFileModelAttributeMethodProcessor"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
Afin d'activer le StreamFileModelAttributeMethodProcessor
implémenté cette fois, ajoutez le Bean de ★ point 1 à <mvc: argument-resolvers>
.
Pour TERASOLUNA 5.x, définissez-le dans le fichier spring-mvc.xml
.
Paramètres du serveur d'applications (par exemple, serveur Tomcat.xml)
<Connector connectionTimeout="200000" port="8090"
protocol="HTTP/1.1" redirectPort="8443"
maxPostSize="-1" maxParameterCount="-1"
maxSavePostSize="-1" maxSwallowSize="-1"
socket.appReadBufSize="40960"/>
Si vous téléchargez un fichier volumineux, vous devez également vérifier les paramètres du serveur d'applications.
temps libre
Plus la taille des données est grande, plus il faudra de temps pour envoyer les données. Il doit être réglé de manière à ne pas expirer avant la fin de la transmission des données.
Pour Tomcat8.0, définissez avec connectionTimeout
. L'unité est la milliseconde et la valeur par défaut est 20000 (20 secondes).
Demander la taille des données
En règle générale, le serveur d'applications a une limite supérieure sur la taille des données de la demande. Il doit être défini en fonction de la taille des données qui permet le téléchargement. Si vous n'effectuez pas ce paramètre, la limite supérieure sera interceptée et le serveur d'applications déconnectera la demande.
Certains serveurs vous permettent de définir une taille de données différente pour les requêtes HTTP pour les téléchargements de fichiers (multipart / form-data
). Cependant, cette méthode n'est pas «multipart / form-data», elle est donc considérée comme une requête HTTP normale.
Pour Tomcat 8.0, maxPostSize
, maxSavePostSize
, maxSwallowSize
, etc. sont pertinents pour ce paramètre. Cette fois, j'ai choisi «-1», ce qui signifie aucune limite.
Demander la taille du tampon de lecture des données
Il s'est avéré que les données ne pouvaient pas être obtenues à partir du flux d'entrée de la requête avec la taille de tampon spécifiée (★ BUFFER_SIZE
au point 9).
Pour Tomcat8.0, définissez avec socket.appReadBufSize
. L'unité est d'octets et le défaut est 8192. ★ Doit être réglé avec la valeur de BUFFER_SIZE
au point 9.
Pour Tomcat8.0, voir https://tomcat.apache.org/tomcat-8.0-doc/config/http.html.
Cette fois, j'ai expliqué comment télécharger un fichier avec une grande taille de données avec TERASOLUNA 5.x (= Spring MVC)
.
Le point est la nécessité de définir des arguments à l'aide de ModelAttributeMethodProcessor
et d'ajuster le serveur d'applications en raison de la grande taille des données.
** Dans le test implémenté par le client "Comment télécharger des fichiers avec ajax sans utiliser multipart", un fichier de moins de 400 Mo est téléchargé en 6 secondes. J'ai pu le faire. ** **
Recommended Posts