Spring MVC – AngularJS – Upload de Arquivo – org.apache.commons.fileupload.FileUploadException

Eu tenho um aplicativo da Web Java Spring MVC como servidor. E aplicativo baseado em AngularJS como cliente.

No AngularJS, eu tenho que carregar um arquivo e enviar para o servidor.

Aqui está o meu html

     

Aqui está o meu 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); }); }; }); 

Ele está indo para o servidor. Aqui está o meu 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 eu executo isso, recebo a seguinte exceção

 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) 

No meu applicationContext.xml , mencionei

    

estou usando

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

Como resolver isso?

Seria ótimo se alguém me dissesse como enviar arquivos e outros formdata do angularJS e obtê-los no servidor.

ATUALIZAÇÃO 1

@Michael: Eu posso ver isso apenas no console, quando clico em 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 

Meu servidor está sendo executado em outra porta 8080. Estou usando o yeoman, o grunhido e o bower. Então thin gruntfile.js eu mencionei a porta do servidor. Então vai para o servidor e rodando isso e lança a exceção

ATUALIZAÇÃO 2

O limite não está estabelecendo

 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 

Eu enfrentei o mesmo problema e encontrei o mesmo problema mesmo depois de atualizar o transformRequest. ‘Alguns como, o limite do header não parece ter definido corretamente.

Após http://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs , o problema é resolvido. Extrair do local ….

Definindo ‘Content-Type’: undefined, o navegador define o Content-Type para multipart / form-data para nós e preenche o limite correto. Definição manual de ‘Tipo de conteúdo’: os dados de várias partes / formulário não preencherão o parâmetro de limite da solicitação.

Não tenho certeza se isso ajuda alguém, mas talvez seja mais fácil para as pessoas que estão olhando para este post … Pelo menos, isso torna menos difícil.

Introdução

Eu tive o mesmo problema e encontrei uma solução completa para enviar o json e o arquivo da página baseada em angular para um método Spring MVC.

O principal problema é o $ http, que não envia o header do tipo Content (explicarei o porquê).

A teoria sobre multipart / form-data

Para enviar o json e o arquivo, precisamos enviar um multipart / form-data, que significa “enviamos itens diferentes no corpo separados por um separador especial”. Esse separador especial é chamado de “limite”, que é uma string que não está presente em nenhum dos elementos que serão enviados.

O servidor precisa saber qual limite está sendo usado, portanto ele deve ser indicado no header Content-type (tipo de conteúdo multipart / form-data; boundary = $ the_boundary_used).

Então … duas coisas são necessárias:

  1. No header -> indicar multipart / form-data E qual limite é usado (aqui é onde $ http falha)
  2. No corpo -> separe cada parâmetro de solicitação com o limite

Exemplo de um bom pedido:

header

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

Que está dizendo ao servidor “Eu envio uma mensagem multiparte com o próximo separador (limite): ————————— 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 -- 

Para onde estamos enviando 2 argumentos, “clientInfo” e “file” separados pelo limite.

O problema

Se o pedido for enviado com $ http, o limite não será enviado no header (ponto 1), portanto, o Spring não poderá processar os dados (não sabe como dividir as “partes” do pedido).

O outro problema é que o limite é conhecido apenas pelo FormData … mas o FormData não tem nenhum acessório, então é impossível saber qual limite está sendo usado !!!

A solução

Em vez de usar $ http em js você deve usar XMLHttpRequest padrão, algo 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); 

Então, no seu método Spring você pode ter 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; } 

A resposta de Carlos Verdes não funcionou com o meu interceptor $ http, que adiciona headers de autorização e assim por diante. Então eu decidi adicionar à sua solução e criar o meu usando $ http .

Cliente Angular (1.3.15)

Meu formulário (usando a syntax controllerAs ) está assumindo um arquivo e um object simples contendo as informações que precisamos enviar para o servidor. Neste caso, estou usando um nome simples e digite a propriedade String .

 

O primeiro passo foi criar uma diretiva que liga meu arquivo ao escopo do controlador designado (neste caso, myController) para que eu possa acessá-lo. Vinculá-lo diretamente a um modelo em seu controlador não funcionará como o tipo de input = arquivo não é um recurso interno.

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

Em segundo lugar, criei uma fábrica chamada myObject com uma criação de método de instância que me permite transformar os dados ao invocar a criação no servidor. Esse método adiciona tudo a um object FormData e o converte usando o método transformRequest ( angular.identity ). É crucial definir seu header como indefinido . (Versões Angulares mais antigas podem exigir que algo indefinido seja definido). Isso permitirá que o marcador multidata / limite seja configurado automaticamente (veja a postagem 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 } }); } 

Tudo o que resta para fazer o lado do cliente é instanciar um novo myObject no myController e invocar o método de criação na function de criação do controlador ao enviar meu formulário.

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

spring Serverside (4,0)

No RestController agora posso simplesmente fazer o seguinte: (Supondo que temos um 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 } 

Observe, eu não estou usando requestparameters mas apenas deixando a primavera fazer a conversão de JSON para POJO / DTO. Certifique-se de ter o bean MultiPartResolver configurado corretamente e adicionado ao seu pom.xml. (E Jackson-Mapper, se necessário)

spring-context.xml

     

pom.xml

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

Você pode tentar isso

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

Isso é baseado em https://github.com/murygin/rest-document-archive

Há um bom exemplo de upload de arquivo https://murygin.wordpress.com/2014/10/13/rest-web-service-file-uploads-spring-boot/