Отправка файла Multipart в виде параметров POST с помощью запросов RestTemplate
Я работаю с Spring 3 и RestTemplate. У меня есть в основном два приложения, и один из них должен публиковать значения в другом приложении. через шаблон отдыха.
Когда значения для публикации являются строками, они работают отлично, но когда мне приходится публиковать смешанные и сложные параметры (например, 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";
}
С другой стороны... у меня есть другое веб-приложение (App2), которое получает параметры из 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";
}
Мое приложение-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>
Вот стек исключения, который я получаю, когда я делаю postForObject 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)
Итак, мои вопросы:
- Можно ли отправить MultipartFile через RestTemplate с помощью POST?
- Есть ли некоторые конкретные конвертеры, которые я должен использовать для отправки объектов такого типа? Я имею в виду, есть ли MultipartFileHttpMessageConverter для использования в моей конфигурации?
Ответы
Ответ 1
Способом решения проблемы без использования 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);
Я переопределяю getFilename для ByteArrayResource, потому что, если я этого не делаю, я получаю исключение из null-указателя (видимо, это зависит от того, является ли java-активация .jar на пути к классам, если это так, он будет использовать имя файла, чтобы попробовать для определения типа содержимого)
Ответ 2
Я также столкнулся с тем же вопросом на днях. Поиск 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();
Я все еще ищу более элегантное решение этой проблемы.
Ответ 3
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);
Ответ 4
Недавно я боролся с этой проблемой, взял меня близко к 3 дням, чтобы понять это. Ошибка неправильного запроса может быть не вызвана тем, как клиент отправляет запрос, а потому, что сервер не настроен для обработки многочастных запросов. Его - то, что я должен был сделать, чтобы заставить его работать:
pom.xml - Добавлена зависимость от shareons-fileupload (загрузите и добавьте банку в свой проект, если вы не используете управление зависимостями, такое как maven)
<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;
}
Это должно сделать трюк. Я добавил как можно больше информации, потому что я провел дни, собирая кусочки полной проблемы, надеюсь, это поможет.
Ответ 5
Один из наших ребят делает что-то подобное с файловой системой.
попробуйте
mvm.add("file", new FileSystemResource(pUploadDTO.getFile()));
Предполагая, что вывод вашего .getFile - это объект Java файла, который должен работать так же, как и наш, который
просто имеет параметр файла.
Ответ 6
Вы можете просто использовать 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()
Ответ 7
Если вам нужно отправить многостраничный файл, который, среди прочего, состоит из объекта, который необходимо преобразовать с помощью специального 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);
Это решило проблему для меня с пользовательским объектом, который вместе с файлом (instanceof FileSystemResource, в моем случае) был частью многостраничного файла, который мне нужно отправить.
Я попытался с TrueGuidance решением (и многими другими, найденными в Интернете) безрезультатно, затем я посмотрел исходный код FormHttpMessageConverter и попробовал это.
Ответ 8
Мне пришлось сделать то же самое, что и @Luxspes, сделанное выше... и я использую Spring 4.2.6. Провел довольно некоторое время, выяснив, почему ByteArrayResource получает передачу с клиента на сервер, но сервер не распознает его.
ByteArrayResource contentsAsResource = new ByteArrayResource(byteArr){
@Override
public String getFilename(){
return filename;
}
};
Ответ 9
Вы должны добавить FormHttpMessageConverter в свой applicationContext.xml, чтобы отправлять многостраничные файлы.
<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 для примеров.