Spring MVC – AngularJS – Carga de archivos – org.apache.commons.fileupload.FileUploadException

Tengo una aplicación web Java Spring MVC como servidor. Y la aplicación basada AngularJS como cliente.

En AngularJS, tengo que subir un archivo y enviarlo al servidor.

Aquí está mi html

Aquí está mi 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); }); }; }); 

Va al servidor. Aquí está mi 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); } } 

Cuando ejecuto esto obtengo la siguiente excepción

 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) 

En mi applicationContext.xml , he mencionado

    

estoy usando

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

¿Cómo resolver esto?

Sería genial si alguien me dice cómo enviar archivos y otros datos de angularJS y obtenerlos en el servidor.

ACTUALIZACIÓN 1

@Michael: puedo ver esto solo en la consola, cuando hago clic en enviar.

 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 

Mi servidor se está ejecutando en otro puerto 8080. Estoy haciendo yeoman, gruñido y glorieta. So thin gruntfile.js He mencionado el puerto del servidor. Entonces va al servidor y ejecuta eso y arroja la excepción

ACTUALIZACIÓN 2

El límite no está configurando

 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 

Me enfrenté al mismo problema y encontré el mismo problema incluso después de actualizar el transformRequest. ‘De alguna manera, el límite del encabezado no parece haberse configurado correctamente.

Siguiendo http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs , el problema está resuelto. Extracto de la ubicación …

Al configurar ‘Content-Type’: undefined, el navegador establece el tipo de contenido en multipart / form-data para nosotros y rellena el límite correcto. Configuración manual de ‘Content-Type’: multipart / form-data no completará el parámetro de límite de la solicitud.

No estoy seguro de si esto ayuda a alguien, pero quizás hace que sea más fácil para las personas que miran esta publicación … Al menos, lo hace menos difícil.

Introducción

He tenido el mismo problema y encontré una solución completa para enviar json y archivo desde la página angular a un método Spring MVC.

El principal problema es el $ http que no envía el encabezado de tipo de contenido adecuado (explicaré por qué).

La teoría sobre multipart / form-data

Para enviar json y el archivo necesitamos enviar un multipart / form-data, lo que significa que “enviamos diferentes elementos en el cuerpo separados por un separador especial”. Este separador especial se llama “límite”, que es una cadena que no está presente en ninguno de los elementos que se enviarán.

El servidor necesita saber qué límite se está utilizando, por lo que debe indicarse en el encabezado Content-type (Content-Type multipart / form-data; boundary = $ the_boundary_used).

Entonces … se necesitan dos cosas:

  1. En el encabezado -> indicar multipart / form-data Y qué límite se usa (aquí es donde $ http falla)
  2. En el cuerpo -> separe cada parámetro de solicitud con el límite

Ejemplo de una buena solicitud:

encabezamiento

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

Lo que le está diciendo al servidor “Envié un mensaje de varias partes con el siguiente separador (límite): ————————— 129291770317552

cuerpo

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

Donde enviamos 2 argumentos, “clientInfo” y “file” separados por el límite.

El problema

Si la solicitud se envía con $ http, el límite no se envía en el encabezado (punto 1), por lo que Spring no puede procesar los datos (no sabe cómo dividir las “partes” de la solicitud).

El otro problema es que el límite solo lo conocen los FormData … ¡pero FormData no tiene accesorios, por lo que es imposible saber qué límite se está utilizando!

La solución

En lugar de usar $ http en js, debe usar XMLHttpRequest estándar, algo así como:

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

Luego, en tu método Spring puedes tener algo como:

 @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 respuesta de Carlos Verdes no funcionó con mi interceptor $ http, que agrega encabezados de autorización y demás. Así que decidí agregar a su solución y crear la mía usando $ http .

Clientside Angular (1.3.15)

Mi formulario (usando la syntax de controllerAs ) está asumiendo un archivo y un objeto simple que contiene la información que necesitamos enviar al servidor. En este caso estoy usando un nombre simple y escriba la propiedad String .

 

El primer paso fue crear una directiva que vincule mi archivo con el scope del controlador designado (en este caso myController) para poder acceder a él. Vincularlo directamente a un modelo en su controlador no funcionará ya que el tipo de entrada = el archivo no es una función incorporada.

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

En segundo lugar, creé una fábrica llamada myObject con un método de instancia create que me permite transformar los datos al invocar create en el servidor. Este método agrega todo a un objeto FormData y lo convierte utilizando el método transformRequest ( angular.identity ). Es crucial configurar su encabezado a indefinido . (Las versiones angulares antiguas pueden requerir que se configure algo indefinido). Esto permitirá que el marcador multidata / límite se establezca automáticamente (vea la publicación de 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 } }); } 

Todo lo que queda por hacer del lado del cliente es crear una instancia de un nuevo myObject en myController e invocar el método de creación en la función de creación del controlador al enviar mi formulario.

 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)

En el RestController ahora puedo simplemente hacer lo siguiente: (Suponiendo que tengamos un 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 } 

Tenga en cuenta que no estoy usando parámetros de solicitud, sino solo dejando que la spring haga la conversión de JSON a POJO / DTO. Asegúrese de configurar correctamente el bean MultiPartResolver y agregarlo a su pom.xml. (Y Jackson-Mapper si es necesario)

spring-context.xml

     

pom.xml

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

Puedes intentar esto

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

Eso está basado en https://github.com/murygin/rest-document-archive

Hay un buen ejemplo de carga de archivos https://murygin.wordpress.com/2014/10/13/rest-web-service-file-uploads-spring-boot/