Spring MVC – AngularJS – Caricamento file – org.apache.commons.fileupload.FileUploadException

Ho un’applicazione Web Java Spring MVC come server. E l’applicazione basata su AngularJS come client.

In AngularJS, devo caricare un file e inviarlo al server.

Ecco il mio html

Ecco il mio 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); }); }; }); 

Sta andando al server. Ecco il mio 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 itr=request.getFileNames(); MultipartFile file=request.getFile(itr.next()); String fileName=file.getOriginalFilename(); System.out.println(fileName); } } 

Quando eseguo questo ottengo la seguente eccezione

 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.(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) 

Nel mio applicationContext.xml , ho menzionato

    

sto usando

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

Come risolvere questo?

Sarebbe bello se qualcuno mi inviasse come inviare file e altri dati da angularJS e scaricarlo nel server.

AGGIORNAMENTO 1

@ Michael: Posso vederlo solo nella console, quando faccio clic su Invia.

 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 

Il mio server è in esecuzione in un’altra porta 8080. Sono uisng yeoman, grugnito e pergolato. Così sottile gruntfile.js ho menzionato la porta del server. Quindi va al server ed esegue quello e genera l’eccezione

AGGIORNAMENTO 2

Il limite non è impostato

 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 

Ho affrontato lo stesso problema e ho riscontrato lo stesso problema anche dopo aver aggiornato TransformRequest. ‘Alcuni come, il limite dell’intestazione non sembra aver impostato correttamente.

Seguendo http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs , il problema è risolto. Estratto dalla posizione ….

Impostando ‘Content-Type’: indefinito, il browser imposta il Content-Type su multipart / form-data per noi e riempie il limite corretto. Impostazione manuale ‘Content-Type’: multipart / form-data non riuscirà a riempire il parametro di confine della richiesta.

Non sono sicuro se questo aiuti qualcuno, ma forse rende facile per le persone che guardano questo post … Almeno, lo rende meno difficile.

introduzione

Ho avuto lo stesso problema e ho trovato una soluzione completa per inviare sia json che file da una pagina angular a un metodo Spring MVC.

Il problema principale è $ http che non invia l’intestazione Content-type corretta (spiegherò perché).

La teoria su multipart / form-data

Per inviare sia json che file abbiamo bisogno di inviare un multipart / form-data, che significa “inviamo diversi elementi nel corpo separati da un separatore speciale”. Questo separatore speciale è chiamato “boundary”, che è una stringa che non è presente in nessuno degli elementi che verranno inviati.

Il server deve sapere quale limite viene utilizzato, quindi deve essere indicato nell’intestazione del tipo di contenuto (Content-Type multipart / form-data; boundary = $ the_boundary_used).

Quindi … sono necessarie due cose:

  1. Nell’intestazione -> indicare multipart / form-data E quale limite viene utilizzato (qui è dove $ http non riesce)
  2. Nel corpo -> separa ciascun parametro di richiesta con il limite

Esempio di una buona richiesta:

intestazione

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

Che dice al server “Invio un messaggio multipart con il separatore successivo (confine): ————————— 129291770317552

corpo

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

Dove stiamo inviando 2 argomenti, “clientInfo” e “file” separati dal confine.

Il problema

Se la richiesta viene inviata con $ http, il limite non viene inviato nell’intestazione (punto 1), quindi Spring non è in grado di elaborare i dati (non sa come dividere le “parti” della richiesta).

L’altro problema è che il limite è conosciuto solo dal FormData … ma FormData non ha accessori quindi è imansible sapere quale limite viene usato !!!

La soluzione

Invece di usare $ http in js dovresti usare XMLHttpRequest standard, qualcosa del tipo:

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

Quindi, nel tuo metodo Spring potresti avere qualcosa come:

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

La risposta di Carlos Verdes non ha funzionato con il mio intercettore $ http, che aggiunge le intestazioni di authorization e così via. Così ho deciso di aggiungere alla sua soluzione e creare il mio usando $ http .

Clientside Angular (1.3.15)

Il mio modulo (usando la syntax del controllerAs ) sta assumendo un file e un object semplice contenente le informazioni che dobbiamo inviare al server. In questo caso sto usando un nome semplice e digito proprietà String .

 

Il primo passo è stato creare una direttiva che colleghi il mio file all’ambito del controller designato (in questo caso myController) in modo che possa accedervi. Il collegamento diretto a un modello nel controller non funzionerà come tipo di input = il file non è una funzione incorporata.

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

In secondo luogo ho creato una factory chiamata myObject con un metodo di istanza create che mi consente di trasformare i dati invocando la creazione sul server. Questo metodo aggiunge tutto a un object FormData e lo converte utilizzando il metodo transformRequest ( angular.identity ). È fondamentale impostare l’intestazione su undefined . (Le versioni precedenti di Angular potrebbero richiedere qualcosa di diverso da definire non definito). Ciò consentirà l’impostazione automatica dell’indicatore multidata / limite (vedi il post di 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 } }); } 

Tutto ciò che resta da fare lato client è la creazione di istanze di un nuovo myObject in myController e l’invocazione del metodo create nella funzione create del controller all’atto dell’invio del mio modulo.

 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)

Sul RestController ora posso semplicemente fare quanto segue: (Supponendo di avere un POO 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 } 

Nota, non sto usando i parametri di richiesta ma sto facendo semplicemente partire la conversione da JSON a POJO / DTO. Assicurati di aver impostato correttamente anche il bean MultiPartResolver e di averlo aggiunto al tuo pom.xml. (E Jackson-Mapper se necessario)

spring-context.xml

     

pom.xml

  commons-fileupload commons-fileupload ${commons-fileupload.version}  

Puoi provare questo

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

.Giava

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

Questo è basato su https://github.com/murygin/rest-document-archive

C’è un buon esempio di caricamento di file https://murygin.wordpress.com/2014/10/13/rest-web-service-file-uploads-spring-boot/