SpringBoot:使用Apache Commons FileUpload上传大型流文件

45

我正在尝试使用“流式”Apache Commons文件上传API来上传大型文件。

我之所以使用Apache Commons文件上传器而不是默认的Spring Multipart上传器,是因为当我们上传非常大的文件大小(~2GB)时,后者会失败。我正在开发一个GIS应用程序,这种文件上传是相当常见的。

我的文件上传控制器的完整代码如下:

@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;
    }

}

问题在于getItemIterator(request)总是返回一个没有任何项的迭代器(即iter.hasNext()始终返回false)。

我的application.properties文件如下:

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

/uploader的JSP视图如下:

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

我可能做错了什么?


5
你是否禁用了Spring的多部分支持?否则你的解决方案将无法运行,因为Spring已经解析了请求。请将所有的multipart属性替换为单个multipart.enabled=false以禁用默认处理。 - M. Deinum
我没有做任何特定的事情来禁用Spring多部分支持。我尝试在我的application.properties文件中添加multipart.enabled=false。然而,一旦我这样做了,每次上传时都会出现“405:请求方法'POST'不受支持”的错误。 - balajeerc
这可能表示映射错误或者提交到了错误的URL...启用调试日志并查看您正在提交到哪个URL以及您的控制器方法匹配到了哪个URL。 - M. Deinum
1
这绝对不是发布到错误的URL的情况,因为当我删除 multipart.enabled=false 时,我的控制器确实被调用了(并且我再次遇到了上面描述的问题)。 - balajeerc
5
您不需要任何 MultipartResolver,因为它会解析传入的请求并将文件存储在内存中。您需要自己处理以避免其他东西干扰您的多部分请求。 - M. Deinum
显示剩余2条评论
5个回答

46

感谢M.Deinum的帮助性评论,我成功解决了问题。我整理了一下原始帖子并将其发布为完整答案以供以后参考。

我犯的第一个错误是没有禁用Spring提供的默认MultipartResolver。这导致处理HttpServeletRequest的解析器在我的控制器之前对其进行处理并消耗它。

禁用它的方法,感谢M.Deinum如下:

multipart.enabled=false

然而,在这之后,还有一个隐藏的陷阱等着我。一旦我禁用了默认的多部分解析器,当我尝试上传文件时,就会出现以下错误:
Fri Sep 25 20:23:47 IST 2015
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported

在我的安全配置中,我已启用了CSRF保护。这要求我按以下方式发送POST请求:
<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>

我也稍微修改了我的控制器:

@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;
    }
}

这里的 Response 只是我使用的一种简单通用的响应类型:

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

2
当multipart.enabled=false时,MockMultipartFile在单元测试用例中无法工作。是否有使用MockMvc上传文件的单元测试用例示例? - free斩
我尝试设置spring.servlet.multipart.enabled=false,但迭代器仍然没有返回任何值。这是为什么? - Suvojit
在Spring Boot 2中,正确的属性名称是 spring.servlet.multipart.enabled=false - tchudyk

18

如果你正在使用较新版本的Spring Boot(我使用的是2.0.0.M7),则属性名称已更改。Spring开始使用技术特定的名称。

spring.servlet.multipart.maxFileSize=-1

spring.servlet.multipart.maxRequestSize=-1

spring.servlet.multipart.enabled=false

如果您遇到由于多个实现处于活动状态而导致的StreamClosed异常,则最后一个选项允许您禁用默认的Spring实现。


3
请在application.properties文件中添加spring.http.multipart.enabled=false

0

我使用kindeditor + springboot。 当我使用(MultipartHttpServletRequest)请求时,我可以获取文件,但是当我使用appeche-common-io:upload.parse(request)时,返回值为空。

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

0

您可以简单地添加Spring属性:

spring.servlet.multipart.max-file-size=20000KB
spring.servlet.multipart.max-request-size=20000KB

这里我的最大文件大小为20000KB,如果需要可以更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接