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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21015891/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 05:57:49  来源:igfitidea点击:

Spring MVC - AngularJS - File Upload - org.apache.commons.fileupload.FileUploadException

javaspringangularjsspring-mvcfile-upload

提问by Shiju K Babu

I have a Java Spring MVC Web application as server. And AngularJS based application as client.

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

In AngularJS, I have to upload a file and send to server.

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

Here is my html

这是我的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>

Here is my UploadController.js

这是我的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);
                });
      };
});

It is going to the server. Here is my DocumentUploadController.java

它要去服务器。这是我的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);
    }
}

When I run this I get the following exception

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

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)

In my applicationContext.xml, I have mentioned

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

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

I am using

我在用

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

How to solve this?

如何解决这个问题?

It would be great if anyone tel me how to send file and other formdata from angularJS and get it in server.

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

UPDATE 1

更新 1

@Michael : I can see this only in the console, when I click submit.

@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

My server is running in other port 8080. I am uisng yeoman,grunt and bower. So thin gruntfile.js I have mentioned the server port. So it goes to server and running that and throws the exception

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

UPDATE 2

更新 2

The boundary is not setting

边界未设定

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

采纳答案by Anand Vemula

I faced the same issue and encountered the same issue even after updating the transformRequest. 'Some how, the header boundary doesn't seem to have set correctly.

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

Following http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs, the problem is resolved. Extract from the location....

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

By setting ‘Content-Type': undefined, the browser sets the Content-Type to multipart/form-data for us and fills in the correct boundary. Manually setting ‘Content-Type': multipart/form-data will fail to fill in the boundary parameter of the request.

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

Not sure if this helps any one but perhaps makes it easy for people looking at this post... At least, it makes it less difficult.

不确定这是否对任何人有帮助,但也许会让人们更容易查看这篇文章......至少,它不那么困难。

回答by Marina

You can try this

你可以试试这个

.js

.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

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

That's based on https://github.com/murygin/rest-document-archive

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

There is a good example of file upload https://murygin.wordpress.com/2014/10/13/rest-web-service-file-uploads-spring-boot/

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

回答by Carlos Verdes

Introduction

介绍

I have had the same problem and found a complete solution to send both json and file from angular based page to a Spring MVC method.

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

The main problem is the $http which doesn't send the proper Content-type header (I will explain why).

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

The theory about multipart/form-data

multipart/form-data 理论

To send both json and file we need to send a multipart/form-data, which means "we send different items in the body separated by a special separator". This special separator is called "boundary", which is a string that is not present in any of the elements that are going to be sent.

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

The server needs to know which boundary is being used so it has to be indicated in the Content-type header (Content-Type multipart/form-data; boundary=$the_boundary_used).

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

So... two things are needed:

所以......需要两件事:

  1. In the header --> indicate multipart/form-data AND which boundary is used (here is where $http fails)
  2. In the body --> separate each request parameter with the boundary
  1. 在标题中 --> 指明 multipart/form-data AND 使用哪个边界(这里是 $http 失败的地方)
  2. 在正文中 --> 用边界分隔每个请求参数

Example of a good request:

一个好的请求示例:

header

标题

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

Which is telling the server "I send a multipart message with the next separator (boundary): ---------------------------129291770317552

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

body

身体

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

Where we are sending 2 arguments, "clientInfo" and "file" separated by the boundary.

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

The problem

问题

If the request is sent with $http, the boundary is not sent in the header (point 1), so Spring is not able to process the data (it doesn't know how to split the "parts" of the request).

如果请求是用 $http 发送的,则边界不会在头中发送(第 1 点),因此 Spring 无法处理数据(它不知道如何拆分请求的“部分”)。

The other problem is that the boundary is only known by the FormData... but FormData has no accesors so it's impossible to know which boundary is being used!!!

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

The solution

解决方案

Instead of using $http in js you should use standard XMLHttpRequest, something like:

您应该使用标准的 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);

Then, in your Spring method you could have something like:

然后,在您的 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;

}

回答by skubski

Carlos Verdes's answer failed to work with my $http interceptor, which adds authorization headers and so on. So I decided to add to his solution and create mine using $http.

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

Clientside Angular (1.3.15)

客户端 Angular (1.3.15)

My form (using the controllerAssyntax) is assuming a file and a simple object containing the information we need to send to the server. In this case I'm using a simple name and type Stringproperty.

我的表单(使用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>

The first step was to create a directive that binds my file to the scope of the designated controller (in this case myController) so I can access it. Binding it directly to a model in your controller won't work as the input type=file isn't a built-in feature.

第一步是创建一个指令,将我的文件绑定到指定控制器的范围(在本例中为 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]);
                });
            });
        }
    };
}]);

Secondly I created a factory called myObject with an instance method createthat allows me to transform the data upon invoking create on the server. This method adds everything to a FormData object and converts it using the transformRequest method (angular.identity). It is crucial to set your header to undefined. (Older Angular versions might require something than undefined to be set). This will allow the multidata/boundary marker to be set automatically (see Carlos's post).

其次,我使用实例方法create创建了一个名为 myObject 的工厂,该方法允许我在服务器上调用 create 时转换数据。此方法将所有内容添加到 FormData 对象并使用 transformRequest 方法 ( angular.identity)进行转换。将标头设置为undefined至关重要。(较旧的 Angular 版本可能需要设置 undefined 以外的东西)。这将允许自动设置多数据/边界标记(参见 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 }
        });
}

All that is left to do client side is instantiating a new myObjectin myController and invoking the create method in the controller's create function upon submitting my form.

客户端要做的就是在 myController 中实例化一个新的 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);

Serverside Spring (4.0)

服务器端 Spring (4.0)

On the RestController I can now simply do the following: (Assuming we have a POJO MyObject)

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

Notice, I'm not using requestparameters but just letting spring do the JSON to POJO/DTO conversion. Make sure you got the MultiPartResolver beanset up correctly too and added to your pom.xml. (And Hymanson-Mapper if needed)

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

spring-context.xml

spring-context.xml

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

pom.xml

pom.xml

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