Spring MVC - AngularJS - 文件上传 - org.apache.commons.fileupload.FileUploadException

Posted

技术标签:

【中文标题】Spring MVC - AngularJS - 文件上传 - org.apache.commons.fileupload.FileUploadException【英文标题】:Spring MVC - AngularJS - File Upload - org.apache.commons.fileupload.FileUploadException 【发布时间】:2014-01-27 17:57:32 【问题描述】:

我有一个 Java Spring MVC Web 应用程序作为服务器。以及基于 AngularJS 的应用程序作为客户端。

在 AngularJS 中,我必须上传文件并发送到服务器。

这是我的html

<form ng-submit="uploadFile()" class="form-horizontal" enctype="multipart/form-data">
   <input type="file" name="file" ng-model="document.fileInput" id="file" onchange="angular.element(this).scope().setTitle(this)" />
   <input type="text" class="col-sm-4" ng-model="document.title" id="title" />
   <button class="btn btn-primary" type="submit">
         Submit
    </button>
</form>

这是我的 UploadController.js

'use strict';

var mainApp=angular.module('mainApp', ['ngCookies']);

mainApp.controller('FileUploadController', function($scope, $http) 

    $scope.document = ;

        $scope.setTitle = function(fileInput) 

        var file=fileInput.value;
        var filename = file.replace(/^.*[\\\/]/, '');
        var title = filename.substr(0, filename.lastIndexOf('.'));
        $("#title").val(title);
        $("#title").focus();
        $scope.document.title=title;
    ;

        $scope.uploadFile=function()
             var formData=new FormData();
         formData.append("file",file.files[0]);
                   $http(
                  method: 'POST',
                  url: '/serverApp/rest/newDocument',
                  headers:  'Content-Type': 'multipart/form-data',
                  data:  formData
                )
                .success(function(data, status)                        
                    alert("Success ... " + status);
                )
                .error(function(data, status) 
                    alert("Error ... " + status);
                );
      ;
);

它会发送到服务器。这是我的 DocumentUploadController.java

@Controller
public class DocumentUploadController 

    @RequestMapping(value="/newDocument", headers = "'Content-Type': 'multipart/form-data'", method = RequestMethod.POST)
    public void UploadFile(MultipartHttpServletRequest request, HttpServletResponse response) 

        Iterator<String> itr=request.getFileNames();

        MultipartFile file=request.getFile(itr.next());

        String fileName=file.getOriginalFilename();
        System.out.println(fileName);
    

当我运行它时,我得到以下异常

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found] with root cause
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
    at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:954)
    at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:331)
    at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:351)
    at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:126)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:156)
    at org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.java:139)
    at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1047)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:892)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:801)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

在我的 applicationContext.xml 中,我已经提到了

<bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="100000" />
    </bean>

我正在使用

spring - 3.2.1.RELAESE
commons-fileupload - 1.2.2
commons-io - 2.4

如何解决?

如果有人告诉我如何从 angularJS 发送文件和其他表单数据并在服务器中获取它,那就太好了。

更新 1

@Michael:当我点击提交时,我只能在控制台中看到这个。

POST http://localhost:9000/serverApp/rest/newDocument 500 (Internal Server Error) angular.js:9499
(anonymous function) angular.js:9499
sendReq angular.js:9333
$http angular.js:9124
$scope.uploadFile invoice.js:113
(anonymous function) angular.js:6541
(anonymous function) angular.js:13256
Scope.$eval angular.js:8218
Scope.$apply angular.js:8298
(anonymous function) angular.js:13255
jQuery.event.dispatch jquery.js:3074
elemData.handle

我的服务器在其他端口 8080 上运行。我正在使用 yeoman、grunt 和 bower。这么瘦的 gruntfile.js 我已经提到了服务器端口。所以它去服务器并运行它并抛出异常

更新 2

边界没有设置

Request URL:http://localhost:9000/serverApp/rest/newDocument
Request Method:POST
Status Code:500 Internal Server Error

Request Headers view source
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:792
Content-Type:multipart/form-data
Cookie:ace.settings=%7B%22sidebar-collapsed%22%3A-1%7D; isLoggedIn=true; loggedUser=%7B%22name%22%3A%22admin%22%2C%22password%22%3A%22admin23%22%7D
Host:localhost:9000
Origin:http://localhost:9000
Referer:http://localhost:9000/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/31.0.1650.63 Safari/537.36
X-Requested-With:XMLHttpRequest
Request Payload
------WebKitFormBoundaryCWaRAlfQoZEBGofY
Content-Disposition: form-data; name="file"; filename="csv.csv"
Content-Type: text/csv


