Invio di file multipart come parametri POST con richieste di RestTemplate

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

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

Ad 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 mvm = new LinkedMultiValueMap(); mvm.add("param1", "TestParameter"); mvm.add("file", pUploadDTO.getFile()); // MultipartFile Map result = restTemplate.postForObject(uri, mvm, Map.class); return "redirect:postupload"; } 

Dall’altra parte … ho un’altra applicazione web (App2) che riceve i parametri dall’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 application-context.xml:

             104857600   4096   

Ecco la pila dell’eccezione che ottengo 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. È ansible inviare MultipartFile tramite RestTemplate usando POST?
  2. Ci sono alcuni convertitori specifici che devo usare per inviare questo tipo di oggetti? Voglio dire c’è qualche MultipartFileHttpMessageConverter da usare nella mia configurazione?

Un modo per risolvere questo problema senza utilizzare un FileSystemResource che richiede un file su disco, consiste nell’utilizzare un object ByteArrayResource, in questo modo è ansible inviare un array di byte nel proprio post (questo codice funziona con Spring 3.2.3):

 MultiValueMap map = new LinkedMultiValueMap(); 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 getFilename di ByteArrayResource perché se non ottengo un’eccezione di puntatore nullo (a quanto pare dipende dall’triggerszione java .jar sul classpath, se lo è, utilizzerà il nome del file per provare a determinare il tipo di contenuto)

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.

 MultiValueMap parts = new LinkedMultiValueMap(); 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("")); parts.add("xml", xml); template.postForLocation("http://example.com/multipart", parts); 

Recentemente ho faticato con questo problema, mi ha portato vicino a 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 dovevo fare per farlo funzionare:

pom.xml – Aggiunta la dipendenza commons-fileupload (scarica e aggiungi il jar al tuo progetto se non stai utilizzando la gestione delle dipendenze come ad esempio Maven)

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

web.xml – Aggiungi filtro e mapping multiparte

  multipartFilter org.springframework.web.multipart.support.MultipartFilter   multipartFilter /springrest/*  

app-context.xml – Aggiungi resolver multipart

   10000000   

Il tuo 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 tuo 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 parts = new LinkedMultiValueMap(); 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.

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

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

supponendo che l’output di .getFile sia un object File java, dovrebbe funzionare come il nostro, che ha solo un parametro File.

Puoi semplicemente usare 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; } 

Dove i nomi dei parametri in request.getParameter() devono essere uguali ai corrispondenti nomi frontend.

Nota, quel file estratto tramite getFile() mentre altri parametri aggiuntivi sono stati estratti tramite getParameter()

Ho dovuto fare la stessa cosa che @Luxspes ha fatto sopra..e sto usando Spring 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; } }; 

Se devi inviare un file multipart che è composto, tra le altre cose, da un object che deve essere convertito con uno specifico HttpMessageConverter e ottieni l’errore “HttpMessageConverter” non adatto, non importa quello che provi, potresti 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 mi ha risolto il problema con un object personalizzato che, insieme a un file (instanceof FileSystemResource, nel mio caso), faceva parte del file multipart che dovevo inviare. Ho provato con la soluzione di TrueGuidance (e molti altri trovati nel web) senza alcun risultato, poi ho guardato il codice sorgente di FormHttpMessageConverter e ho provato questo.

Devi aggiungere FormHttpMessageConverter al tuo applicationContext.xml per poter pubblicare file multipart.

         

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