[JAVA] So realisieren Sie mit TERASOLUNA 5.x (= Spring MVC) das Hochladen großer Dateien

1. Zuallererst

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)".

2. Quellcode

2.1 Schlechter Code

(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. ** ** **

2.2 Code zur Einführung

** 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.

Informationen zu Tomcat8.0 finden Sie unter https://tomcat.apache.org/tomcat-8.0-doc/config/http.html.

3. Schließlich

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

So realisieren Sie mit TERASOLUNA 5.x (= Spring MVC) das Hochladen großer Dateien
So erzielen Sie mit Rest Template of Spring einen großen Datei-Upload
Ich habe versucht, das Hochladen von Dateien mit Spring MVC zu implementieren
So erreichen Sie das Hochladen von Dateien mit Feign
Datei-Upload mit Spring Boot
So testen Sie den Bildschirm zum Hochladen von Dateien mit Spring + Selenium
So erstellen Sie ein Excel-Formular mithilfe einer Vorlagendatei mit Spring MVC
Implementieren Sie den Dateidownload mit Spring MVC
So laden Sie eine Spring-Upload-Datei und zeigen ihren Inhalt an
So erreichen Sie den Dateidownload mit Feign
[Spring MVC] Übergeben von Pfadvariablen
So teilen Sie eine Spring Boot-Nachrichtendatei
Wie man jeden Fall mit Mockito 1x verspottet
Verwendung von MyBatis2 (iBatis) mit Spring Boot 1.4 (Spring 4)
Verwendung des eingebauten h2db mit Federstiefel
So binden Sie mit einer Eigenschaftendatei in Spring Boot
[Spring Boot] So verweisen Sie auf die Eigenschaftendatei
[Java] So lassen Sie die Federkonstruktorinjektion mit Lombok weg
So entfernen Sie Kacheln aus einem leeren Projekt in TERASOLUNA 5.x.
Booten nach Umgebung mit Spring Boot of Maven
Datei-Upload mit Spring Boot (keine mehrteilige Datei verwenden)
So ändern Sie ein leeres Projekt von TERASOLUNA 5.x, um PostgreSQL zu unterstützen
So fordern Sie mit jMeter eine CSV-Datei als JSON an
So kehren Sie die Kompilierung der Apk-Datei in Java-Quellcode mit MAC um
Schneiden Sie SQL in die Eigenschaftendatei mit jdbcTemplate von Spring Boot aus
[Einfach] So formatieren Sie Ruby erb-Dateien automatisch mit vsCode
So erhalten Sie eine leere Anfrage mit Spring Web MVC @RequestBody
Aktivieren Sie WebJars für leere Projekte in TERASOLUNA 5.x (= Spring MVC).
Java-Konfiguration mit Spring MVC
Wie man mit html.erb nummeriert (nummeriert)
So aktualisieren Sie mit activerecord-import
Umgang mit dem Ereignis, dass Committee :: InvalidRequest während des Rspec-Datei-Upload-Tests im Committee auftritt
Einführung in Spring Boot x Offene API ~ Offene API, erstellt mit Generationslückenmuster ~
So erstellen Sie eine JAR-Datei ohne Abhängigkeiten in Maven
Ich habe versucht, mithilfe von JDBC Template mit Spring MVC eine Verbindung zu MySQL herzustellen
So erstellen Sie mit Spring Boot einen eigenen Controller, der / error entspricht
So öffnen Sie eine Skriptdatei von Ubuntu mit VS-Code
Automatischer Datei-Upload mit altem Ruby-Edelstein Was tun mit Watir?
Wie man Lombok im Frühling benutzt
So führen Sie einen Komponententest für Spring AOP durch
Wie fange ich mit schlank an?
Hinweise zur Verwendung von Spring Data JDBC
Wie man ein Zeichen mit "~" einschließt
[So installieren Sie Spring Data Jpa]
So stellen Sie Spring Boot + PostgreSQL ein
So konvertieren Sie erb-Datei in haml
Wie man mssql-tools mit alpine benutzt
Verwendung von ModelMapper (Spring Boot)
[Anfänger] So löschen Sie NO FILE
So starten Sie Camunda mit Docker
So wenden Sie Thymeleaf-Änderungen sofort mit #Spring Boot + maven auf den Browser an
So lesen Sie Request Body mit Spring Boot + Spring Security mehrmals
Wie man eine hybride Suche mit morphologischer Analyse und Ngram mit Solr realisiert
So greifen Sie mit der TCP-Funktion von Spring Integration direkt auf Socket zu
So erstellen Sie eine Ruby on Rails-Entwicklungsumgebung mit Docker (Rails 6.x)
Server mit Spring Gradle ausführbar So erstellen Sie JAR und WAR