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

Mam jako serwer aplikację webową Java Spring MVC. I aplikacji opartej na AngularJS jako klienta.

W AngularJS muszę wgrać plik i wysłać na serwer.

Oto mój 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>

Oto mój 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);
                });
      };
});

Idzie na serwer. Oto mój 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);
    }
}

Kiedy uruchamiam to dostaję następujący wyjątek

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)

W moim applicationkontekst.xml , I wspominali

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

Używam

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

Jak to rozwiązać?

Byłoby świetnie, gdyby ktoś mi powiedział, Jak wysłać plik i inne formdata z angularJS i dostać go na serwer.

UPDATE 1

@Michał: widzę to tylko w konsoli, po kliknięciu submit.
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

Mój serwer działa na innym porcie 8080. Jestem uisng yeoman, grunt i bower. Taki cienki gruntfile.js wspomniałem o porcie serwera. Więc idzie na serwer i uruchamia to and throws the exception

UPDATE 2

Granica nie jest ustawiona

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
Author: Shiju K Babu, 2014-01-09

4 answers

Napotkałem ten sam problem i napotkałem ten sam problem nawet po aktualizacji transformRequest. 'Niektóre jak, granica nagłówka nie wydaje się być ustawiona poprawnie.

Po http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs , problem został rozwiązany. / Align = "left" / ...

Ustawiając 'Content-Type': undefined, przeglądarka ustawia Content-Type na multipart/form-data dla nas i wypełnia poprawną granicę. Ręcznie ustawienie 'Content-Type': multipart / form-data nie wypełni parametru boundary żądania.

Nie wiem, czy to komuś pomoże, ale może ułatwi ludziom oglądającym ten post... Przynajmniej to utrudnia.

 22
Author: Anand Vemula,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-28 08:43:12

Wprowadzenie

Miałem ten sam problem i znalazłem kompletne rozwiązanie, aby wysłać zarówno json jak i plik ze strony bazującej na angular do metody Spring MVC.

Głównym problemem jest $http, który nie wysyła odpowiedniego nagłówka Content-type (wyjaśnię dlaczego).

The theory about multipart / form-data

Aby wysłać zarówno json jak i file musimy wysłać multipart / form-data, co oznacza "wysyłamy różne elementy w ciele oddzielone specjalnym separatorem". This special separator nazywa się "boundary" , który jest ciągiem znaków, który nie występuje w żadnym z elementów, które mają być wysłane.

Serwer musi wiedzieć, która granica jest używana, więc musi być wskazana w nagłówku Content-type (Content-Type multipart / form-data; boundary = $the_boundary_used).

Więc... potrzebne są dwie rzeczy:

  1. w nagłówku --> wskaż multipart / form-data i która granica jest używana (tutaj jest miejsce, w którym $http nie działa)
  2. w ciele --> oddzielenie każdego parametru żądania granicą

Przykład dobrej prośby:

Header

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

Który mówi serwerowi " wysyłam wiadomość wieloczęściową z następnym separatorem (boundary): ---------------------------129291770317552

Ciało

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

Gdzie wysyłamy 2 argumenty," clientInfo "i" file " oddzielone granicą.

Problem

Jeśli żądanie jest wysyłane z $http, granica nie jest wysyłana w nagłówku (punkt 1), więc Spring nie jest w stanie przetworzyć danych (nie wie, jak podzielić "części" żądania).

Inny problem polega na tym, że granica jest znana tylko przez FormData... ale FormData nie ma żadnych accesorów, więc nie można wiedzieć, która granica jest używana!!!

Rozwiązanie

Zamiast używać $http w js powinieneś użyć standardowego XMLHttpRequest, coś w stylu:

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

Wtedy w Twojej wiosennej metodzie możesz mieć coś w stylu:

@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
Author: Carlos Verdes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-03 16:07:43

Odpowiedź Carlosa Verdesa nie zadziałała z moim $ http interceptor, który dodaje nagłówki autoryzacji i tak dalej. Postanowiłem więc dodać do jego rozwiązania i stworzyć moje używając $http.

Clientside Angular (1.3.15)

Mój formularz (używając składni controllerAs ) zakłada plik i prosty obiekt zawierający informacje potrzebne do wysłania na serwer. W tym przypadku używam prostej nazwy i typ String właściwość.

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

Pierwszy krok było stworzenie dyrektywy, która wiąże mój plik z zakresem wyznaczonego kontrolera (w tym przypadku myController), abym mógł uzyskać do niego dostęp. Powiązanie go bezpośrednio z Modelem w kontrolerze nie będzie działać, ponieważ plik input type = file nie jest wbudowaną funkcją.

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

Po Drugie stworzyłem fabrykę o nazwie myObject za pomocą metody instancji create , która pozwala mi przekształcić dane po wywołaniu create na serwerze. Ta metoda dodaje wszystko do obiektu FormData i konwertuje go za pomocą metoda transformRequest (angular.tożsamość ). Ważne jest, aby ustawić nagłówek na undefined . (Starsze wersje kątowe mogą wymagać ustawienia czegoś więcej niż undefined). Pozwoli to na Automatyczne ustawienie znacznika multidata/boundary (Zobacz post Carlosa).

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

Po stronie klienta pozostaje jedynie utworzenie instancji nowego myObject w myController i wywołanie metody create w funkcji create kontrolera po przesłaniu formularza.

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)

Na Restcontrolle mogę teraz po prostu wykonać następujące czynności: (zakładając, że mamy 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
}

UWAGA, nie używam requestparameters, ale pozwalam spring zrobić konwersję JSON do POJO/DTO. Upewnij się, że masz MultiPartResolver bean również skonfigurowany poprawnie i dodany do pom.xml. (I Jackson-Mapper w razie potrzeby)

Wiosna-kontekst.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
Author: skubski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-16 12:06:38

Możesz spróbować tego

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

To na podstawie https://github.com/murygin/rest-document-archive

Jest dobry przykład przesyłania plików https://murygin.wordpress.com/2014/10/13/rest-web-service-file-uploads-spring-boot/

 4
Author: Marina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-27 13:58:57