Support empty body without content type in WebFlux
Issue: SPR-15758
This commit is contained in:
@@ -18,8 +18,10 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -32,6 +34,8 @@ import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.codec.DecodingException;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
@@ -62,6 +66,10 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
*/
|
||||
public abstract class AbstractMessageReaderArgumentResolver extends HandlerMethodArgumentResolverSupport {
|
||||
|
||||
private static final Set<HttpMethod> SUPPORTED_METHODS =
|
||||
EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);
|
||||
|
||||
|
||||
private final List<HttpMessageReader<?>> messageReaders;
|
||||
|
||||
private final List<MediaType> supportedMediaTypes;
|
||||
@@ -111,10 +119,9 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
MediaType mediaType = request.getHeaders().getContentType();
|
||||
if (mediaType == null) {
|
||||
mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
MediaType mediaType = (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
for (HttpMessageReader<?> reader : getMessageReaders()) {
|
||||
if (reader.canRead(elementType, mediaType)) {
|
||||
@@ -133,6 +140,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
|
||||
return Mono.just(adapter.fromPublisher(flux));
|
||||
}
|
||||
else {
|
||||
// Single-value (with or without reactive type wrapper)
|
||||
Mono<?> mono = reader.readMono(bodyType, elementType, request, response, readHints);
|
||||
mono = mono.onErrorResume(ex -> Mono.error(handleReadError(bodyParameter, ex)));
|
||||
if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) {
|
||||
@@ -153,6 +161,20 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
|
||||
}
|
||||
}
|
||||
|
||||
// No compatible reader but body may be empty..
|
||||
|
||||
HttpMethod method = request.getMethod();
|
||||
if (contentType == null && method != null && SUPPORTED_METHODS.contains(method)) {
|
||||
Flux<DataBuffer> body = request.getBody().doOnNext(o -> {
|
||||
// Body not empty, back to 415..
|
||||
throw new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes);
|
||||
});
|
||||
if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) {
|
||||
body = body.switchIfEmpty(Mono.error(handleMissingBody(bodyParameter)));
|
||||
}
|
||||
return (adapter != null ? Mono.just(adapter.fromPublisher(body)) : Mono.from(body));
|
||||
}
|
||||
|
||||
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes));
|
||||
}
|
||||
|
||||
|
||||
@@ -83,14 +83,16 @@ public class MessageReaderArgumentResolverTests {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void missingContentType() throws Exception {
|
||||
ServerWebExchange exchange = post("/path").body("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}").toExchange();
|
||||
ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class);
|
||||
MethodParameter param = this.testMethod.arg(type);
|
||||
Mono<Object> result = this.resolver.readBody(param, true, this.bindingContext, exchange);
|
||||
Mono<TestBean> value = (Mono<TestBean>) result.block(Duration.ofSeconds(1));
|
||||
|
||||
StepVerifier.create(result).expectError(UnsupportedMediaTypeStatusException.class).verify();
|
||||
StepVerifier.create(value).expectError(UnsupportedMediaTypeStatusException.class).verify();
|
||||
}
|
||||
|
||||
// More extensive "empty body" tests in RequestBody- and HttpEntityArgumentResolverTests
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.reactivex.Maybe;
|
||||
@@ -106,6 +107,14 @@ public class RequestBodyArgumentResolverTests {
|
||||
assertNull(body);
|
||||
}
|
||||
|
||||
@Test // SPR-15758
|
||||
public void emptyBodyWithoutContentType() throws Exception {
|
||||
MethodParameter param = this.testMethod.annot(requestBody().notRequired()).arg(Map.class);
|
||||
String body = resolveValueWithEmptyBody(param);
|
||||
|
||||
assertNull(body);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void emptyBodyWithMono() throws Exception {
|
||||
@@ -262,6 +271,7 @@ public class RequestBodyArgumentResolverTests {
|
||||
@RequestBody(required = false) Observable<String> obsNotRequired,
|
||||
@RequestBody(required = false) io.reactivex.Observable<String> rxjava2ObsNotRequired,
|
||||
@RequestBody(required = false) CompletableFuture<String> futureNotRequired,
|
||||
@RequestBody(required = false) Map<?, ?> mapNotRequired,
|
||||
String notAnnotated) {}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user