mvc example ejemplo spring-mvc multipartform-data resttemplate

spring-mvc - example - @modelattribute ejemplo



Envío de archivos de varias partes como parámetros POST con solicitudes RestTemplate (9)

Estoy trabajando con Spring 3 y RestTemplate. Básicamente, tengo dos aplicaciones y una de ellas tiene que publicar valores en la otra aplicación. a través de la plantilla de descanso.

Cuando los valores a publicar son cadenas, es perfecto, pero cuando tengo que publicar params mixtos y complejos (como MultipartFiles) obtengo una excepción de convertidor.

Como ejemplo, tengo esto:

App1 - PostController:

@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute UploadDTO pUploadDTO, BindingResult pResult) throws URISyntaxException, IOException { URI uri = new URI("http://localhost:8080/app2/file/receiver"); MultiValueMap<String, Object> mvm = new LinkedMultiValueMap<String, Object>(); mvm.add("param1", "TestParameter"); mvm.add("file", pUploadDTO.getFile()); // MultipartFile Map result = restTemplate.postForObject(uri, mvm, Map.class); return "redirect:postupload"; }

Por otro lado ... tengo otra aplicación web (App2) que recibe los parámetros de la aplicación1.

App2 - ReceiverController

@RequestMapping(value = "/receiver", method = { RequestMethod.POST }) public String processUploadFile( @RequestParam(value = "param1") String param1, @RequestParam(value = "file") MultipartFile file) { if (file == null) { System.out.println("Shit!... is null"); } else { System.out.println("Yes!... work done!"); } return "redirect:postupload"; }

Mi application-context.xml:

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" /> <bean class="org.springframework.http.converter.FormHttpMessageConverter" /> <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" /> </list> </property> </bean> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize"> <value>104857600</value> </property> <property name="maxInMemorySize"> <value>4096</value> </property> </bean>

Aquí está la pila de la excepción que recibo cuando hago el postForObject de RestTemplate ...

org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.web.multipart.commons.CommonsMultipartFile] at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:292) at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:252) at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:242) at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:194) at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:1) at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:588) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:436) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:415) at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:294) at com.yoostar.admintool.web.UploadTestController.create(UploadTestController.java:86) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:175) at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:421) at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:409) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:774) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560) at javax.servlet.http.HttpServlet.service(HttpServlet.java:637) at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489) at java.lang.Thread.run(Thread.java:619)

Entonces mis preguntas son:

  1. ¿Es posible enviar MultipartFile a través de RestTemplate utilizando POST?
  2. ¿Hay algunos convertidores específicos que tengo que usar para enviar este tipo de objetos? Quiero decir, ¿hay algún MultipartFileHttpMessageConverter para usar en mi configuración?

Debe agregar FormHttpMessageConverter a su applicationContext.xml para poder publicar archivos de varias partes.

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> <bean class="org.springframework.http.converter.FormHttpMessageConverter" /> </list> </property> </bean>

Consulte http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html para ver ejemplos.


Hace poco tuve problemas con este problema, me tomó cerca de 3 días para resolverlo. El error de solicitud incorrecta puede no estar causado por la forma en que el cliente envía la solicitud, sino porque el servidor no está configurado para manejar solicitudes de varias partes. Lo suyo es lo que tengo que hacer para que funcione:

pom.xml - Se agregó la dependencia commons-fileupload (descarga y agrega el jar a tu proyecto si no estás usando la administración de dependencias como maven)

<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>${commons-version}</version> </dependency>

web.xml - Agregar filtro y mapeo multiparte

