Support multipart/* MediaTypes in RestTemplate
Prior to this commit, RestTemplate posted multipart with Content-Type
"multipart/form-data" even if the FormHttpMessageConverter configured
in the RestTemplate had been configured to support additional multipart
subtypes. This made it impossible to POST form data using a content
type such as "multipart/mixed" or "multipart/related".
This commit addresses this issue by updating FormHttpMessageConverter
to support custom multipart subtypes for writing form data.
For example, the following use case is now supported.
MediaType multipartMixed = new MediaType("multipart", "mixed");
restTemplate.getMessageConverters().stream()
.filter(FormHttpMessageConverter.class::isInstance)
.map(FormHttpMessageConverter.class::cast)
.findFirst()
.orElseThrow(() ->
new IllegalStateException("Failed to find FormHttpMessageConverter"))
.addSupportedMediaTypes(multipartMixed);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("field 1", "value 1");
parts.add("file", new ClassPathResource("myFile.jpg"));
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(multipartMixed);
HttpEntity<MultiValueMap<String, Object>> requestEntity =
new HttpEntity<>(parts, requestHeaders);
restTemplate.postForLocation("https://example.com/myFileUpload", requestEntity);
Closes gh-23159
This commit is contained in:
@@ -55,15 +55,29 @@ import org.springframework.util.StringUtils;
|
||||
* write (but not read) the {@code "multipart/form-data"} media type as
|
||||
* {@link MultiValueMap MultiValueMap<String, Object>}.
|
||||
*
|
||||
* <h3>Multipart Data</h3>
|
||||
*
|
||||
* <p>By default, {@code "multipart/form-data"} is used as the content type when
|
||||
* {@linkplain #write writing} multipart data. As of Spring Framework 5.2 it is
|
||||
* also possible to write multipart data using other multipart subtypes such as
|
||||
* {@code "multipart/mixed"} and {@code "multipart/related"}, as long as the
|
||||
* multipart subtype is registered as a {@linkplain #getSupportedMediaTypes
|
||||
* supported media type} <em>and</em> the desired multipart subtype is specified
|
||||
* as the content type when {@linkplain #write writing} the multipart data.
|
||||
*
|
||||
* <p>When writing multipart data, this converter uses other
|
||||
* {@link HttpMessageConverter HttpMessageConverters} to write the respective
|
||||
* MIME parts. By default, basic converters are registered (e.g., for {@code String}
|
||||
* and {@code Resource}). These can be overridden through the
|
||||
* {@link #setPartConverters partConverters} property.
|
||||
* MIME parts. By default, basic converters are registered for byte array,
|
||||
* {@code String}, and {@code Resource}. These can be overridden via
|
||||
* {@link #setPartConverters} or augmented via {@link #addPartConverter}.
|
||||
*
|
||||
* <h3>Examples</h3>
|
||||
*
|
||||
* <p>The following snippet shows how to submit an HTML form using the
|
||||
* {@code "multipart/form-data"} content type.
|
||||
*
|
||||
* <p>For example, the following snippet shows how to submit an HTML form:
|
||||
* <pre class="code">
|
||||
* RestTemplate template = new RestTemplate();
|
||||
* RestTemplate restTemplate = new RestTemplate();
|
||||
* // AllEncompassingFormHttpMessageConverter is configured by default
|
||||
*
|
||||
* MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
|
||||
@@ -71,17 +85,46 @@ import org.springframework.util.StringUtils;
|
||||
* form.add("field 2", "value 2");
|
||||
* form.add("field 2", "value 3");
|
||||
* form.add("field 3", 4); // non-String form values supported as of 5.1.4
|
||||
* template.postForLocation("https://example.com/myForm", form);
|
||||
* restTemplate.postForLocation("https://example.com/myForm", form);
|
||||
* </pre>
|
||||
*
|
||||
* <p>The following snippet shows how to do a file upload:
|
||||
* <p>The following snippet shows how to do a file upload using the
|
||||
* {@code "multipart/form-data"} content type.
|
||||
*
|
||||
* <pre class="code">
|
||||
* MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
* parts.add("field 1", "value 1");
|
||||
* parts.add("file", new ClassPathResource("myFile.jpg"));
|
||||
* template.postForLocation("https://example.com/myFileUpload", parts);
|
||||
* restTemplate.postForLocation("https://example.com/myFileUpload", parts);
|
||||
* </pre>
|
||||
*
|
||||
* <p>The following snippet shows how to do a file upload using the
|
||||
* {@code "multipart/mixed"} content type.
|
||||
*
|
||||
* <pre class="code">
|
||||
* MediaType multipartMixed = new MediaType("multipart", "mixed");
|
||||
*
|
||||
* restTemplate.getMessageConverters().stream()
|
||||
* .filter(FormHttpMessageConverter.class::isInstance)
|
||||
* .map(FormHttpMessageConverter.class::cast)
|
||||
* .findFirst()
|
||||
* .orElseThrow(() -> new IllegalStateException("Failed to find FormHttpMessageConverter"))
|
||||
* .addSupportedMediaTypes(multipartMixed);
|
||||
*
|
||||
* MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
* parts.add("field 1", "value 1");
|
||||
* parts.add("file", new ClassPathResource("myFile.jpg"));
|
||||
*
|
||||
* HttpHeaders requestHeaders = new HttpHeaders();
|
||||
* requestHeaders.setContentType(multipartMixed);
|
||||
* HttpEntity<MultiValueMap<String, Object>> requestEntity =
|
||||
* new HttpEntity<>(parts, requestHeaders);
|
||||
*
|
||||
* restTemplate.postForLocation("https://example.com/myFileUpload", requestEntity);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Miscellaneous</h3>
|
||||
*
|
||||
* <p>Some methods in this class were inspired by
|
||||
* {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}.
|
||||
*
|
||||
@@ -95,6 +138,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
|
||||
|
||||
private static final MediaType MULTIPART_ALL = new MediaType("multipart", "*");
|
||||
|
||||
/**
|
||||
* The default charset used by the converter.
|
||||
*/
|
||||
@@ -154,6 +199,12 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see #setSupportedMediaTypes(List)
|
||||
* @see #addSupportedMediaTypes(MediaType...)
|
||||
*/
|
||||
@Override
|
||||
public List<MediaType> getSupportedMediaTypes() {
|
||||
return Collections.unmodifiableList(this.supportedMediaTypes);
|
||||
@@ -236,8 +287,11 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
return true;
|
||||
}
|
||||
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
|
||||
// We can't read multipart....
|
||||
if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) && supportedMediaType.includes(mediaType)) {
|
||||
if (MULTIPART_ALL.includes(supportedMediaType)) {
|
||||
// We can't read multipart, so skip this supported media type.
|
||||
continue;
|
||||
}
|
||||
if (supportedMediaType.includes(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -291,7 +345,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
if (isMultipart(map, contentType)) {
|
||||
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
|
||||
writeMultipart((MultiValueMap<String, Object>) map, contentType, outputMessage);
|
||||
}
|
||||
else {
|
||||
writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);
|
||||
@@ -301,7 +355,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
|
||||
private boolean isMultipart(MultiValueMap<String, ?> map, @Nullable MediaType contentType) {
|
||||
if (contentType != null) {
|
||||
return MediaType.MULTIPART_FORM_DATA.includes(contentType);
|
||||
return MULTIPART_ALL.includes(contentType);
|
||||
}
|
||||
for (List<?> values : map.values()) {
|
||||
for (Object value : values) {
|
||||
@@ -368,9 +422,16 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
|
||||
private void writeMultipart(final MultiValueMap<String, Object> parts, MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException {
|
||||
|
||||
// If the supplied content type is null, fall back to multipart/form-data.
|
||||
// Otherwise rely on the fact that isMultipart() already verified the
|
||||
// supplied content type is multipart/*.
|
||||
if (contentType == null) {
|
||||
contentType = MediaType.MULTIPART_FORM_DATA;
|
||||
}
|
||||
|
||||
final byte[] boundary = generateMultipartBoundary();
|
||||
Map<String, String> parameters = new LinkedHashMap<>(2);
|
||||
if (!isFilenameCharsetSet()) {
|
||||
@@ -378,9 +439,9 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
}
|
||||
parameters.put("boundary", new String(boundary, StandardCharsets.US_ASCII));
|
||||
|
||||
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
|
||||
HttpHeaders headers = outputMessage.getHeaders();
|
||||
headers.setContentType(contentType);
|
||||
// Add parameters to output content type
|
||||
contentType = new MediaType(contentType, parameters);
|
||||
outputMessage.getHeaders().setContentType(contentType);
|
||||
|
||||
if (outputMessage instanceof StreamingHttpOutputMessage) {
|
||||
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
|
||||
|
||||
@@ -47,6 +47,9 @@ import org.springframework.util.MultiValueMap;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
|
||||
import static org.springframework.http.MediaType.TEXT_XML;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link FormHttpMessageConverter} and
|
||||
@@ -58,24 +61,46 @@ import static org.mockito.Mockito.verify;
|
||||
*/
|
||||
public class FormHttpMessageConverterTests {
|
||||
|
||||
protected static final MediaType MULTIPART_MIXED = new MediaType("multipart", "mixed");
|
||||
protected static final MediaType MULTIPART_RELATED = new MediaType("multipart", "related");
|
||||
private static final MediaType MULTIPART_ALL = new MediaType("multipart", "*");
|
||||
private static final MediaType MULTIPART_MIXED = new MediaType("multipart", "mixed");
|
||||
private static final MediaType MULTIPART_RELATED = new MediaType("multipart", "related");
|
||||
|
||||
private final FormHttpMessageConverter converter = new AllEncompassingFormHttpMessageConverter();
|
||||
|
||||
|
||||
@Test
|
||||
public void canRead() {
|
||||
assertThat(this.converter.canRead(MultiValueMap.class, MediaType.APPLICATION_FORM_URLENCODED)).isTrue();
|
||||
assertThat(this.converter.canRead(MultiValueMap.class, MediaType.MULTIPART_FORM_DATA)).isFalse();
|
||||
assertCanRead(MultiValueMap.class, null);
|
||||
assertCanRead(APPLICATION_FORM_URLENCODED);
|
||||
|
||||
assertCannotRead(String.class, null);
|
||||
assertCannotRead(String.class, APPLICATION_FORM_URLENCODED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotReadMultipart() {
|
||||
// Without custom multipart types supported
|
||||
assertCannotRead(MULTIPART_ALL);
|
||||
assertCannotRead(MULTIPART_FORM_DATA);
|
||||
assertCannotRead(MULTIPART_MIXED);
|
||||
assertCannotRead(MULTIPART_RELATED);
|
||||
|
||||
this.converter.addSupportedMediaTypes(MULTIPART_MIXED, MULTIPART_RELATED);
|
||||
|
||||
// With custom multipart types supported
|
||||
assertCannotRead(MULTIPART_ALL);
|
||||
assertCannotRead(MULTIPART_FORM_DATA);
|
||||
assertCannotRead(MULTIPART_MIXED);
|
||||
assertCannotRead(MULTIPART_RELATED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canWrite() {
|
||||
assertCanWrite(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
assertCanWrite(MediaType.MULTIPART_FORM_DATA);
|
||||
assertCanWrite(APPLICATION_FORM_URLENCODED);
|
||||
assertCanWrite(MULTIPART_FORM_DATA);
|
||||
assertCanWrite(new MediaType("multipart", "form-data", StandardCharsets.UTF_8));
|
||||
assertCanWrite(MediaType.ALL);
|
||||
assertCanWrite(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,14 +128,6 @@ public class FormHttpMessageConverterTests {
|
||||
assertCanWrite(MULTIPART_RELATED);
|
||||
}
|
||||
|
||||
private void assertCanWrite(MediaType mediaType) {
|
||||
assertThat(this.converter.canWrite(MultiValueMap.class, mediaType)).isTrue();
|
||||
}
|
||||
|
||||
private void assertCannotWrite(MediaType mediaType) {
|
||||
assertThat(this.converter.canWrite(MultiValueMap.class, mediaType)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readForm() throws Exception {
|
||||
String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3";
|
||||
@@ -136,7 +153,7 @@ public class FormHttpMessageConverterTests {
|
||||
body.add("name 2", "value 2+2");
|
||||
body.add("name 3", null);
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
this.converter.write(body, MediaType.APPLICATION_FORM_URLENCODED, outputMessage);
|
||||
this.converter.write(body, APPLICATION_FORM_URLENCODED, outputMessage);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).as("Invalid result").isEqualTo("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3");
|
||||
assertThat(outputMessage.getHeaders().getContentType().toString()).as("Invalid content-type").isEqualTo("application/x-www-form-urlencoded;charset=UTF-8");
|
||||
@@ -165,7 +182,7 @@ public class FormHttpMessageConverterTests {
|
||||
|
||||
Source xml = new StreamSource(new StringReader("<root><child/></root>"));
|
||||
HttpHeaders entityHeaders = new HttpHeaders();
|
||||
entityHeaders.setContentType(MediaType.TEXT_XML);
|
||||
entityHeaders.setContentType(TEXT_XML);
|
||||
HttpEntity<Source> entity = new HttpEntity<>(xml, entityHeaders);
|
||||
parts.add("xml", entity);
|
||||
|
||||
@@ -226,7 +243,7 @@ public class FormHttpMessageConverterTests {
|
||||
parts.add("part1", myBean);
|
||||
|
||||
HttpHeaders entityHeaders = new HttpHeaders();
|
||||
entityHeaders.setContentType(MediaType.TEXT_XML);
|
||||
entityHeaders.setContentType(TEXT_XML);
|
||||
HttpEntity<MyBean> entity = new HttpEntity<>(myBean, entityHeaders);
|
||||
parts.add("part2", entity);
|
||||
|
||||
@@ -261,6 +278,32 @@ public class FormHttpMessageConverterTests {
|
||||
.endsWith("><string>foo</string></MyBean>");
|
||||
}
|
||||
|
||||
private void assertCanRead(MediaType mediaType) {
|
||||
assertCanRead(MultiValueMap.class, mediaType);
|
||||
}
|
||||
|
||||
private void assertCanRead(Class<?> clazz, MediaType mediaType) {
|
||||
assertThat(this.converter.canRead(clazz, mediaType)).as(clazz.getSimpleName() + " : " + mediaType).isTrue();
|
||||
}
|
||||
|
||||
private void assertCannotRead(MediaType mediaType) {
|
||||
assertCannotRead(MultiValueMap.class, mediaType);
|
||||
}
|
||||
|
||||
private void assertCannotRead(Class<?> clazz, MediaType mediaType) {
|
||||
assertThat(this.converter.canRead(clazz, mediaType)).as(clazz.getSimpleName() + " : " + mediaType).isFalse();
|
||||
}
|
||||
|
||||
private void assertCanWrite(MediaType mediaType) {
|
||||
Class<?> clazz = MultiValueMap.class;
|
||||
assertThat(this.converter.canWrite(clazz, mediaType)).as(clazz.getSimpleName() + " : " + mediaType).isTrue();
|
||||
}
|
||||
|
||||
private void assertCannotWrite(MediaType mediaType) {
|
||||
Class<?> clazz = MultiValueMap.class;
|
||||
assertThat(this.converter.canWrite(clazz, mediaType)).as(clazz.getSimpleName() + " : " + mediaType).isFalse();
|
||||
}
|
||||
|
||||
|
||||
private static class MockHttpOutputMessageRequestContext implements RequestContext {
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.HttpHeaders.LOCATION;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
@@ -42,6 +43,9 @@ import static org.springframework.http.HttpHeaders.LOCATION;
|
||||
*/
|
||||
public class AbstractMockWebServerTestCase {
|
||||
|
||||
protected static final MediaType MULTIPART_MIXED = new MediaType("multipart", "mixed");
|
||||
protected static final MediaType MULTIPART_RELATED = new MediaType("multipart", "related");
|
||||
|
||||
protected static final MediaType textContentType =
|
||||
new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8"));
|
||||
|
||||
@@ -120,10 +124,31 @@ public class AbstractMockWebServerTestCase {
|
||||
.setResponseCode(201);
|
||||
}
|
||||
|
||||
private MockResponse multipartRequest(RecordedRequest request) {
|
||||
MediaType mediaType = MediaType.parseMediaType(request.getHeader("Content-Type"));
|
||||
assertThat(mediaType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA)).isTrue();
|
||||
private MockResponse multipartFormDataRequest(RecordedRequest request) {
|
||||
MediaType mediaType = MediaType.parseMediaType(request.getHeader(CONTENT_TYPE));
|
||||
assertThat(mediaType.isCompatibleWith(MULTIPART_FORM_DATA)).as(MULTIPART_FORM_DATA.toString()).isTrue();
|
||||
assertMultipart(request, mediaType);
|
||||
return new MockResponse().setResponseCode(200);
|
||||
}
|
||||
|
||||
private MockResponse multipartMixedRequest(RecordedRequest request) {
|
||||
MediaType mediaType = MediaType.parseMediaType(request.getHeader(CONTENT_TYPE));
|
||||
assertThat(mediaType.isCompatibleWith(MULTIPART_MIXED)).as(MULTIPART_MIXED.toString()).isTrue();
|
||||
assertMultipart(request, mediaType);
|
||||
return new MockResponse().setResponseCode(200);
|
||||
}
|
||||
|
||||
private MockResponse multipartRelatedRequest(RecordedRequest request) {
|
||||
MediaType mediaType = MediaType.parseMediaType(request.getHeader(CONTENT_TYPE));
|
||||
assertThat(mediaType.isCompatibleWith(MULTIPART_RELATED)).as(MULTIPART_RELATED.toString()).isTrue();
|
||||
assertMultipart(request, mediaType);
|
||||
return new MockResponse().setResponseCode(200);
|
||||
}
|
||||
|
||||
private void assertMultipart(RecordedRequest request, MediaType mediaType) {
|
||||
assertThat(mediaType.isCompatibleWith(new MediaType("multipart", "*"))).as("multipart/*").isTrue();
|
||||
String boundary = mediaType.getParameter("boundary");
|
||||
assertThat(boundary).as("boundary").isNotBlank();
|
||||
Buffer body = request.getBody();
|
||||
try {
|
||||
assertPart(body, "form-data", boundary, "name 1", "text/plain", "value 1");
|
||||
@@ -132,9 +157,8 @@ public class AbstractMockWebServerTestCase {
|
||||
assertFilePart(body, "form-data", boundary, "logo", "logo.jpg", "image/jpeg");
|
||||
}
|
||||
catch (EOFException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
return new MockResponse().setResponseCode(200);
|
||||
}
|
||||
|
||||
private void assertPart(Buffer buffer, String disposition, String boundary, String name,
|
||||
@@ -245,8 +269,14 @@ public class AbstractMockWebServerTestCase {
|
||||
else if (request.getPath().contains("/uri/")) {
|
||||
return new MockResponse().setBody(request.getPath()).setHeader(CONTENT_TYPE, "text/plain");
|
||||
}
|
||||
else if (request.getPath().equals("/multipart")) {
|
||||
return multipartRequest(request);
|
||||
else if (request.getPath().equals("/multipartFormData")) {
|
||||
return multipartFormDataRequest(request);
|
||||
}
|
||||
else if (request.getPath().equals("/multipartMixed")) {
|
||||
return multipartMixedRequest(request);
|
||||
}
|
||||
else if (request.getPath().equals("/multipartRelated")) {
|
||||
return multipartRelatedRequest(request);
|
||||
}
|
||||
else if (request.getPath().equals("/form")) {
|
||||
return formRequest(request);
|
||||
|
||||
@@ -49,6 +49,18 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link AsyncRestTemplate}.
|
||||
*
|
||||
* <h3>Logging configuration for {@code MockWebServer}</h3>
|
||||
*
|
||||
* <p>In order for our log4j2 configuration to be used in an IDE, you must
|
||||
* set the following system property before running any tests — for
|
||||
* example, in <em>Run Configurations</em> in Eclipse.
|
||||
*
|
||||
* <pre class="code">
|
||||
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
|
||||
* </pre>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
@@ -578,7 +590,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractMockWebServerTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipart() throws Exception {
|
||||
public void multipartFormData() throws Exception {
|
||||
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
parts.add("name 1", "value 1");
|
||||
parts.add("name 2", "value 2+1");
|
||||
@@ -587,7 +599,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractMockWebServerTest
|
||||
parts.add("logo", logo);
|
||||
|
||||
HttpEntity<MultiValueMap<String, Object>> requestBody = new HttpEntity<>(parts);
|
||||
Future<URI> future = template.postForLocation(baseUrl + "/multipart", requestBody);
|
||||
Future<URI> future = template.postForLocation(baseUrl + "/multipartFormData", requestBody);
|
||||
future.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -66,6 +67,16 @@ import static org.springframework.http.HttpMethod.POST;
|
||||
/**
|
||||
* Integration tests for {@link RestTemplate}.
|
||||
*
|
||||
* <h3>Logging configuration for {@code MockWebServer}</h3>
|
||||
*
|
||||
* <p>In order for our log4j2 configuration to be used in an IDE, you must
|
||||
* set the following system property before running any tests — for
|
||||
* example, in <em>Run Configurations</em> in Eclipse.
|
||||
*
|
||||
* <pre class="code">
|
||||
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
|
||||
* </pre>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
@@ -258,19 +269,55 @@ public class RestTemplateIntegrationTests extends AbstractMockWebServerTestCase
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipart() throws UnsupportedEncodingException {
|
||||
public void multipartFormData() {
|
||||
template.postForLocation(baseUrl + "/multipartFormData", createMultipartParts());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartMixed() {
|
||||
addSupportedMediaTypeToFormHttpMessageConverter(MULTIPART_MIXED);
|
||||
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
requestHeaders.setContentType(MULTIPART_MIXED);
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(createMultipartParts(),
|
||||
requestHeaders);
|
||||
|
||||
template.postForLocation(baseUrl + "/multipartMixed", requestEntity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartRelated() {
|
||||
addSupportedMediaTypeToFormHttpMessageConverter(MULTIPART_RELATED);
|
||||
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
requestHeaders.setContentType(MULTIPART_RELATED);
|
||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(createMultipartParts(),
|
||||
requestHeaders);
|
||||
|
||||
template.postForLocation(baseUrl + "/multipartRelated", requestEntity);
|
||||
}
|
||||
|
||||
private MultiValueMap<String, Object> createMultipartParts() {
|
||||
MultiValueMap<String, Object> 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);
|
||||
return parts;
|
||||
}
|
||||
|
||||
template.postForLocation(baseUrl + "/multipart", parts);
|
||||
private void addSupportedMediaTypeToFormHttpMessageConverter(MediaType mediaType) {
|
||||
this.template.getMessageConverters().stream()
|
||||
.filter(FormHttpMessageConverter.class::isInstance)
|
||||
.map(FormHttpMessageConverter.class::cast)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Failed to find FormHttpMessageConverter"))
|
||||
.addSupportedMediaTypes(mediaType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void form() throws UnsupportedEncodingException {
|
||||
public void form() {
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
||||
form.add("name 1", "value 1");
|
||||
form.add("name 2", "value 2+1");
|
||||
|
||||
@@ -1142,7 +1142,10 @@ request and response. By default, this converter supports all text media types
|
||||
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP
|
||||
request and response. By default, this converter reads and writes the
|
||||
`application/x-www-form-urlencoded` media type. Form data is read from and written into a
|
||||
`MultiValueMap<String, String>`.
|
||||
`MultiValueMap<String, String>`. The converter can also write (but not read) multipart
|
||||
data read from a `MultiValueMap<String, Object>`. By default, `multipart/form-data` is
|
||||
supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for
|
||||
writing form data. Consult the javadoc for `FormHttpMessageConverter` for further details.
|
||||
|
||||
| `ByteArrayHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write byte arrays from the
|
||||
|
||||
Reference in New Issue
Block a user