Browse Source

Support empty body without content type in WebFlux

Issue: SPR-15758
pull/1476/head
Rossen Stoyanchev 9 years ago
parent
commit
ed5cc27f7b
  1. 30
      spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java
  2. 4
      spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java
  3. 10
      spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java

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

@ -18,8 +18,10 @@ package org.springframework.web.reactive.result.method.annotation; @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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));
}

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

@ -83,14 +83,16 @@ public class MessageReaderArgumentResolverTests { @@ -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

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

@ -19,6 +19,7 @@ package org.springframework.web.reactive.result.method.annotation; @@ -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 { @@ -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 { @@ -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) {}
}

Loading…
Cancel
Save