------WebKitFormBoundaryCWaRAlfQoZEBGofY--
Response Headers view source
connection:close
content-length:5007
content-type:text/html;charset=utf-8
date:Thu, 09 Jan 2014 11:46:53 GMT
server:Apache-Coyote/1.1

【问题讨论】:

我有类似问题的经验分享[这里][1] [1]:***.com/questions/31774695/… 【参考方案1】:

Carlos Verdes 的回答无法与我的 $http 拦截器一起使用,该拦截器添加了授权标头等。所以我决定添加到他的解决方案中并使用 $http 创建我的解决方案。

客户端 Angular (1.3.15)

我的表单(使用 controllerAs 语法)假设一个文件和一个包含我们需要发送到服务器的信息的简单对象。在这种情况下,我使用一个简单的名称并键入 String 属性。

<form>
    <input type="text" ng-model="myController.myObject.name" />

     <select class="form-control input-sm" ng-model="myController.myObject.type"
      ng-options="type as type for type in myController.types"></select>

     <input class="input-file" file-model="myController.file" type="file">

</form>

第一步是创建一个指令,将我的文件绑定到指定控制器(在本例中为 myController)的范围,以便我可以访问它。将其直接绑定到控制器中的模型将不起作用,因为输入 type=file 不是内置功能。

.directive('fileModel', ['$parse', function ($parse) 
    return 
        restrict: 'A',
        link: function(scope, element, attrs) 
            var model = $parse(attrs.fileModel);
            var modelSetter = model.assign;

            element.bind('change', function()
                scope.$apply(function()
                    modelSetter(scope, element[0].files[0]);
                );
            );
        
    ;
]);

其次,我使用实例方法 create 创建了一个名为 myObject 的工厂,该方法允许我在服务器上调用 create 时转换数据。此方法将所有内容添加到 FormData 对象并使用 transformRequest 方法 (angular.identity) 对其进行转换。将标题设置为 undefined 至关重要。 (旧的 Angular 版本可能需要设置一些未定义的东西)。这将允许自动设置多数据/边界标记(参见 Carlos 的帖子)。

  myObject.prototype.create = function(myObject, file) 
        var formData = new FormData();
        formData.append('refTemplateDTO', angular.toJson(myObject));
        formData.append('file', file);

        return $http.post(url, formData, 
            transformRequest: angular.identity,
            headers: 'Content-Type': undefined 
        );

客户端剩下要做的就是在 myController 中实例化一个 new myObject 并在提交表单时调用控制器的 create 函数中的 create 方法。

this.myObject = new myObject();

this.create = function() 
        //Some pre handling/verification
        this.myObject.create(this.myObject, this.file).then(
                              //Do some post success/error handling
                            );
    .bind(this);

服务器端 Spring (4.0)

在 RestController 上,我现在可以简单地执行以下操作:(假设我们有一个 POJO MyObject)

@RequestMapping(method = RequestMethod.POST)
@Secured( "ROLE_ADMIN" ) //This is why I needed my $httpInterceptor
public void create(MyObject myObject, MultipartFile file) 
     //delegation to the correct service

注意,我没有使用请求参数,只是让 spring 进行 JSON 到 POJO/DTO 的转换。确保您也正确设置了 MultiPartResolver bean 并将其添加到您的 pom.xml。 (如果需要,还有 Jackson-Mapper)

spring-context.xml

<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="268435456" /> <!-- 256 megs -->
</bean>

pom.xml

<dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>$commons-fileupload.version</version> 
</dependency>

【讨论】:

【参考方案2】:

简介

我遇到了同样的问题,并找到了一个完整的解决方案,将 json 和文件从基于角度的页面发送到 Spring MVC 方法。

主要问题是 $http 没有发送正确的 Content-type 标头(我将解释原因)。

multipart/form-data理论

要同时发送 json 和文件,我们需要发送一个 multipart/form-data,这意味着“我们在正文中发送不同的项目,由一个特殊的分隔符分隔”。这种特殊的分隔符称为“边界”,它是一个字符串,不会出现在要发送的任何元素中。

服务器需要知道正在使用哪个边界,因此必须在 Content-Type 标头中指明(Content-Type multipart/form-data;boundary=$the_boundary_used)。

