Envoyer un fichier multipartie comme paramètres de POST avec les requêtes RestTemplate

je travaille avec Spring 3 et RestTemplate. J'ai essentiellement, deux applications et l'un d'eux aux valeurs post à l'autre application. par le repos du modèle.

quand les valeurs à afficher sont des chaînes, c'est parfait, mais quand je dois poster des params mixtes et complexes (comme des MultipartFiles) j'obtiens une exception de convertisseur.

comme exemple, j'ai ceci:

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

de l'autre côté... j'ai une autre application web (App2) qui reçoit les paramètres de L'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";
}

mon application-contexte.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>  

Voici la pile de l'exception que je reçois quand je fais le postForObject du 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)

Donc mes questions sont:

  1. Est-il possible d'envoyer MultipartFile par RestTemplate l'aide de POST?
  2. y a-t-il certains convertisseurs que j'ai utiliser pour envoyer ce type d'objets? Je veux dire, c'est qu'il y a certaines MultipartFileHttpMessageConverter à utiliser dans ma configuration?
37
demandé sur DJ. 2010-11-07 19:54:22

9 réponses

une façon de résoudre ce problème sans avoir besoin d'utiliser une ressource filesystem qui nécessite un fichier sur le disque, est d'utiliser une ressource ByteArray, de cette façon vous pouvez envoyer un tableau byte dans votre message (ce code fonctionne avec le printemps 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);

j'annule le nom getFilename de la ressource ByteArrayResource parce que si Je ne le fais pas, j'obtiens une exception de pointeur null (apparemment cela dépend si le activation java .jar est sur le chemin de classe, s'il l'est, il va utiliser le nom du fichier pour essayer de déterminer le type de contenu)

49
répondu Luxspes 2013-10-01 05:09:25

j'ai aussi rencontré le même problème l'autre jour. Google search m'a amené ici et plusieurs autres endroits, mais aucun n'a donné la solution à ce problème. J'ai fini par sauvegarder le fichier téléchargé (MultiPartFile) en tant que Fichier tmp, puis j'ai utilisé FileSystemResource pour le télécharger via RestTemplate. Voici le code que j'utilise,

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

je suis toujours à la recherche d'une solution plus élégante à ce problème.

14
répondu barryku 2011-05-22 22:57:05
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);
9
répondu signonsridhar 2013-04-26 14:26:35

j'ai récemment lutté avec ce numéro pendant 3 jours. La façon dont le client envoie la requête peut ne pas être la cause, le serveur peut ne pas être configuré pour traiter les requêtes multiparties. C'est ce que j'ai dû faire pour que ça marche:

pom.xml-ajouté commons-FileUpload dependency (téléchargez et ajoutez le jar à votre projet si vous n'utilisez pas la gestion de dépendances telle que maven)

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

web.xml - ajouter un filtre multipart et mapping

<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-contexte.xml - Ajouter multipart de résolution

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

Votre Contrôleur

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

votre client

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

cela devrait faire l'affaire. J'ai ajouté autant d'informations que possible, car j'ai passé des jours, rassemblant des morceaux de la totalité du problème, j'espère que cela aidera.

7
répondu TrueCoke 2018-09-24 22:04:58

un de nos gars fait quelque chose de similaire avec le filesystemresource. essayez

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

en supposant la sortie de votre .getFile est un objet de fichier java, qui devrait fonctionner comme le nôtre, ce qui a juste un Fichier de paramètre.

3
répondu Alan 2013-05-03 08:31:20

vous pouvez simplement utiliser MultipartHttpServletRequest

Exemple:

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

Où les noms des paramètres dans request.getParameter() doit être identique avec les noms de frontend correspondants.

notez, ce fichier extrait via getFile() alors que d'autres paramètres supplémentaires sont extraits via getParameter()

3
répondu Baurzhan 2015-09-21 12:42:08

j'ai dû faire la même chose que @Luxspes a fait ci-dessus..et J'utilise le ressort 4.2.6. J'ai passé pas mal de temps à comprendre pourquoi ByteArrayResource est transféré d'un client à un serveur, mais le serveur ne le reconnaît pas.

ByteArrayResource contentsAsResource = new ByteArrayResource(byteArr){
            @Override
            public String getFilename(){
                return filename;
            }
        };
2
répondu mvasa 2016-11-04 21:28:00

si vous devez envoyer un fichier multipart qui est composé, entre autres choses, par un objet qui doit être converti avec un HttpMessageConverter spécifique et vous obtenez l'erreur "no suitable HttpMessageConverter" peu importe ce que vous essayez, vous pouvez vouloir essayer avec ceci:

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

cela a résolu le problème pour moi avec un objet personnalisé qui, avec un fichier (instanceof FileSystemResource, dans mon cas), faisait partie du fichier multipart que j'avais besoin d'envoyer. J'ai essayé avec TrueGuidance's solution (et beaucoup d'autres trouvées sur le web) en vain, alors j'ai regardé FormHttpMessageConverter du code source et j'ai essayé cette.

1
répondu nonzaprej 2017-05-23 10:31:32

vous devez ajouter le FormHttpMessageConverter à votre applicationContext.xml pour pouvoir poster des fichiers en plusieurs parties.

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

voir http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html pour des exemples.

0
répondu Marcel Panse 2014-09-16 13:14:57