web-dev-qa-db-fra.com

Spring Boot: téléchargement de fichiers volumineux en streaming avec Apache Commons FileUpload

J'essaie de télécharger un fichier volumineux à l'aide de l'API Apache Commons File Upload "en continu".

J'utilise Apache Commons File Uploader et non l'uploader Spring Multipart par défaut, car il échoue lorsque nous téléchargeons des fichiers très volumineux (~ 2 Go). Je travaille sur une application SIG où de tels téléchargements de fichiers sont assez courants.

Le code complet de mon contrôleur de téléchargement de fichier est le suivant:

@Controller
public class FileUploadController {

    @RequestMapping(value="/upload", method=RequestMethod.POST)
    public void upload(HttpServletRequest request) {
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart) {
            // Inform user about invalid request
            return;
        }

        //String filename = request.getParameter("name");

        // Create a new file upload handler
        ServletFileUpload upload = new ServletFileUpload();

        // Parse the request
        try {
            FileItemIterator iter = upload.getItemIterator(request);
            while (iter.hasNext()) {
                FileItemStream item = iter.next();
                String name = item.getFieldName();
                InputStream stream = item.openStream();
                if (item.isFormField()) {
                    System.out.println("Form field " + name + " with value " + Streams.asString(stream) + " detected.");
                } else {
                    System.out.println("File field " + name + " with file name " + item.getName() + " detected.");
                    // Process the input stream
                    OutputStream out = new FileOutputStream("incoming.gz");
                    IOUtils.copy(stream, out);
                    stream.close();
                    out.close();

                }
            }
        }catch (FileUploadException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    @RequestMapping(value = "/uploader", method = RequestMethod.GET)
    public ModelAndView uploaderPage() {
        ModelAndView model = new ModelAndView();
        model.setViewName("uploader");
        return model;
    }

}

Le problème est que la fonction getItemIterator(request) renvoie toujours un itérateur qui ne contient aucun élément (c'est-à-dire iter.hasNext()) renvoie toujours false.

Mon fichier application.properties est le suivant:

spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:19095/authdb
spring.datasource.username=georbis
spring.datasource.password=asdf123

logging.level.org.springframework.web=DEBUG

spring.jpa.hibernate.ddl-auto=update

multipart.maxFileSize: 128000MB
multipart.maxRequestSize: 128000MB

server.port=19091

La vue JSP pour le /uploader est la suivante:

<html>
<body>
<form method="POST" enctype="multipart/form-data" action="/upload">
    File to upload: <input type="file" name="file"><br />
    Name: <input type="text" name="name"><br /> <br />
    Press here to upload the file!<input type="submit" value="Upload">
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</body>
</html>

Que pourrais-je faire de mal?

19
balajeerc

Grâce aux commentaires très utiles de M.Deinum, j'ai réussi à résoudre le problème. J'ai nettoyé une partie de mon message original et je l'affiche comme une réponse complète pour référence future.

La première erreur que j'ai commise a été de ne pas désactiver la valeur par défaut MultipartResolver fournie par Spring. Cela finissait par traiter le résolveur HttpServeletRequest par le résolveur avant que mon contrôleur ne puisse agir dessus.

Le moyen de le désactiver, grâce à M. Deinum, était le suivant:

multipart.enabled=false

Cependant, il y avait encore un autre piège caché qui m'attendait après cela. Dès que j'ai désactivé le résolveur multipart par défaut, j'ai commencé à obtenir l'erreur suivante lors d'une tentative de téléchargement:

Fri Sep 25 20:23:47 IST 2015
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported

Dans ma configuration de sécurité, j'avais activé la protection CSRF. Cela a nécessité l'envoi de ma demande POST de la manière suivante:

<html>
<body>
<form method="POST" enctype="multipart/form-data" action="/upload?${_csrf.parameterName}=${_csrf.token}">
    <input type="file" name="file"><br>
    <input type="submit" value="Upload">
</form>
</body>
</html>

J'ai aussi un peu modifié mon contrôleur:

@Controller
public class FileUploadController {
    @RequestMapping(value="/upload", method=RequestMethod.POST)
    public @ResponseBody Response<String> upload(HttpServletRequest request) {
        try {
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (!isMultipart) {
                // Inform user about invalid request
                Response<String> responseObject = new Response<String>(false, "Not a multipart request.", "");
                return responseObject;
            }

            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload();

            // Parse the request
            FileItemIterator iter = upload.getItemIterator(request);
            while (iter.hasNext()) {
                FileItemStream item = iter.next();
                String name = item.getFieldName();
                InputStream stream = item.openStream();
                if (!item.isFormField()) {
                    String filename = item.getName();
                    // Process the input stream
                    OutputStream out = new FileOutputStream(filename);
                    IOUtils.copy(stream, out);
                    stream.close();
                    out.close();
                }
            }
        } catch (FileUploadException e) {
            return new Response<String>(false, "File upload error", e.toString());
        } catch (IOException e) {
            return new Response<String>(false, "Internal server IO error", e.toString());
        }

        return new Response<String>(true, "Success", "");
    }

    @RequestMapping(value = "/uploader", method = RequestMethod.GET)
    public ModelAndView uploaderPage() {
        ModelAndView model = new ModelAndView();
        model.setViewName("uploader");
        return model;
    }
}

où Response est juste un type de réponse générique simple que j'utilise:

public class Response<T> {
    /** Boolean indicating if request succeeded **/
    private boolean status;

    /** Message indicating error if any **/
    private String message;

    /** Additional data that is part of this response **/
    private T data;

    public Response(boolean status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }

    // Setters and getters
    ...
}
24
balajeerc

Si vous utilisez une version récente de Spring Boot (j'utilise la version 2.0.0.M7), les noms de propriété ont alors été modifiés.

spring.servlet.multipart.maxFileSize = -1

spring.servlet.multipart.maxRequestSize = -1

spring.servlet.multipart.enabled = false

Si vous obtenez des exceptions StreamClosed dues à l'activation de plusieurs implémentations, la dernière option vous permet de désactiver l'implémentation Spring par défaut.

8
ThetaSinner

Essayez d’ajouter spring.http.multipart.enabled=false dans application.properties file.

1
HopeBing

J'utilise kindeditor + springboot. Lorsque j'utilise la demande (MultipartHttpServletRequest). Je pourrais obtenir le fichier, mais j'utilise appeche-common-io: upload.parse (demande), la valeur de retour est null.

public BaseResult uploadImg(HttpServletRequest request,String type){
                MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
                MultiValueMap<String, MultipartFile> multiFileMap = multipartRequest.getMultiFileMap();
0
user9814979