所以...需要两件事:

    在标题中 --> 指明 multipart/form-data 以及使用哪个边界(这里是 $http 失败的地方) 在正文中 --> 用边界分隔每个请求参数

良好请求的示例:

标题

Content-Type    multipart/form-data; boundary=---------------------------129291770317552

这告诉服务器“我发送带有下一个分隔符(边界)的多部分消息:---------------------------129291770317552

身体

-----------------------------129291770317552 Content-Disposition: form-data; name="clientInfo" 
 "name": "Johny", "surname":"Cash" 

-----------------------------129291770317552 
Content-Disposition: form-data; name="file"; filename="yourFile.pdf"           
Content-Type: application/pdf 
%PDF-1.4 
%õäöü 
-----------------------------129291770317552 --

我们发送 2 个参数,“clientInfo”和“file”,由边界分隔。

问题

如果请求是用$http发送的,那么边界不是在header中发送的(第1点),所以Spring无法处理数据(它不知道如何分割请求的“部分” )。

另一个问题是边界只有 FormData 知道......但是 FormData 没有访问器,所以不可能知道正在使用哪个边界!!!

解决办法

您应该使用标准的 XMLHttpRequest,而不是在 js 中使用 $http,例如:

//create form data to send via POST
var formData=new FormData();

console.log('loading json info');
formData.append('infoClient',angular.toJson(client,true)); 
// !!! when calling formData.append the boundary is auto generated!!!
// but... there is no way to know which boundary is being used !!!

console.log('loading file);
var file= ...; // you should load the fileDomElement[0].files[0]
formData.append('file',file);

//create the ajax request (traditional way)
var request = new XMLHttpRequest();
request.open('POST', uploadUrl);
request.send(formData);

然后,在你的 Spring 方法中,你可以有类似的东西:

@RequestMapping(value = "/", method = RequestMethod.POST)
public @ResponseBody Object newClient(
        @RequestParam(value = "infoClient") String infoClientString,
        @RequestParam(value = "file") MultipartFile file) 

    // parse the json string into a valid DTO
    ClientDTO infoClient = gson.fromJson(infoClientString, ClientDTO.class);
    //call the proper service method
    this.clientService.newClient(infoClient,file);

    return null;


【讨论】:

这是一个烦人的问题,我希望 Angular 能解决它! 在不设置内容类型的情况下使用 XMLHttpRequest 对我有用,谢谢。【参考方案3】:

你可以试试这个

.js

$scope.uploadFile=function()
    var formData=new FormData();
    formData.append("file",file.files[0]);
    $http.post('/serverApp/rest/newDocument', formData, 
        transformRequest: function(data, headersGetterFunction) 
            return data;
        ,
        headers:  'Content-Type': undefined 
        ).success(function(data, status)                        
            alert("Success ... " + status);
        ).error(function(data, status) 
            alert("Error ... " + status);
        );

.java

@Controller
public class DocumentUploadController 
@RequestMapping(value="/newDocument", method = RequestMethod.POST)
    public @ResponseBody void UploadFile(@RequestParam(value="file", required=true) MultipartFile file) 
        String fileName=file.getOriginalFilename();
        System.out.println(fileName);
    

这是基于https://github.com/murygin/rest-document-archive

有一个很好的文件上传例子 https://murygin.wordpress.com/2014/10/13/rest-web-service-file-uploads-spring-boot/

【讨论】:

【参考方案4】:

我遇到了同样的问题,即使在更新了 transformRequest 之后也遇到了同样的问题。 '不知何故,标题边界似乎没有正确设置。

按照http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs,问题解决了。从该位置提取....

通过设置‘Content-Type’: undefined,浏览器为我们设置Content-Type为multipart/form-data,并填写正确的边界。手动设置'Content-Type':multipart/form-data将无法填写请求的边界参数。

不确定这是否对任何人有帮助,但也许可以让查看这篇文章的人更容易......至少,它降低了难度。

【讨论】:

URL 不方便访问,请推荐邮政编码并放置参考结束响应。 网址uncorkedstudios.com/blog/… 适合我

以上是关于Spring MVC - AngularJS - 文件上传 - org.apache.commons.fileupload.FileUploadException的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC 和 Angularjs

AngularJS 和 Spring MVC 安全性

Spring MVC + AngularJS + JWT 令牌过期 - HowTo

使用 Spring MVC 和 AngularJS 的方法 DELETE

AngularJS和Spring MVC的Ajax GET错误

Spring MVC - AngularJS - 文件上传 - org.apache.commons.fileupload.FileUploadException