Skip to content

Commit ed5cc27

Browse files
committed
Support empty body without content type in WebFlux
Issue: SPR-15758
1 parent d9eafce commit ed5cc27

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.util.Collections;
21+
import java.util.EnumSet;
2122
import java.util.List;
2223
import java.util.Map;
24+
import java.util.Set;
2325
import java.util.stream.Collectors;
2426

2527
import reactor.core.publisher.Flux;
@@ -32,6 +34,8 @@
3234
import org.springframework.core.ResolvableType;
3335
import org.springframework.core.annotation.AnnotationUtils;
3436
import org.springframework.core.codec.DecodingException;
37+
import org.springframework.core.io.buffer.DataBuffer;
38+
import org.springframework.http.HttpMethod;
3539
import org.springframework.http.MediaType;
3640
import org.springframework.http.codec.HttpMessageReader;
3741
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -62,6 +66,10 @@
6266
*/
6367
public abstract class AbstractMessageReaderArgumentResolver extends HandlerMethodArgumentResolverSupport {
6468

69+
private static final Set<HttpMethod> SUPPORTED_METHODS =
70+
EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);
71+
72+
6573
private final List<HttpMessageReader<?>> messageReaders;
6674

6775
private final List<MediaType> supportedMediaTypes;
@@ -111,10 +119,9 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
111119

112120
ServerHttpRequest request = exchange.getRequest();
113121
ServerHttpResponse response = exchange.getResponse();
114-
MediaType mediaType = request.getHeaders().getContentType();
115-
if (mediaType == null) {
116-
mediaType = MediaType.APPLICATION_OCTET_STREAM;
117-
}
122+
123+
MediaType contentType = request.getHeaders().getContentType();
124+
MediaType mediaType = (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
118125

119126
for (HttpMessageReader<?> reader : getMessageReaders()) {
120127
if (reader.canRead(elementType, mediaType)) {
@@ -133,6 +140,7 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
133140
return Mono.just(adapter.fromPublisher(flux));
134141
}
135142
else {
143+
// Single-value (with or without reactive type wrapper)
136144
Mono<?> mono = reader.readMono(bodyType, elementType, request, response, readHints);
137145
mono = mono.onErrorResume(ex -> Mono.error(handleReadError(bodyParameter, ex)));
138146
if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) {
@@ -153,6 +161,20 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
153161
}
154162
}
155163

164+
// No compatible reader but body may be empty..
165+
166+
HttpMethod method = request.getMethod();
167+
if (contentType == null && method != null && SUPPORTED_METHODS.contains(method)) {
168+
Flux<DataBuffer> body = request.getBody().doOnNext(o -> {
169+
// Body not empty, back to 415..
170+
throw new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes);
171+
});
172+
if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) {
173+
body = body.switchIfEmpty(Mono.error(handleMissingBody(bodyParameter)));
174+
}
175+
return (adapter != null ? Mono.just(adapter.fromPublisher(body)) : Mono.from(body));
176+
}
177+
156178
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes));
157179
}
158180

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,16 @@ public void setup() throws Exception {
8383
}
8484

8585

86+
@SuppressWarnings("unchecked")
8687
@Test
8788
public void missingContentType() throws Exception {
8889
ServerWebExchange exchange = post("/path").body("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}").toExchange();
8990
ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class);
9091
MethodParameter param = this.testMethod.arg(type);
9192
Mono<Object> result = this.resolver.readBody(param, true, this.bindingContext, exchange);
93+
Mono<TestBean> value = (Mono<TestBean>) result.block(Duration.ofSeconds(1));
9294

93-
StepVerifier.create(result).expectError(UnsupportedMediaTypeStatusException.class).verify();
95+
StepVerifier.create(value).expectError(UnsupportedMediaTypeStatusException.class).verify();
9496
}
9597

9698
// More extensive "empty body" tests in RequestBody- and HttpEntityArgumentResolverTests

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Duration;
2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.Map;
2223
import java.util.concurrent.CompletableFuture;
2324

2425
import io.reactivex.Maybe;
@@ -106,6 +107,14 @@ public void emptyBodyWithStringNotRequired() throws Exception {
106107
assertNull(body);
107108
}
108109

110+
@Test // SPR-15758
111+
public void emptyBodyWithoutContentType() throws Exception {
112+
MethodParameter param = this.testMethod.annot(requestBody().notRequired()).arg(Map.class);
113+
String body = resolveValueWithEmptyBody(param);
114+
115+
assertNull(body);
116+
}
117+
109118
@Test
110119
@SuppressWarnings("unchecked")
111120
public void emptyBodyWithMono() throws Exception {
@@ -262,6 +271,7 @@ void handle(
262271
@RequestBody(required = false) Observable<String> obsNotRequired,
263272
@RequestBody(required = false) io.reactivex.Observable<String> rxjava2ObsNotRequired,
264273
@RequestBody(required = false) CompletableFuture<String> futureNotRequired,
274+
@RequestBody(required = false) Map<?, ?> mapNotRequired,
265275
String notAnnotated) {}
266276

267277
}

0 commit comments

Comments
 (0)