Dieses Mal werde ich erklären, wie eine Datei mit einer großen Datengröße mit TERASOLUNA 5.x (= Spring MVC)
hochgeladen wird. Wenn die Datengröße klein ist, müssen Sie sich keine Sorgen machen, aber wenn sie groß ist, müssen Sie vorsichtig mit den Serverressourcen sein.
(Punkt)
Informationen zum Herunterladen finden Sie unter "So realisieren Sie den Download großer Dateien mit TERASOLUNA 5.x (= SpringMVC)".
(Nicht guter Code 1) Wenn Sie eine HTTP-Anforderung als Argument verwenden
@RequestMapping(path = "chunked", method = RequestMethod.POST)
public ResponseEntity<String> streamUpload(HttpServletRequest httpRequest) {
// You should be getted 5 parameters from HttpServletRequest and validated it
// omitted
}
Dies ist die altmodische Methode, die mit "Action" von "Servlet" und "Struts" vertraut ist. Es empfängt "HttpServletRequest" als Argument der Handler-Methode und liest die hochgeladenen Dateidaten von BODY.
Der Nachteil dieser Methode ist, dass sie rohes HttpServletRequest
verarbeitet und nicht testbar ist.
(Nicht guter Code 2) Wenn Parameter einzeln definiert werden
@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
}
Im Vergleich zum obigen Code werden jetzt die Funktionen von spring-mvc
verwendet, z. B. @ RequestHeader
, aber es fühlt sich kompliziert an, da es viele Parameter gibt.
Außerdem können Sie mit dieser Methode keine Eingabeprüfung mit "Bean Validation" und "BindingResult" verwenden.
**(Hinweis) Wenn Sie "InputStream" als Argument der Handler-Methode angeben, können Sie die BODY-Daten der HTTP-Anforderung als Eingabestream empfangen. Mit anderen Worten, es werden die hochgeladenen Binärdateidaten sein. ** ** **
** Ich möchte ModelAttributeMethodProcessor
verwenden, um die Klasse zu erhalten, in der die erforderlichen Daten als Argument der Handler-Methode gespeichert sind.
Es gibt eine "HandlerMethodArgumentResolver" -Schnittstelle als ähnliche Funktion, die jedoch "ModelAttributeMethodProcessor" verwendet, da eine Eingabeprüfung nicht möglich ist. ** ** **
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
}
Wie bei der Eingabeprüfung der Formularklasse "(Form Backing Bean)" wird dem Feld eine Anmerkung für die Eingabeprüfung der "Bean Validation" hinzugefügt.
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;
//★ Punkt 1
public class StreamFileModelAttributeMethodProcessor extends
ModelAttributeMethodProcessor {
//★ Punkt 2
public StreamFileModelAttributeMethodProcessor() {
super(false);
}
//★ Punkt 3
@Override
public boolean supportsParameter(MethodParameter parameter) {
return StreamFile.class.equals(parameter.getParameterType());
}
//★ Punkt 4
@Override
protected void bindRequestParameters(WebDataBinder binder,
NativeWebRequest request) {
//★ Punkt 5
HttpServletRequest httpRequest = request
.getNativeRequest(HttpServletRequest.class);
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(
httpRequest);
//★ Punkt 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"));
//★ Punkt 7
try {
pvs.add("inputStream", httpRequest.getInputStream());
} catch (IOException e) {
pvs.add("inputStream", null);
}
//★ Punkt 8
binder.bind(pvs);
}
}
** ★ Punkt 1 **
Definiert eine Klasse, die "org.springframework.web.method.annotation.ModelAttributeMethodProcessor" erweitert.
ModelAttributeMethodProcessor
ist eine Klasse, die die Schnittstelle org.springframework.web.method.support.HandlerMethodArgumentResolver
implementiert.
** ★ Punkt 2 ** [ModelAttributeMethodProcessor Constructor](https://docs.spring.io/autorepo/docs/spring-framework/4.3.5.RELEASE/javadoc-api/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.html#ModArel Rufen Sie boolean-) auf. Dieses Mal erstellen wir ein Objekt mit dem Standardwert "false".
** ★ Punkt 3 **
Die supportParameter
-Methode von org.springframework.web.method.support.HandlerMethodArgumentResolver
.
Prozess, wenn der Datentyp des Arguments der Handler-Methode "StreamFile.class" ist.
** ★ Punkt 4 ** Dies ist der Punkt dieser Methode. Implementieren Sie den Prozess des Bindens (Festlegens) der aus der HTTP-Anforderung erhaltenen Daten an das Argumentobjekt (diesmal "StreamFile") der Handler-Methode in der Methode "bindRequestParameters". Das heißt, es ruft den BODY-Eingabestream der HTTP-Anforderung und den Wert des HTTP-Anforderungsheaders ab und bindet ihn an "WebDataBinder".
** ★ Punkt 5 ** org.springframework.beans.PropertyValues Implementierungsklasse [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) Erstellen Sie ein Objekt der Klasse.
** ★ Punkt 6 ** Legen Sie den vom HTTP-Anforderungsheader erhaltenen Wert mit der Methode "add" von "ServletRequestParameterPropertyValues" fest.
** ★ Punkt 7 ** ★ Stellen Sie den BODY-Eingabestream der HTTP-Anforderung auf die gleiche Weise wie in Punkt 6 ein. Ich habe beschlossen, "null" zu setzen, wenn "IOException" auftritt.
** ★ Punkt 8 **
Binden (setzen) Sie die an Punkt 5 generierten "PropertyValues" mit der "bind" -Methode von "WebDataBinder".
Damit wird der Wert der ★ Punkte 6 und 7 im Argumentobjekt der Handler-Methode gesetzt (diesmal StreamFile
).
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);
/**
*★ Punkt 9
* define max upload file size
*/
private static final long MAX_FILE_SIZE = 500 * 1024 * 1024;
/**
*★ Punkt 9
* buffer size 1MB
*/
private static final int BUFFER_SIZE = 1 * 1024 * 1024;
//★ Punkt 10
@RequestMapping(path = "chunked", method = RequestMethod.POST)
public ResponseEntity<String> streamUpload(
@Validated StreamFile streamFile,
BindingResult result) {
//★ Punkt 11
if (result.hasErrors()) {
LOGGER.debug("validated error = {}", result.getAllErrors());
return new ResponseEntity<String>("validated error!",
HttpStatus.BAD_REQUEST);
}
//★ Punkt 12
if (MAX_FILE_SIZE < streamFile.getContentLength()) {
return fileSizeOverEntity();
}
//★ Punkt 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);
//★ Punkt 12
if (MAX_FILE_SIZE < total) {
return fileSizeOverEntity();
}
}
}
LOGGER.debug(uploadFile.getAbsolutePath());
//★ Punkt 14
return new ResponseEntity<String>("success!", HttpStatus.CREATED);
} catch (IOException e) {
LOGGER.error(e.getMessage());
//★ Punkt 15
return new ResponseEntity<String>("error!",
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
*★ Punkt 12
* @Rückgabe von ResponseEntity bei überschüssiger Dateigröße
*/
private ResponseEntity<String> fileSizeOverEntity() {
return new ResponseEntity<String>(
"file size is too large. " + MAX_FILE_SIZE + "(byte) or less",
HttpStatus.BAD_REQUEST);
}
/**
*Zeigen Sie den Bildschirm für das Upload-Formular an
* @Bildschirm zum Hochladen des Formulars zurückgeben
*/
@RequestMapping(path = "form", method = RequestMethod.GET)
public String form() {
return "upload/form";
}
}
** ★ Punkt 9 ** Definiert die Obergrenze der Datengröße der hochgeladenen Datei und die Puffergröße, die beim Speichern in der Datei verwendet wird.
** ★ Punkt 10 ** Geben Sie "@ Validated" und "BindingResult" an, damit die Eingabeprüfung als Argument der Handler-Methode aktiviert wird. ★ Durch Verarbeiten von "StreamFileModelAttributeMethodProcessor", definiert in Punkt 1, ist es möglich, "StreamFile", dessen Eingabe überprüft wurde, als Argument zu verwenden.
** ★ Punkt 11 ** Verwenden Sie wie bei der normalen Eingabeprüfung die Methode "hasErrors" von "BindingResult", um nach Fehlern bei der Eingabeprüfung zu suchen. Dieses Mal geben wir im Falle eines Eingabefehlers eine Antwort mit "HttpStatus.BAD_REQUEST" zurück, dh dem HTTP-Antwortstatuscode 400.
** ★ Punkt 12 ** Überprüfen Sie, ob die Datengröße der hochgeladenen Datei die in ★ Punkt 9 definierte Obergrenze überschreitet. Überprüfen Sie die Datengröße an zwei Stellen: (1) den Wert des Headers "Content-Length" und (2) die tatsächlich gelesene Datengröße. Wenn das Limit überschritten wird, geben wir eine Antwort mit "HttpStatus.BAD_REQUEST" zurück, dh dem HTTP-Antwortstatuscode 400. ,
** ★ Punkt 13 ** Lesen Sie Daten aus dem Eingabestream von BODY der HTTP-Anforderung mit der in ★ Punkt 9 definierten Puffergröße und speichern Sie die hochgeladene Datei als Datei. Dieses Mal habe ich beschlossen, es in einem temporären Verzeichnis zu speichern. Bitte ändern Sie hier entsprechend Ihren Geschäftsanforderungen.
** ★ Punkt 14 ** Da die hochgeladene Datei als Datei auf dem Server gespeichert werden kann, geben wir eine Antwort mit "HttpStatus.CREATED" zurück, dh den HTTP-Antwortstatuscode 201.
(Hinweis) Dieses Mal werden die Metadaten der hochgeladenen Datei (Dateiname, Inhaltstyp, Datengröße, Prüfsumme (Hashwert) usw.) nicht gespeichert. In einem tatsächlichen System sind beim Zugriff auf eine hochgeladene Datei (Herunterladen, Öffnen mit einer Anwendung usw.) Metadaten erforderlich, sodass die Metadaten in einer Datenbank oder dergleichen gespeichert werden.
** ★ Punkt 15 ** Wenn während der Verarbeitung eine "IOException" auftritt, geben wir diesmal eine Antwort mit "HttpStatus.INTERNAL_SERVER_ERROR" zurück, dh dem HTTP-Antwortstatuscode 500.
Spring Bean-Definitionsdatei (spring-mvc.xml)
<!-- omitted -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<!-- omitted -->
<bean class="todo.app.largefile.StreamFileModelAttributeMethodProcessor"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
Um den diesmal implementierten "StreamFileModelAttributeMethodProcessor" zu aktivieren, fügen Sie die Bean mit ★ Punkt 1 zu "<mvc: argument-resolvers>" hinzu. Definieren Sie TERASOLUNA 5.x in der Datei "spring-mvc.xml".
Anwendungsservereinstellungen (z. B. Tomcat-Server.xml)
<Connector connectionTimeout="200000" port="8090"
protocol="HTTP/1.1" redirectPort="8443"
maxPostSize="-1" maxParameterCount="-1"
maxSavePostSize="-1" maxSwallowSize="-1"
socket.appReadBufSize="40960"/>
Wenn Sie eine große Datei hochladen, sollten Sie auch die Einstellungen des Anwendungsservers überprüfen.
Auszeit
Je größer die Datengröße ist, desto länger dauert das Senden der Daten. Es muss so eingestellt werden, dass keine Zeitüberschreitung auftritt, bevor die Datenübertragung abgeschlossen ist.
Für Tomcat8.0 mit connectionTimeout
festlegen. Das Gerät ist Millisekunden und der Standardwert ist 20000 (20 Sekunden).
Datengröße anfordern
Im Allgemeinen hat der Anwendungsserver eine Obergrenze für die Datengröße der Anforderung. Es muss entsprechend der Datengröße eingestellt werden, die das Hochladen ermöglicht. Wenn Sie diese Einstellung nicht vornehmen, wird die Obergrenze abgefangen und der Anwendungsserver trennt die Anforderung.
Auf einigen Servern können Sie eine andere Datengröße für HTTP-Anforderungen für Datei-Uploads festlegen ("Multipart / Formulardaten"). Diese Methode ist jedoch nicht "mehrteilig / Formulardaten", daher wird sie als normale HTTP-Anforderung beurteilt.
Für Tomcat 8.0 sind "maxPostSize", "maxSavePostSize", "maxSwallowSize" usw. für diese Einstellung relevant. Dieses Mal habe ich "-1" gewählt, was keine Begrenzung bedeutet.
Datenpuffergröße anfordern
Es stellte sich heraus, dass keine Daten aus dem Eingabestream der Anforderung mit der angegebenen Puffergröße abgerufen werden konnten (★ BUFFER_SIZE
bei Punkt 9).
Für Tomcat8.0 mit socket.appReadBufSize
festlegen. Die Einheit ist Bytes und der Fehler ist 8192. ★ Muss mit dem Wert von BUFFER_SIZE
an Punkt 9 abgestimmt werden.
Informationen zu Tomcat8.0 finden Sie unter https://tomcat.apache.org/tomcat-8.0-doc/config/http.html.
Dieses Mal habe ich erklärt, wie man eine Datei mit einer großen Datengröße mit TERASOLUNA 5.x (= Spring MVC)
hochlädt.
Der Punkt ist die Notwendigkeit, Argumente mit "ModelAttributeMethodProcessor" festzulegen und den Anwendungsserver aufgrund der großen Datenmenge zu optimieren.
** Laden Sie in dem von "Hochladen von Dateien mit Ajax ohne Verwendung von Multipart" implementierten Test eine Datei mit weniger als 400 MB in 6 Sekunden hoch. Ich konnte es tun. ** ** **
Recommended Posts