2010-11-07 17 views
34

Sto lavorando con Spring 3 e RestTemplate. Ho fondamentalmente, due applicazioni e una di esse deve inviare valori all'altra app. attraverso il modello di riposo.Invio di file multipart come parametri POST con richieste di RestTemplate

Quando i valori da caricare sono stringhe, funziona perfettamente, ma quando devo inserire parametri misti e complessi (come MultipartFiles) ottengo un'eccezione del convertitore.

Come esempio, ho questo:

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

Sull'altro lato ... ho un'altra applicazione web (App2) che riceve i parametri dal 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"; 
} 

La mia applicazione-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> 

Ecco la pila di eccezione che sto ottenendo quando faccio il 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) 

Quindi le mie domande sono:

  1. È possibile inviare MultipartFile tramite RestTemplate utilizzando POST?
  2. Esistono alcuni convertitori specifici che devo utilizzare per inviare questo tipo di oggetti? Voglio dire c'è qualche MultipartFileHttpMessageConverter da usare nella mia configurazione?
+4

Tsk tsk, bestemmiare codice? Spero che non entri in produzione. – Spedge

risposta

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

La logica di questa risposta non è esattamente uguale a quella che sta facendo l'op? Ottengo lo stesso errore che ottiene se prendo questa risposta e semplicemente lo incollo nel mio codice al posto della mia richiesta. Penso che la vera domanda qui è perché 'non è stato trovato HttpMessageConverter adatto per richiesta' quando si utilizza un LinkedMultiValueMap per alcuni utenti e non per altri? C'è una biblioteca che ha bisogno di collegamenti? – deepwinter

14

Ho anche incontrato lo stesso problema, l'altro giorno. La ricerca su Google mi ha portato qui e in molti altri posti, ma nessuno ha dato la soluzione a questo problema. Ho finito per salvare il file caricato (MultiPartFile) come file tmp, quindi utilizzare FileSystemResource per caricarlo tramite RestTemplate. Ecco il codice che 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(); 

Sto ancora cercando una soluzione più elegante a questo problema.

+0

Non sono chiaro sul motivo per cui stai ricevendo un file caricato, quindi lo sto caricando di nuovo, ma, supponendo che tu abbia una ragione valida per farlo, potresti usare il metodo MultipartFile.transferTo() (http://bit.ly/Pm6sPN) invece della chiamata FileOutputStream.write(), che è un po 'più pulita di come la stai facendo. Altrimenti, il vero trucco qui è l'uso di FileSystemResource. Questo è gestito da ResourceHttpMessageConverter. Altrimenti (e sono sorpreso che questo non esista) avresti bisogno di scrivere un convertitore specifico per la gestione di file multipart. –

+0

FileSystemResource funziona solo se conosciamo il percorso esatto del file. Quale non è realizzabile, a causa di problemi di sicurezza del browser. – user754657

3

Uno dei nostri ragazzi fa qualcosa di simile con il file system . provare

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

assumendo l'uscita del .getFile è un oggetto Java file, che dovrebbe funzionare lo stesso come la nostra, che ha appena un parametro file.

40

Un modo per risolvere questo problema senza utilizzare un FileSystemResource che richiede un file su disco, consiste nell'utilizzare un oggetto ByteArrayResource, in questo modo è possibile inviare un array di byte nel post (questo codice funziona 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); 

sovrascrivo la getFilename del ByteArrayResource, perché se non lo faccio ottengo un'eccezione di puntatore nullo (a quanto pare dipende dal fatto che l'attivazione Java .jar è sul classpath, se si tratta di , utilizzerà il nome del file per provare a determinare il tipo di contenuto)

+3

Qual è il tipo di dati di "contenuto"? – user754657

+0

@Luxspes come è possibile sovrascrivere il metodo finale della classe ByteArrayResource? Questo non è possibile. – xyz

+1

@xyz il metodo getFilename non è definitivo: http://docs.spring.io/autorepo/docs/spring/3.2.3.RELEASE/javadoc-api/org/springframework/core/io/AbstractResource.html#getFilename () – Luxspes

7

Recentemente ho faticato con questo problema, mi ci sono voluti 3 giorni per capirlo. L'errore di richiesta errata potrebbe non essere causato da come il client sta inviando la richiesta ma perché il server non è configurato per gestire richieste multipart. Il suo è quello che ho dovuto fare per farlo funzionare:

pom.xml - Aggiunto commons-fileupload dipendenza (scaricare e aggiungere il vaso per il vostro progetto se il vostro non si utilizza la gestione delle dipendenze, come Maven)

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

web.xml - Aggiungi filtro multipart e mappatura

<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 - Aggiungi resolver multipart

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

Il Controller

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

Il suo 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; 
} 

Questo dovrebbe fare il trucco. Ho aggiunto quante più informazioni possibili perché ho passato giorni, mettendo insieme frammenti di tutto il problema, spero che questo possa essere d'aiuto.

1

Se devi inviare un file multipart che è composto, tra le altre cose, da un oggetto che deve essere convertito con uno specifico HttpMessageConverter e ottieni l'errore "HttpMessageConverter" non adatto, non importa quello che provi, puoi vuole provare con questo:

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

questo risolto il problema per me con un oggetto personalizzato che, insieme a un file (instanceof FileSystemResource, nel mio caso), era parte del file multipart avevo bisogno di inviare. Ho provato con la soluzione di TrueGuidance (e molti altri trovati in tutto il web) senza alcun risultato, poi ho guardato il codice sorgente di FormHttpMessageConverter e ho provato questo.

0

È necessario aggiungere FormHttpMessageConverter al file applicationContext.xml per poter pubblicare file multipart.

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

Vedere http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html per esempi.

3

Si può semplicemente utilizzare MultipartHttpServletRequest

Esempio:

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

Quando i parametri nomi request.getParameter() deve essere lo stesso con i corrispondenti nomi frontend.

nota, il file estratto tramite getFile() mentre gli altri parametri aggiuntivi estratti tramite getParameter()

2

ho dovuto fare la stessa cosa che ha fatto @Luxspes above..and Sto usando Primavera 4.2.6. Trascorso un po 'di tempo a capire perché ByteArrayResource viene trasferito da un client all'altro, ma il server non lo riconosce.

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