<filter> <filter-name>multipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> </filter> <filter-mapping> <filter-name>multipartFilter</filter-name> <url-pattern>/springrest/*</url-pattern> </filter-mapping>

app-context.xml - Agregar resolución de varias partes

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

Su controlador

@RequestMapping(value=Constants.REQUEST_MAPPING_ADD_IMAGE, method = RequestMethod.POST, produces = { "application/json"}) public @ResponseBody boolean saveStationImage( @RequestParam(value = Constants.MONGO_STATION_PROFILE_IMAGE_FILE) MultipartFile file, @RequestParam(value = Constants.MONGO_STATION_PROFILE_IMAGE_URI) String imageUri, @RequestParam(value = Constants.MONGO_STATION_PROFILE_IMAGE_TYPE) String imageType, @RequestParam(value = Constants.MONGO_FIELD_STATION_ID) String stationId) { // Do something with file // Return results }

Tu cliente

public static Boolean updateStationImage(StationImage stationImage) { if(stationImage == null) { Log.w(TAG + ":updateStationImage", "Station Image object is null, returning."); return null; } Log.d(TAG, "Uploading: " + stationImage.getImageUri()); try { RestTemplate restTemplate = new RestTemplate(); FormHttpMessageConverter formConverter = new FormHttpMessageConverter(); formConverter.setCharset(Charset.forName("UTF8")); restTemplate.getMessageConverters().add(formConverter); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setAccept(Collections.singletonList(MediaType.parseMediaType("application/json"))); MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>(); parts.add(Constants.STATION_PROFILE_IMAGE_FILE, new FileSystemResource(stationImage.getImageFile())); parts.add(Constants.STATION_PROFILE_IMAGE_URI, stationImage.getImageUri()); parts.add(Constants.STATION_PROFILE_IMAGE_TYPE, stationImage.getImageType()); parts.add(Constants.FIELD_STATION_ID, stationImage.getStationId()); return restTemplate.postForObject(Constants.REST_CLIENT_URL_ADD_IMAGE, parts, Boolean.class); } catch (Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); Log.e(TAG + ":addStationImage", sw.toString()); } return false; }

Eso debería hacer el truco. Agregué la mayor cantidad de información posible porque pasé días recopilando fragmentos del tema completo, espero que esto ayude.


Si tiene que enviar un archivo de varias partes compuesto, entre otras cosas, por un objeto que debe convertirse con un HttpMessageConverter específico y obtiene el error "no adecuado HttpMessageConverter" no importa lo que intente, es posible que desee intentar con esta:

RestTemplate restTemplate = new RestTemplate(); FormHttpMessageConverter converter = new FormHttpMessageConverter(); converter.addPartConverter(new TheRequiredHttpMessageConverter()); //for example, in my case it was "new MappingJackson2HttpMessageConverter()" restTemplate.getMessageConverters().add(converter);

Esto me solucionó el problema con un Objeto personalizado que, junto con un archivo (instancia de FileSystemResource, en mi caso), formaba parte del archivo de varias partes que necesitaba enviar. Intenté con la solución de TrueGuidance (y muchas otras encontradas en la web) en vano, luego miré el código fuente de FormHttpMessageConverter y lo intenté.


Simplemente puede usar MultipartHttpServletRequest

Ejemplo:

@RequestMapping(value={"/upload"}, method = RequestMethod.POST,produces = "text/html; charset=utf-8") @ResponseBody public String upload(MultipartHttpServletRequest request /*@RequestBody MultipartFile file*/){ String responseMessage = "OK"; MultipartFile file = request.getFile("file"); String param = request.getParameter("param"); try { System.out.println(file.getOriginalFilename()); System.out.println("some param = "+param); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8)); // read file } catch(Exception ex){ ex.printStackTrace(); responseMessage = "fail"; } return responseMessage; }

Donde los nombres de los parámetros en request.getParameter() deben ser los mismos con los nombres frontend correspondientes.

Tenga en cuenta que ese archivo se extrajo mediante getFile() mientras que otros parámetros adicionales se extrajeron a través de getParameter()


También me encontré con el mismo problema el otro día. La búsqueda de Google me consiguió aquí y en otros lugares, pero ninguno dio la solución a este problema. Terminé guardando el archivo cargado (MultiPartFile) como un archivo tmp, luego uso FileSystemResource para cargarlo a través de RestTemplate. Aquí está el código que uso,

String tempFileName = "/tmp/" + multiFile.getOriginalFileName(); FileOutputStream fo = new FileOutputStream(tempFileName); fo.write(asset.getBytes()); fo.close(); parts.add("file", new FileSystemResource(tempFileName)); String response = restTemplate.postForObject(uploadUrl, parts, String.class, authToken, path); //clean-up File f = new File(tempFileName); f.delete();

Todavía estoy buscando una solución más elegante a este problema.


Tuve que hacer lo mismo que @Luxspes hizo arriba ... y estoy usando Spring 4.2.6. Pasé bastante tiempo pensando por qué ByteArrayResource se transfiere de un cliente a otro, pero el servidor no lo reconoce.

ByteArrayResource contentsAsResource = new ByteArrayResource(byteArr){ @Override public String getFilename(){ return filename; } };


Una forma de resolver esto sin la necesidad de utilizar un FileSystemResource que requiera un archivo en el disco, es utilizar ByteArrayResource, de esa manera puede enviar una matriz de bytes en su publicación (este código funciona con Spring 3.2.3):

MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>(); final String filename="somefile.txt"; map.add("name", filename); map.add("filename", filename); ByteArrayResource contentsAsResource = new ByteArrayResource(content.getBytes("UTF-8")){ @Override public String getFilename(){ return filename; } }; map.add("file", contentsAsResource); String result = restTemplate.postForObject(urlForFacade, map, String.class);

Sobreescribo el getFilename de ByteArrayResource porque si no obtengo una excepción de puntero nulo (aparentemente depende de si el .jar de activación java está en el classpath, si es así, utilizará el nombre del archivo para tratar de determinar el tipo de contenido)


Uno de nuestros muchachos hace algo similar con el recurso del sistema de archivos . tratar

mvm.add("file", new FileSystemResource(pUploadDTO.getFile()));

suponiendo que el resultado de .getFile es un objeto de archivo java, debería funcionar igual que el nuestro, que solo tiene un parámetro de archivo.


MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>(); parts.add("name 1", "value 1"); parts.add("name 2", "value 2+1"); parts.add("name 2", "value 2+2"); Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); parts.add("logo", logo); Source xml = new StreamSource(new StringReader("<root><child/></root>")); parts.add("xml", xml); template.postForLocation("http://example.com/multipart", parts);