web-dev-qa-db-ja.com

Spring MVC-AngularJS-ファイルのアップロード-org.Apache.commons.fileupload.FileUploadException

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>

これが私のploadController.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で実行されています。私はヨーマン、うなり声、お辞儀をしています。薄い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
18
Shiju K Babu

TransformRequestを更新した後でも、同じ問題に直面し、同じ問題に遭遇しました。 「どういうわけか、ヘッダーの境界が正しく設定されていないようです。

http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs に従って、問題は解決しました。ロケーションから抽出...

「Content-Type」:未定義を設定することにより、ブラウザーはContent-Typeをmultipart/form-dataに設定し、正しい境界を埋めます。 「Content-Type」を手動で設定:multipart/form-dataは、リクエストの境界パラメーターの入力に失敗します。

これが誰かに役立つかどうかはわかりませんが、おそらくこの投稿を見ている人にとっては簡単になるでしょう...少なくとも、それはそれほど難しくありません。

22
Anand Vemula

前書き

同じ問題があり、jsonとファイルの両方をangularベースのページからSpring MVCメソッドに送信する完全なソリューションを見つけました。

主な問題は、適切なContent-typeヘッダーを送信しない$ httpです(理由を説明します)。

Multipart/form-dataに関する理論

Jsonとfileの両方を送信するには、multipart/form-dataを送信する必要があります。これは、「特別な区切り文字で区切られた本文の異なるアイテムを送信する」ことを意味します。この特別なセパレータは「境界」と呼ばれ、送信される要素には存在しない文字列です。

サーバーは、どの境界が使用されているかを知る必要があるため、Content-typeヘッダーで指定する必要があります(Content-Type multipart/form-data; boundary = $ the_boundary_used)。

だから... 2つのことが必要です。

  1. ヘッダーで-> multipart/form-dataおよび使用される境界を示します(ここで$ httpが失敗します)
  2. 本文で->各リクエストパラメータを境界で区切ります

適切なリクエストの例:

ヘッダ

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で送信された場合、境界はヘッダーで送信されないため(ポイント1)、Springはデータを処理できません(要求の「部分」を分割する方法がわかりません)。

もう1つの問題は、境界がFormDataによってのみ認識されることですが、FormDataにはアクセサーがないため、どの境界が使用されているかを知ることができません!!!

ソリューション

Jsで$ httpを使用する代わりに、次のような標準のXMLHttpRequestを使用する必要があります。

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

}
16
Carlos Verdes

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を呼び出したときにデータを変換できるインスタンスメソッドcreateを使用してmyObjectというファクトリーを作成しました。このメソッドは、すべてを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);

サーバーサイドスプリング(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
}

リクエストパラメータを使用していないが、JSONからPOJO/DTOへの変換をspringに任せていることに注意してください。 MultiPartResolverbeanも正しくセットアップされ、pom.xmlに追加されていることを確認してください。 (必要に応じてジャクソンマッパー)

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

これを試すことができます

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