web-dev-qa-db-ja.com

Multipart FileをPOST RestTemplateリクエストのパラメーターとして送信する

Spring 3とRestTemplateを使用しています。基本的に、2つのアプリケーションと1つのアプリケーションが他のアプリケーションに値を投稿する必要があります。残りのテンプレートを通して。

投稿する値が文字列の場合、それは完璧に機能しますが、混合した複雑なパラメーター(MultipartFilesなど)を投稿する必要がある場合、コンバーターの例外が発生します。

例として、私はこれを持っています:

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

反対側には... App1からパラメーターを受け取る別のWebアプリケーション(App2)があります。

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

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

ここに、RestTemplateのpostForObjectを実行したときに得られる例外のスタックがあります...

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)

だから私の質問は:

  1. POSTを使用してRestTemplateを介してMultipartFileを送信することは可能ですか?
  2. このタイプのオブジェクトを送信するために使用する必要がある特定のコンバーターはありますか?つまり、構成で使用するMultipartFileHttpMessageConverterはありますか?
37
Mauro Monti

ディスク上のファイルを必要とするFileSystemResourceを使用せずにこれを解決する方法は、ByteArrayResourceを使用することです。これにより、投稿でバイト配列を送信できます(このコードは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);

ByteArrayResourceのgetFilenameをオーバーライドします。nullポインター例外を取得しない場合(明らかに、Javaアクティベーション.jarがクラスパス上にあるかどうかによって異なります。コンテンツタイプを判別しようとするファイル名)

56
Luxspes

先日も同じ問題に遭遇しました。 Google検索でここと他のいくつかの場所に行きましたが、この問題の解決策はありませんでした。アップロードしたファイル(MultiPartFile)をtmpファイルとして保存し、FileSystemResourceを使用してRestTemplate経由でアップロードしました。私が使用するコードは次のとおりです。

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

この問題に対するよりエレガントな解決策を探しています。

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

私は最近3日間この問題に苦労しました。クライアントが要求を送信する方法が原因ではない場合があり、サーバーがマルチパート要求を処理するように構成されていない場合があります。これは私がそれを機能させるためにしなければならなかったことです:

pom.xml-commons-fileupload依存関係を追加しました(Mavenなどの依存関係管理を使用していない場合は、jarをダウンロードしてプロジェクトに追加します)

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

web.xml-マルチパートフィルターとマッピングの追加

<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-マルチパートリゾルバーの追加

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

あなたのコントローラー

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

あなたのクライアント

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

これでうまくいくはずです。何日もかけて問題全体を細かくつなぎ合わせたため、できるだけ多くの情報を追加しました。これが役立つことを願っています。

7
TrueCoke

私たちの仲間の1人がfilesystemresourceと同様のことをしています。試してみる

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

。getFileの出力がJava Fileオブジェクトであると仮定します。これは、Fileパラメーターのみを持つ、私たちのものと同じように動作するはずです。

5
Alan

単にMultipartHttpServletRequestを使用できます

例:

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

request.getParameter()のパラメーター名は、対応するフロントエンド名と同じでなければなりません。

getFile()を介して抽出されたファイルと、getParameter()を介して抽出されたその他の追加パラメーターに注意してください。

3
Baurzhan

@Luxspesが上でやったのと同じことをしなければなりませんでした。そして、Spring 4.2.6を使用しています。 ByteArrayResourceがクライアントからサーバーに転送される理由を理解するのにかなりの時間を費やしましたが、サーバーはそれを認識していません。

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

特に、特定のHttpMessageConverterで変換する必要のあるオブジェクトで構成されるマルチパートファイルを送信する必要があり、何を試しても「適切なHttpMessageConverterなし」エラーが発生する場合は、試してみてください。この:

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

これにより、ファイル(私の場合はFileSystemResourceのインスタンス)とともに、送信する必要のあるマルチパートファイルの一部であるカスタムオブジェクトを使用して問題を解決しました。 TrueGuidance のソリューション(およびWebで見つかった他の多くのソリューション)を試してみましたが、FormHttpMessageConverterのソースコードを見て試してみました。

1
nonzaprej

私はこの問題を抱えており、ByteArrayResourceを使用するよりもはるかに簡単なソリューションを見つけました。

単にやる

public void loadInvoices(MultipartFile invoices, String channel) throws IOException {

    init();

    Resource invoicesResource = invoices.getResource();

    LinkedMultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
    parts.add("file", invoicesResource);

    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
    httpHeaders.set("channel", channel);

    HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(parts, httpHeaders);

    String url = String.format("%s/rest/inbound/invoices/upload", baseUrl);

    restTemplate.postForEntity(url, httpEntity, JobData.class);
}

それは機能し、ファイルシステムやバイト配列をいじることはありません。

0
Rory Lynch

マルチパートファイルを投稿できるようにするには、applicationContext.xmlにFormHttpMessageConverterを追加する必要があります。

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

例については、 http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html を参照してください。

0
Marcel Panse