2010-11-07 11 views
34

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.Envío de archivos de varias partes como parámetros POST con solicitudes RestTemplate

Cuando los valores a publicar son cadenas, funciona 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 App1 .

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 aplicación 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 de que estoy consiguiendo cuando hago la postForObject del 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) 

Así que mis preguntas son:

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

Tsk tsk, jurar en el código? Espero que no entre en producción. – Spedge

Respuesta

9
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); 
+1

¿No es la lógica de esta respuesta exactamente la misma que lo que está haciendo la operación? Obtengo el mismo error que obtiene si tomo esta respuesta y solo la pego en mi código en lugar de mi propia solicitud. Creo que la verdadera pregunta aquí es por qué 'no se encuentra HttpMessageConverter adecuado para la solicitud' cuando se utiliza un LinkedMultiValueMap para algunos usuarios, y no para otros? ¿Hay una biblioteca que necesita vincularse? – deepwinter

14

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.

+0

No tengo claro el motivo por el que está recibiendo un archivo cargado y luego lo vuelvo a cargar, pero, suponiendo que tenga una razón válida para hacerlo, podría usar el método MultipartFile.transferTo() (http://bit.ly)/Pm6sPN) en lugar de la llamada FileOutputStream.write(), que es un poco más limpia que la forma en que lo está haciendo. De lo contrario, el verdadero truco aquí es el uso de FileSystemResource. Eso es manejado por ResourceHttpMessageConverter. De lo contrario (y me sorprende que esto no exista), necesitarás escribir un convertidor específicamente para el manejo de archivos de varias partes. –

+0

FileSystemResource funciona solo, si conocemos la ruta exacta del archivo. Lo cual no es posible debido a problemas de seguridad del navegador. – user754657

3

Uno de nuestros chicos hace algo similar con el filesystemresource. tratar

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

asumiendo la salida de su .getFile es un objeto Java Archive, que debería funcionar de la misma que la nuestra, que solo tiene un parámetro del archivo.

40

Una manera de resolver esto sin la necesidad de utilizar un FileSystemResource que requiera un archivo en el disco, es usar 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); 

puedo reemplazar el getFilename del ByteArrayResource porque si no lo hago me sale una excepción de puntero nulo (al parecer depende de si la activación de Java .jar está en la ruta de clases, si es , utilizará el nombre del archivo para tratar de determinar el tipo de contenido)

+3

¿Cuál es el tipo de datos de "contenido"? – user754657

+0

@Luxspes ¿cómo se puede anular el método final de ByteArrayResource Class? Esto no es posible. – xyz

+1

@xyz el método getFilename no es definitivo: http://docs.spring.io/autorepo/docs/spring/3.2.3.RELEASE/javadoc-api/org/springframework/core/io/AbstractResource.html#getFilename () – Luxspes

7

Recientemente luché 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. Su es lo que tenía que hacer para que funcione:

pom.xml - Se ha añadido Commons-fileupload dependencia (descarga y añadir el frasco a su proyecto si no están utilizando la gestión de la dependencia como experto)

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

web.xml - Añadir filtro de varias partes y mapeo

<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 - Añadir 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 
} 

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

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

1

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, puede quiere probar con esto:

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 resolvió el problema para mí con un objeto personalizado que, junto con un archivo (instanceof FileSystemResource, en mi caso), era parte del archivo de varias partes que necesitaba para enviar. Intenté con la solución TrueGuidance (y muchas otras encontradas en la web) sin éxito, luego miré el código fuente de FormHttpMessageConverter y lo intenté.

0

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> 

Ver ejemplos de http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html.

3

Usted puede simplemente utilizar 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 parámetros nombres en request.getParameter() debe ser la misma con los nombres frontend correspondiente.

Nota, ese archivo se extrae a través de getFile() mientras que otros parámetros adicionales extraídos a través de getParameter()

2

que tenía que hacer lo mismo que hicieron @Luxspes above..and estoy usando primavera 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; 
      } 
     }; 
Cuestiones relacionadas