web-dev-qa-db-fra.com

Envoi d'un fichier multipart en tant que POST paramètres avec demandes RestTemplate

Je travaille avec Spring 3 et RestTemplate. En gros, j'ai deux applications et l'une d'elles doit publier des valeurs sur l'autre application. à travers le modèle de repos. 

Lorsque les valeurs à publier sont Strings, le travail est parfait, mais lorsque je dois publier des paramètres complexes et mixtes (comme MultipartFiles), je reçois une exception de conversion.

A titre d'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-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>  

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 via RestTemplate à l'aide de POST?
  2. Y a-t-il des convertisseurs spécifiques que je dois utiliser pour envoyer ce type d'objets? Je veux dire, y at-il desMultipartFileHttpMessageConverterà utiliser dans ma configuration?
37
Mauro Monti

Une façon de résoudre ce problème sans avoir besoin d'utiliser une FileSystemResource nécessitant un fichier sur disque est d'utiliser une ByteArrayResource, de cette façon vous pouvez envoyer un tableau d'octets dans votre message (ce code fonctionne avec 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);

Je remplace le getFilename de ByteArrayResource parce que si je ne reçois pas d'exception de pointeur null (apparemment cela dépend de l'activation de Java .jar se trouve sur le chemin de classe. Si c'est le cas, il utilisera le nom de fichier pour essayez de déterminer le type de contenu)

53
Luxspes

J'ai aussi rencontré le même problème l'autre jour. La recherche Google m'a amené ici et à plusieurs autres endroits, mais aucun d'entre eux n'a donné de solution à ce problème. J'ai fini par enregistrer le fichier téléchargé (MultiPartFile) en tant que fichier tmp, puis utiliser 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 cherche toujours une solution plus élégante à ce problème.

14
barryku
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);
8
signonsridhar

J'ai récemment lutté avec ce problème pendant 3 jours. La manière dont le client envoie la demande peut ne pas en être la cause, le serveur peut ne pas être configuré pour gérer les demandes en plusieurs parties. Voici ce que je devais faire pour que cela fonctionne:

pom.xml - Ajout de la dépendance commons-fileupload (téléchargez et ajoutez le fichier jar à votre projet si vous n'utilisez pas la gestion des 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 un mappage

<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 - Ajouter un résolveur en plusieurs parties

<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 parce que j'ai passé des jours à rassembler des morceaux du numéro complet, j'espère que cela vous aidera.

7
TrueCoke

Un de nos gars fait quelque chose de similaire avec filesystemresource . Try 

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

en supposant que la sortie de votre .getFile soit un objet de fichier Java, qui devrait fonctionner de la même façon que le nôtre, qui ne comporte qu'un paramètre File.

4
Alan

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 de paramètres dans request.getParameter() doivent être identiques aux noms des interfaces correspondantes.

Notez que ce fichier est extrait via getFile() alors que d'autres paramètres sont extraits via getParameter().

3
Baurzhan

Je devais faire la même chose que @Luxspes ci-dessus .. et j'utilise Spring 4.2.6. J'ai passé un bon moment à comprendre pourquoi ByteArrayResource était transféré d'un client à un autre, mais le serveur ne le reconnaissait pas. 

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

Si vous devez envoyer un fichier multipart composé, entre autres choses, par un objet devant être converti avec un convertisseur HttpMessageConverter spécifique et que vous obtenez l'erreur «no HttpMessageConverter», quoi que vous essayiez, vous pouvez essayer avec ce:

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 (instance of FileSystemResource, dans mon cas), faisait partie du fichier multipart que je devais envoyer . J'ai essayé avec la solution TrueGuidance (et de nombreux autres trouvés sur le Web) en vain, puis j’ai examiné le code source de FormHttpMessageConverter et l’ai essayé.

1
nonzaprej

Vous devez ajouter le FormHttpMessageConverter à votre applicationContext.xml pour pouvoir publier 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
Marcel Panse