Support multipart/mixed and related in DefaultServerWebExchange

See gh-29671
This commit is contained in:
Ronny Perinke 2022-12-09 22:03:06 +01:00 committed by rstoyanchev
parent 17bc3facaa
commit 67df0756cd
3 changed files with 37 additions and 10 deletions

View File

@ -613,8 +613,8 @@ The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse
---- ----
The `DefaultServerWebExchange` uses the configured The `DefaultServerWebExchange` uses the configured
`HttpMessageReader<MultiValueMap<String, Part>>` to parse `multipart/form-data` content `HttpMessageReader<MultiValueMap<String, Part>>` to parse `multipart/form-data`,
into a `MultiValueMap`. `multipart/mixed` and `multipart/related` content into a `MultiValueMap`.
By default, this is the `DefaultPartHttpMessageReader`, which does not have any third-party By default, this is the `DefaultPartHttpMessageReader`, which does not have any third-party
dependencies. dependencies.
Alternatively, the `SynchronossPartHttpMessageReader` can be used, which is based on the Alternatively, the `SynchronossPartHttpMessageReader` can be used, which is based on the
@ -805,9 +805,9 @@ consistently for access to the cached form data versus reading from the raw requ
==== Multipart ==== Multipart
`MultipartHttpMessageReader` and `MultipartHttpMessageWriter` support decoding and `MultipartHttpMessageReader` and `MultipartHttpMessageWriter` support decoding and
encoding "multipart/form-data" content. In turn `MultipartHttpMessageReader` delegates to encoding "multipart/form-data", "multipart/mixed" and "multipart/related" content.
another `HttpMessageReader` for the actual parsing to a `Flux<Part>` and then simply In turn `MultipartHttpMessageReader` delegates to another `HttpMessageReader`
collects the parts into a `MultiValueMap`. for the actual parsing to a `Flux<Part>` and then simply collects the parts into a `MultiValueMap`.
By default, the `DefaultPartHttpMessageReader` is used, but this can be changed through the By default, the `DefaultPartHttpMessageReader` is used, but this can be changed through the
`ServerCodecConfigurer`. `ServerCodecConfigurer`.
For more information about the `DefaultPartHttpMessageReader`, refer to the For more information about the `DefaultPartHttpMessageReader`, refer to the

View File

@ -163,9 +163,11 @@ public class DefaultServerWebExchange implements ServerWebExchange {
try { try {
MediaType contentType = request.getHeaders().getContentType(); MediaType contentType = request.getHeaders().getContentType();
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) { if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType) ||
MediaType.MULTIPART_MIXED.isCompatibleWith(contentType) ||
MediaType.MULTIPART_RELATED.isCompatibleWith(contentType)) {
return ((HttpMessageReader<MultiValueMap<String, Part>>) configurer.getReaders().stream() return ((HttpMessageReader<MultiValueMap<String, Part>>) configurer.getReaders().stream()
.filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA)) .filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, contentType))
.findFirst() .findFirst()
.orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader."))) .orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
.readMono(MULTIPART_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix)) .readMono(MULTIPART_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))

View File

@ -32,6 +32,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart; import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart; import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part; import org.springframework.http.codec.multipart.Part;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -55,14 +56,38 @@ class MultipartHttpHandlerIntegrationTests extends AbstractHttpHandlerIntegratio
} }
@ParameterizedHttpServerTest @ParameterizedHttpServerTest
void getFormParts(HttpServer httpServer) throws Exception { void getFormPartsFormdata(HttpServer httpServer) throws Exception {
performTest(httpServer, MediaType.MULTIPART_FORM_DATA);
}
@ParameterizedHttpServerTest
void getFormPartsMixed(HttpServer httpServer) throws Exception {
performTest(httpServer, MediaType.MULTIPART_MIXED);
}
@ParameterizedHttpServerTest
void getFormPartsRelated(HttpServer httpServer) throws Exception {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().stream()
.filter(FormHttpMessageConverter.class::isInstance)
.map(FormHttpMessageConverter.class::cast)
.findFirst()
.orElseThrow()
.addSupportedMediaTypes(MediaType.MULTIPART_RELATED);
performTest(httpServer, MediaType.MULTIPART_RELATED, restTemplate);
}
private void performTest(HttpServer httpServer, MediaType mediaType) throws Exception {
performTest(httpServer, mediaType, new RestTemplate());
}
private void performTest(HttpServer httpServer, MediaType mediaType, RestTemplate restTemplate) throws Exception {
startServer(httpServer); startServer(httpServer);
@SuppressWarnings("resource") @SuppressWarnings("resource")
RestTemplate restTemplate = new RestTemplate();
RequestEntity<MultiValueMap<String, Object>> request = RequestEntity RequestEntity<MultiValueMap<String, Object>> request = RequestEntity
.post(URI.create("http://localhost:" + port + "/form-parts")) .post(URI.create("http://localhost:" + port + "/form-parts"))
.contentType(MediaType.MULTIPART_FORM_DATA) .contentType(mediaType)
.body(generateBody()); .body(generateBody());
ResponseEntity<Void> response = restTemplate.exchange(request, Void.class); ResponseEntity<Void> response = restTemplate.exchange(request, Void.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);