From dc1926a8614920e1fcf2a952287d0773fe2e59ee Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 5 Oct 2016 15:01:23 +0200 Subject: [PATCH] Moved BodyExtractor and BodyInserter to http.codec This commit moves the web.reactive.function.[BodyInserter|BodyExtractor] to http.codec, so that they can be used from the client as well. Furthermore, it parameterized both inserter and extractor over ReactiveHttpOutputMessage and ReactiveHttpInputMessage respectively, so that they can be limited to only be used on the client or server. --- .../web/reactive/function/DefaultRequest.java | 14 +++- .../function/DefaultResponseBuilder.java | 24 ++++-- .../web/reactive/function/Request.java | 4 +- .../reactive/function/RequestPredicates.java | 4 +- .../web/reactive/function/Response.java | 5 +- .../function/support/RequestWrapper.java | 5 +- .../function/DefaultRequestTests.java | 2 +- .../function/DefaultResponseBuilderTests.java | 3 +- .../DispatcherHandlerIntegrationTests.java | 5 +- .../web/reactive/function/MockRequest.java | 6 +- ...lisherHandlerFunctionIntegrationTests.java | 4 +- .../function/RouterFunctionTests.java | 2 +- .../SseHandlerFunctionIntegrationTests.java | 7 +- .../http/codec}/BodyExtractor.java | 31 ++++++-- .../http/codec}/BodyExtractors.java | 35 +++++---- .../http/codec}/BodyInserter.java | 40 ++++++---- .../http/codec}/BodyInserters.java | 70 +++++++++--------- .../codec/UnsupportedMediaTypeException.java | 74 +++++++++++++++++++ .../http/codec}/BodyExtractorsTests.java | 59 +++++++++++---- .../http/codec}/BodyInsertersTests.java | 54 +++++++++++--- .../springframework/http/codec}/response.txt | 0 21 files changed, 320 insertions(+), 128 deletions(-) rename {spring-web-reactive/src/main/java/org/springframework/web/reactive/function => spring-web/src/main/java/org/springframework/http/codec}/BodyExtractor.java (52%) rename {spring-web-reactive/src/main/java/org/springframework/web/reactive/function => spring-web/src/main/java/org/springframework/http/codec}/BodyExtractors.java (76%) rename {spring-web-reactive/src/main/java/org/springframework/web/reactive/function => spring-web/src/main/java/org/springframework/http/codec}/BodyInserter.java (61%) rename {spring-web-reactive/src/main/java/org/springframework/web/reactive/function => spring-web/src/main/java/org/springframework/http/codec}/BodyInserters.java (77%) create mode 100644 spring-web/src/main/java/org/springframework/http/codec/UnsupportedMediaTypeException.java rename {spring-web-reactive/src/test/java/org/springframework/web/reactive/function => spring-web/src/test/java/org/springframework/http/codec}/BodyExtractorsTests.java (57%) rename {spring-web-reactive/src/test/java/org/springframework/web/reactive/function => spring-web/src/test/java/org/springframework/http/codec}/BodyInsertersTests.java (65%) rename {spring-web-reactive/src/test/resources/org/springframework/web/reactive/function => spring-web/src/test/resources/org/springframework/http/codec}/response.txt (100%) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultRequest.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultRequest.java index 3e3ecc3c37..8333ac111d 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultRequest.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultRequest.java @@ -24,11 +24,15 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalLong; +import java.util.function.Supplier; +import java.util.stream.Stream; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyExtractor; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.server.ServerWebExchange; @@ -68,8 +72,14 @@ class DefaultRequest implements Request { } @Override - public T body(BodyExtractor extractor) { - return extractor.extract(request(), this.strategies); + public T body(BodyExtractor extractor) { + return extractor.extract(request(), + new BodyExtractor.Context() { + @Override + public Supplier>> messageReaders() { + return DefaultRequest.this.strategies.messageReaders(); + } + }); } @Override diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultResponseBuilder.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultResponseBuilder.java index 5d4d8f1b37..2236be2277 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultResponseBuilder.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultResponseBuilder.java @@ -41,6 +41,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyInserter; +import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -142,7 +144,7 @@ class DefaultResponseBuilder implements Response.BodyBuilder { @Override public Response build() { return body(BodyInserter.of( - (response, strategies) -> response.setComplete(), + (response, context) -> response.setComplete(), () -> null)); } @@ -150,18 +152,18 @@ class DefaultResponseBuilder implements Response.BodyBuilder { public > Response build(T voidPublisher) { Assert.notNull(voidPublisher, "'voidPublisher' must not be null"); return body(BodyInserter.of( - (response, strategies) -> Flux.from(voidPublisher).then(response.setComplete()), + (response, context) -> Flux.from(voidPublisher).then(response.setComplete()), () -> null)); } @Override - public Response body(BiFunction> writer, + public Response body(BiFunction> writer, Supplier supplier) { return body(BodyInserter.of(writer, supplier)); } @Override - public Response body(BodyInserter inserter) { + public Response body(BodyInserter inserter) { Assert.notNull(inserter, "'inserter' must not be null"); return new BodyInserterResponse(this.statusCode, this.headers, inserter); } @@ -235,11 +237,12 @@ class DefaultResponseBuilder implements Response.BodyBuilder { private static final class BodyInserterResponse extends AbstractResponse { - private final BodyInserter inserter; + private final BodyInserter inserter; - public BodyInserterResponse( - int statusCode, HttpHeaders headers, BodyInserter inserter) { + public BodyInserterResponse(int statusCode, HttpHeaders headers, + BodyInserter inserter) { + super(statusCode, headers); this.inserter = inserter; } @@ -253,7 +256,12 @@ class DefaultResponseBuilder implements Response.BodyBuilder { public Mono writeTo(ServerWebExchange exchange, StrategiesSupplier strategies) { ServerHttpResponse response = exchange.getResponse(); writeStatusAndHeaders(response); - return this.inserter.insert(response, strategies); + return this.inserter.insert(response, new BodyInserter.Context() { + @Override + public Supplier>> messageWriters() { + return strategies.messageWriters(); + } + }); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Request.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Request.java index f13c9c58a3..1ab0f09bb0 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Request.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Request.java @@ -28,6 +28,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyExtractor; +import org.springframework.http.server.reactive.ServerHttpRequest; /** * Represents an HTTP request, as handled by a {@code HandlerFunction}. @@ -67,7 +69,7 @@ public interface Request { * @param the type of the body returned * @return the extracted body */ - T body(BodyExtractor extractor); + T body(BodyExtractor extractor); /** * Return the request attribute value if present. diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RequestPredicates.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RequestPredicates.java index 09b6c0f054..c88c918ea0 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RequestPredicates.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RequestPredicates.java @@ -27,6 +27,8 @@ import java.util.function.Predicate; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyExtractor; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.PathMatcher; @@ -314,7 +316,7 @@ public abstract class RequestPredicates { } @Override - public T body(BodyExtractor extractor) { + public T body(BodyExtractor extractor) { return this.request.body(extractor); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Response.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Response.java index 25b5e0206a..37122a1a43 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Response.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Response.java @@ -32,6 +32,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyInserter; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; @@ -316,7 +317,7 @@ public interface Response { * @param the type contained in the body * @return the built response */ - Response body(BiFunction> writer, + Response body(BiFunction> writer, Supplier supplier); /** @@ -325,7 +326,7 @@ public interface Response { * @param the type contained in the body * @return the built response */ - Response body(BodyInserter inserter); + Response body(BodyInserter inserter); /** * Render the template with the given {@code name} using the given {@code modelAttributes}. diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/RequestWrapper.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/RequestWrapper.java index 4d1d998e90..c11b091e95 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/RequestWrapper.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/RequestWrapper.java @@ -28,8 +28,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyExtractor; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; -import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.HandlerFunction; import org.springframework.web.reactive.function.Request; @@ -83,7 +84,7 @@ public class RequestWrapper implements Request { } @Override - public T body(BodyExtractor extractor) { + public T body(BodyExtractor extractor) { return this.request.body(extractor); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultRequestTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultRequestTests.java index d48aefc067..aeff32af05 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultRequestTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultRequestTests.java @@ -52,7 +52,7 @@ import org.springframework.web.server.ServerWebExchange; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.web.reactive.function.BodyExtractors.toMono; +import static org.springframework.http.codec.BodyExtractors.toMono; /** * @author Arjen Poutsma diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultResponseBuilderTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultResponseBuilderTests.java index 711f8698fb..110db3b3eb 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultResponseBuilderTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultResponseBuilderTests.java @@ -38,6 +38,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyInserter; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -217,7 +218,7 @@ public class DefaultResponseBuilderTests { public void bodyInserter() throws Exception { String body = "foo"; Supplier supplier = () -> body; - BiFunction> writer = + BiFunction> writer = (response, strategies) -> { byte[] bodyBytes = body.getBytes(UTF_8); ByteBuffer byteBuffer = ByteBuffer.wrap(bodyBytes); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java index 7f346eddfc..278d565a37 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DispatcherHandlerIntegrationTests.java @@ -50,6 +50,7 @@ import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.junit.Assert.assertEquals; +import static org.springframework.http.codec.BodyInserters.fromPublisher; import static org.springframework.web.reactive.function.RouterFunctions.route; /** @@ -155,14 +156,14 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr public Response> mono(Request request) { Person person = new Person("John"); - return Response.ok().body(BodyInserters.fromPublisher(Mono.just(person), Person.class)); + return Response.ok().body(fromPublisher(Mono.just(person), Person.class)); } public Response> flux(Request request) { Person person1 = new Person("John"); Person person2 = new Person("Jane"); return Response.ok().body( - BodyInserters.fromPublisher(Flux.just(person1, person2), Person.class)); + fromPublisher(Flux.just(person1, person2), Person.class)); } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockRequest.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockRequest.java index ebb3bb1d62..13f8a59903 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockRequest.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockRequest.java @@ -33,6 +33,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.codec.BodyExtractor; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -88,9 +90,9 @@ public class MockRequest implements Request { return this.headers; } - @SuppressWarnings("unchecked") @Override - public S body(BodyExtractor extractor) { + @SuppressWarnings("unchecked") + public S body(BodyExtractor extractor){ return (S) this.body; } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PublisherHandlerFunctionIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PublisherHandlerFunctionIntegrationTests.java index 439edd50cf..633b74dab8 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PublisherHandlerFunctionIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PublisherHandlerFunctionIntegrationTests.java @@ -33,8 +33,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import static org.junit.Assert.assertEquals; -import static org.springframework.web.reactive.function.BodyExtractors.toMono; -import static org.springframework.web.reactive.function.BodyInserters.fromPublisher; +import static org.springframework.http.codec.BodyExtractors.toMono; +import static org.springframework.http.codec.BodyInserters.fromPublisher; import static org.springframework.web.reactive.function.RequestPredicates.GET; import static org.springframework.web.reactive.function.RequestPredicates.POST; import static org.springframework.web.reactive.function.RouterFunctions.route; diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionTests.java index 0ffdb3c906..4cddcdd18b 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionTests.java @@ -23,7 +23,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.http.codec.BodyInserters.fromObject; /** * @author Arjen Poutsma diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/SseHandlerFunctionIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/SseHandlerFunctionIntegrationTests.java index b403025059..dc7f35b087 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/SseHandlerFunctionIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/SseHandlerFunctionIntegrationTests.java @@ -30,6 +30,7 @@ import org.springframework.http.codec.ServerSentEvent; import org.springframework.tests.TestSubscriber; import org.springframework.web.client.reactive.WebClient; +import static org.springframework.http.codec.BodyInserters.fromServerSentEvents; import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get; import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream; import static org.springframework.web.reactive.function.RouterFunctions.route; @@ -111,13 +112,13 @@ public class SseHandlerFunctionIntegrationTests public Response> string(Request request) { Flux flux = Flux.interval(Duration.ofMillis(100)).map(l -> "foo " + l).take(2); - return Response.ok().body(BodyInserters.fromServerSentEvents(flux, String.class)); + return Response.ok().body(fromServerSentEvents(flux, String.class)); } public Response> person(Request request) { Flux flux = Flux.interval(Duration.ofMillis(100)) .map(l -> new Person("foo " + l)).take(2); - return Response.ok().body(BodyInserters.fromServerSentEvents(flux, Person.class)); + return Response.ok().body(fromServerSentEvents(flux, Person.class)); } public Response>> sse(Request request) { @@ -127,7 +128,7 @@ public class SseHandlerFunctionIntegrationTests .comment("bar") .build()).take(2); - return Response.ok().body(BodyInserters.fromServerSentEvents(flux)); + return Response.ok().body(fromServerSentEvents(flux)); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java b/spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java similarity index 52% rename from spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java rename to spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java index c21cd30189..a2c469b540 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractor.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java @@ -14,28 +14,43 @@ * limitations under the License. */ -package org.springframework.web.reactive.function; +package org.springframework.http.codec; -import org.springframework.http.server.reactive.ServerHttpRequest; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.springframework.http.ReactiveHttpInputMessage; /** - * A function that can extract data from a {@link Request} body. + * A function that can extract data from a {@link ReactiveHttpInputMessage} body. * * @param the type of data to extract * @author Arjen Poutsma * @since 5.0 - * @see Request#body(BodyExtractor) * @see BodyExtractors */ @FunctionalInterface -public interface BodyExtractor { +public interface BodyExtractor { /** * Extract from the given request. - * @param request the request to extract from - * @param strategies the strategies to use + * @param inputMessage request to extract from + * @param context the configuration to use * @return the extracted data */ - T extract(ServerHttpRequest request, StrategiesSupplier strategies); + T extract(M inputMessage, Context context); + + /** + * Defines the context used during the extraction. + */ + interface Context { + + /** + * Supply a {@linkplain Stream stream} of {@link HttpMessageReader}s to be used for body + * extraction. + * @return the stream of message readers + */ + Supplier>> messageReaders(); + } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java b/spring-web/src/main/java/org/springframework/http/codec/BodyExtractors.java similarity index 76% rename from spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java rename to spring-web/src/main/java/org/springframework/http/codec/BodyExtractors.java index 56fe29c191..d355de46fb 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyExtractors.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.web.reactive.function; +package org.springframework.http.codec; import java.util.Collections; import java.util.List; @@ -28,11 +28,10 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ResolvableType; +import org.springframework.http.HttpMessage; import org.springframework.http.MediaType; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.util.Assert; -import org.springframework.web.server.UnsupportedMediaTypeStatusException; /** * Implementations of {@link BodyExtractor} that read various bodies, such a reactive streams. @@ -48,7 +47,7 @@ public abstract class BodyExtractors { * @param the element type * @return a {@code BodyExtractor} that reads a mono */ - public static BodyExtractor> toMono(Class elementClass) { + public static BodyExtractor, ReactiveHttpInputMessage> toMono(Class elementClass) { Assert.notNull(elementClass, "'elementClass' must not be null"); return toMono(ResolvableType.forClass(elementClass)); } @@ -59,9 +58,9 @@ public abstract class BodyExtractors { * @param the element type * @return a {@code BodyExtractor} that reads a mono */ - public static BodyExtractor> toMono(ResolvableType elementType) { + public static BodyExtractor, ReactiveHttpInputMessage> toMono(ResolvableType elementType) { Assert.notNull(elementType, "'elementType' must not be null"); - return (request, strategies) -> readWithMessageReaders(request, strategies, + return (request, context) -> readWithMessageReaders(request, context, elementType, reader -> reader.readMono(elementType, request, Collections.emptyMap()), Mono::error); @@ -73,7 +72,7 @@ public abstract class BodyExtractors { * @param the element type * @return a {@code BodyExtractor} that reads a mono */ - public static BodyExtractor> toFlux(Class elementClass) { + public static BodyExtractor, ReactiveHttpInputMessage> toFlux(Class elementClass) { Assert.notNull(elementClass, "'elementClass' must not be null"); return toFlux(ResolvableType.forClass(elementClass)); } @@ -84,23 +83,23 @@ public abstract class BodyExtractors { * @param the element type * @return a {@code BodyExtractor} that reads a mono */ - public static BodyExtractor> toFlux(ResolvableType elementType) { + public static BodyExtractor, ReactiveHttpInputMessage> toFlux(ResolvableType elementType) { Assert.notNull(elementType, "'elementType' must not be null"); - return (request, strategies) -> readWithMessageReaders(request, strategies, + return (request, context) -> readWithMessageReaders(request, context, elementType, reader -> reader.read(elementType, request, Collections.emptyMap()), Flux::error); } private static > S readWithMessageReaders( - ServerHttpRequest request, - StrategiesSupplier strategies, + ReactiveHttpInputMessage inputMessage, + BodyExtractor.Context context, ResolvableType elementType, Function, S> readerFunction, Function unsupportedError) { - MediaType contentType = contentType(request); - Supplier>> messageReaders = strategies.messageReaders(); + MediaType contentType = contentType(inputMessage); + Supplier>> messageReaders = context.messageReaders(); return messageReaders.get() .filter(r -> r.canRead(elementType, contentType)) .findFirst() @@ -110,14 +109,14 @@ public abstract class BodyExtractors { List supportedMediaTypes = messageReaders.get() .flatMap(reader -> reader.getReadableMediaTypes().stream()) .collect(Collectors.toList()); - UnsupportedMediaTypeStatusException error = - new UnsupportedMediaTypeStatusException(contentType, supportedMediaTypes); + UnsupportedMediaTypeException error = + new UnsupportedMediaTypeException(contentType, supportedMediaTypes); return unsupportedError.apply(error); }); } - private static MediaType contentType(ServerHttpRequest request) { - MediaType result = request.getHeaders().getContentType(); + private static MediaType contentType(HttpMessage message) { + MediaType result = message.getHeaders().getContentType(); return result != null ? result : MediaType.APPLICATION_OCTET_STREAM; } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserter.java b/spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java similarity index 61% rename from spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserter.java rename to spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java index 116166f8e4..e287e09506 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java @@ -14,35 +14,32 @@ * limitations under the License. */ -package org.springframework.web.reactive.function; +package org.springframework.http.codec; import java.util.function.BiFunction; import java.util.function.Supplier; +import java.util.stream.Stream; import reactor.core.publisher.Mono; -import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.util.Assert; /** - * A component that can insert data into a {@link Response} body. + * A combination of functions that can populate a {@link ReactiveHttpOutputMessage} body. * - * @param the type of data to insert * @author Arjen Poutsma * @since 5.0 - * @see Response#body() - * @see Response.BodyBuilder#body(BodyInserter) - * @see BodyInserters */ -public interface BodyInserter { +public interface BodyInserter { /** * Insert into the given response. - * @param response the response to insert into - * @param strategies the strategies to use + * @param outputMessage the response to insert into + * @param context the context to use * @return a {@code Mono} that indicates completion or error */ - Mono insert(ServerHttpResponse response, StrategiesSupplier strategies); + Mono insert(M outputMessage, Context context); /** * Return the type contained in the body. @@ -50,7 +47,6 @@ public interface BodyInserter { */ T t(); - /** * Return a new {@code BodyInserter} described by the given writer and supplier functions. * @param writer the writer function for the new inserter @@ -58,13 +54,29 @@ public interface BodyInserter { * @param the type supplied and written by the inserter * @return the new {@code BodyInserter} */ - static BodyInserter of(BiFunction> writer, + static BodyInserter of( + BiFunction> writer, Supplier supplier) { Assert.notNull(writer, "'writer' must not be null"); Assert.notNull(supplier, "'supplier' must not be null"); - return new BodyInserters.DefaultBodyInserter(writer, supplier); + return new BodyInserters.DefaultBodyInserter(writer, supplier); } + /** + * Defines the context used during the insertion. + */ + interface Context { + + /** + * Supply a {@linkplain Stream stream} of {@link HttpMessageWriter}s to be used for response + * body conversion. + * @return the stream of message writers + */ + Supplier>> messageWriters(); + + } + + } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserters.java b/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java similarity index 77% rename from spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserters.java rename to spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java index c698c07a67..df2d654c33 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/BodyInserters.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java @@ -14,23 +14,22 @@ * limitations under the License. */ -package org.springframework.web.reactive.function; +package org.springframework.http.codec; import java.util.Collections; +import java.util.List; import java.util.function.BiFunction; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import org.springframework.core.ResolvableType; import org.springframework.core.io.Resource; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.http.codec.ResourceHttpMessageWriter; -import org.springframework.http.codec.ServerSentEvent; -import org.springframework.http.codec.ServerSentEventHttpMessageWriter; +import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; @@ -61,10 +60,10 @@ public abstract class BodyInserters { * @param body the body of the response * @return a {@code BodyInserter} that writes a single object */ - public static BodyInserter fromObject(T body) { + public static BodyInserter fromObject(T body) { Assert.notNull(body, "'body' must not be null"); return BodyInserter.of( - (response, strategies) -> writeWithMessageWriters(response, strategies, + (response, context) -> writeWithMessageWriters(response, context, Mono.just(body), ResolvableType.forInstance(body)), () -> body); } @@ -74,10 +73,10 @@ public abstract class BodyInserters { * @param publisher the publisher to stream to the response body * @param elementClass the class of elements contained in the publisher * @param the type of the elements contained in the publisher - * @param the type of the {@code Publisher}. + * @param the type of the {@code Publisher} * @return a {@code BodyInserter} that writes a {@code Publisher} */ - public static , T> BodyInserter fromPublisher(S publisher, + public static , T> BodyInserter fromPublisher(S publisher, Class elementClass) { Assert.notNull(publisher, "'publisher' must not be null"); @@ -90,16 +89,16 @@ public abstract class BodyInserters { * @param publisher the publisher to stream to the response body * @param elementType the type of elements contained in the publisher * @param the type of the elements contained in the publisher - * @param the type of the {@code Publisher}. + * @param the type of the {@code Publisher} * @return a {@code BodyInserter} that writes a {@code Publisher} */ - public static , T> BodyInserter fromPublisher(S publisher, + public static , T> BodyInserter fromPublisher(S publisher, ResolvableType elementType) { Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(elementType, "'elementType' must not be null"); return BodyInserter.of( - (response, strategies) -> writeWithMessageWriters(response, strategies, + (response, context) -> writeWithMessageWriters(response, context, publisher, elementType), () -> publisher ); @@ -114,10 +113,10 @@ public abstract class BodyInserters { * @param the type of the {@code Resource} * @return a {@code BodyInserter} that writes a {@code Publisher} */ - public static BodyInserter fromResource(T resource) { + public static BodyInserter fromResource(T resource) { Assert.notNull(resource, "'resource' must not be null"); return BodyInserter.of( - (response, strategies) -> { + (response, context) -> { ResourceHttpMessageWriter messageWriter = new ResourceHttpMessageWriter(); MediaType contentType = response.getHeaders().getContentType(); return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, contentType, @@ -134,12 +133,12 @@ public abstract class BodyInserters { * @return a {@code BodyInserter} that writes a {@code ServerSentEvent} publisher * @see Server-Sent Events W3C recommendation */ - public static >> BodyInserter fromServerSentEvents( + public static >> BodyInserter fromServerSentEvents( S eventsPublisher) { Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null"); return BodyInserter.of( - (response, strategies) -> { + (response, context) -> { ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter(); MediaType contentType = response.getHeaders().getContentType(); return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE, @@ -159,7 +158,7 @@ public abstract class BodyInserters { * Server-Sent Events * @see Server-Sent Events W3C recommendation */ - public static > BodyInserter fromServerSentEvents(S eventsPublisher, + public static > BodyInserter fromServerSentEvents(S eventsPublisher, Class eventClass) { Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null"); @@ -177,13 +176,13 @@ public abstract class BodyInserters { * Server-Sent Events * @see Server-Sent Events W3C recommendation */ - public static > BodyInserter fromServerSentEvents(S eventsPublisher, + public static > BodyInserter fromServerSentEvents(S eventsPublisher, ResolvableType eventType) { Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null"); Assert.notNull(eventType, "'eventType' must not be null"); return BodyInserter.of( - (response, strategies) -> { + (response, context) -> { ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter(); MediaType contentType = response.getHeaders().getContentType(); return messageWriter.write(eventsPublisher, eventType, contentType, response, @@ -200,23 +199,27 @@ public abstract class BodyInserters { new ServerSentEventHttpMessageWriter(); } - private static Mono writeWithMessageWriters(ServerHttpResponse response, - StrategiesSupplier strategies, + private static Mono writeWithMessageWriters(ReactiveHttpOutputMessage outputMessage, + BodyInserter.Context context, Publisher body, ResolvableType bodyType) { - // TODO: use ContentNegotiatingResultHandlerSupport - MediaType contentType = response.getHeaders().getContentType(); - return strategies.messageWriters().get() + MediaType contentType = outputMessage.getHeaders().getContentType(); + Supplier>> messageWriters = context.messageWriters(); + return messageWriters.get() .filter(messageWriter -> messageWriter.canWrite(bodyType, contentType)) .findFirst() .map(BodyInserters::cast) .map(messageWriter -> messageWriter - .write(body, bodyType, contentType, response, Collections + .write(body, bodyType, contentType, outputMessage, Collections .emptyMap())) .orElseGet(() -> { - response.setStatusCode(HttpStatus.NOT_ACCEPTABLE); - return response.setComplete(); + List supportedMediaTypes = messageWriters.get() + .flatMap(reader -> reader.getWritableMediaTypes().stream()) + .collect(Collectors.toList()); + UnsupportedMediaTypeException error = + new UnsupportedMediaTypeException(contentType, supportedMediaTypes); + return Mono.error(error); }); } @@ -225,22 +228,23 @@ public abstract class BodyInserters { return (HttpMessageWriter) messageWriter; } - static class DefaultBodyInserter implements BodyInserter { + static class DefaultBodyInserter + implements BodyInserter { - private final BiFunction> writer; + private final BiFunction> writer; private final Supplier supplier; public DefaultBodyInserter( - BiFunction> writer, + BiFunction> writer, Supplier supplier) { this.writer = writer; this.supplier = supplier; } @Override - public Mono insert(ServerHttpResponse response, StrategiesSupplier strategies) { - return this.writer.apply(response, strategies); + public Mono insert(M outputMessage, Context context) { + return this.writer.apply(outputMessage, context); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/codec/UnsupportedMediaTypeException.java b/spring-web/src/main/java/org/springframework/http/codec/UnsupportedMediaTypeException.java new file mode 100644 index 0000000000..ca38457d32 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/codec/UnsupportedMediaTypeException.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.codec; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.http.MediaType; + +/** + * Exception thrown to indicate that a {@code Content-Type} is not supported. + * + * @author Arjen Poutsma + * @since 5.0 + */ +@SuppressWarnings("serial") +public class UnsupportedMediaTypeException extends NestedRuntimeException { + + private final MediaType contentType; + + private final List supportedMediaTypes; + + + /** + * Constructor for when the specified Content-Type is invalid. + */ + public UnsupportedMediaTypeException(String reason) { + super(reason); + this.contentType = null; + this.supportedMediaTypes = Collections.emptyList(); + } + + /** + * Constructor for when the Content-Type can be parsed but is not supported. + */ + public UnsupportedMediaTypeException(MediaType contentType, List supportedMediaTypes) { + super("Content type '" + contentType + "' not supported"); + this.contentType = contentType; + this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes); + } + + + /** + * Return the request Content-Type header if it was parsed successfully. + */ + public Optional getContentType() { + return Optional.ofNullable(this.contentType); + } + + /** + * Return the list of supported content types in cases when the Content-Type + * header is parsed but not supported, or an empty list otherwise. + */ + public List getSupportedMediaTypes() { + return this.supportedMediaTypes; + } + +} diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java b/spring-web/src/test/java/org/springframework/http/codec/BodyExtractorsTests.java similarity index 57% rename from spring-web-reactive/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java rename to spring-web/src/test/java/org/springframework/http/codec/BodyExtractorsTests.java index ba5f450bea..b7ae0b39f5 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/BodyExtractorsTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/BodyExtractorsTests.java @@ -14,31 +14,60 @@ * limitations under the License. */ -package org.springframework.web.reactive.function; +package org.springframework.http.codec; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.core.codec.ByteBufferDecoder; +import org.springframework.core.codec.StringDecoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpInputMessage; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.tests.TestSubscriber; -import org.springframework.web.server.UnsupportedMediaTypeStatusException; /** * @author Arjen Poutsma */ public class BodyExtractorsTests { + private BodyExtractor.Context context; + + @Before + public void createContext() { + final List> messageReaders = new ArrayList<>(); + messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); + messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder())); + messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); + messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); + + this.context = new BodyExtractor.Context() { + @Override + public Supplier>> messageReaders() { + return messageReaders::stream; + } + }; + + } + @Test public void toMono() throws Exception { - BodyExtractor> extractor = BodyExtractors.toMono(String.class); + BodyExtractor, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class); DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); DefaultDataBuffer dataBuffer = @@ -48,9 +77,7 @@ public class BodyExtractorsTests { MockServerHttpRequest request = new MockServerHttpRequest(); request.setBody(body); - StrategiesSupplier strategies = StrategiesSupplier.builder().build(); - - Mono result = extractor.extract(request, strategies); + Mono result = extractor.extract(request, this.context); TestSubscriber.subscribe(result) .assertComplete() @@ -59,7 +86,7 @@ public class BodyExtractorsTests { @Test public void toFlux() throws Exception { - BodyExtractor> extractor = BodyExtractors.toFlux(String.class); + BodyExtractor, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class); DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); DefaultDataBuffer dataBuffer = @@ -69,9 +96,7 @@ public class BodyExtractorsTests { MockServerHttpRequest request = new MockServerHttpRequest(); request.setBody(body); - StrategiesSupplier strategies = StrategiesSupplier.builder().build(); - - Flux result = extractor.extract(request, strategies); + Flux result = extractor.extract(request, this.context); TestSubscriber.subscribe(result) .assertComplete() .assertValues("foo"); @@ -79,7 +104,7 @@ public class BodyExtractorsTests { @Test public void toFluxUnacceptable() throws Exception { - BodyExtractor> extractor = BodyExtractors.toFlux(String.class); + BodyExtractor, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class); DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); DefaultDataBuffer dataBuffer = @@ -90,12 +115,16 @@ public class BodyExtractorsTests { request.getHeaders().setContentType(MediaType.APPLICATION_JSON); request.setBody(body); - StrategiesSupplier strategies = StrategiesSupplier.empty().build(); + BodyExtractor.Context emptyContext = new BodyExtractor.Context() { + @Override + public Supplier>> messageReaders() { + return () -> Collections.>emptySet().stream(); + } + }; - Flux result = extractor.extract(request, strategies); + Flux result = extractor.extract(request, emptyContext); TestSubscriber.subscribe(result) - .assertError(UnsupportedMediaTypeStatusException.class); - + .assertError(UnsupportedMediaTypeException.class); } } \ No newline at end of file diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/BodyInsertersTests.java b/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java similarity index 65% rename from spring-web-reactive/src/test/java/org/springframework/web/reactive/function/BodyInsertersTests.java rename to spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java index 277a32f5db..622f26dd36 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/BodyInsertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java @@ -14,20 +14,30 @@ * limitations under the License. */ -package org.springframework.web.reactive.function; +package org.springframework.http.codec; import java.nio.ByteBuffer; import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.core.codec.ByteBufferEncoder; +import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.http.codec.ServerSentEvent; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.codec.xml.Jaxb2XmlEncoder; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; import org.springframework.tests.TestSubscriber; @@ -40,15 +50,35 @@ import static org.junit.Assert.assertEquals; */ public class BodyInsertersTests { + private BodyInserter.Context context; + + @Before + public void createContext() { + final List> messageWriters = new ArrayList<>(); + messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); + messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); + messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); + messageWriters.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); + + this.context = new BodyInserter.Context() { + @Override + public Supplier>> messageWriters() { + return messageWriters::stream; + } + }; + + } + + @Test public void ofObject() throws Exception { String body = "foo"; - BodyInserter inserter = BodyInserters.fromObject(body); + BodyInserter inserter = BodyInserters.fromObject(body); assertEquals(body, inserter.t()); MockServerHttpResponse response = new MockServerHttpResponse(); - Mono result = inserter.insert(response, StrategiesSupplier.builder().build()); + Mono result = inserter.insert(response, this.context); TestSubscriber.subscribe(result) .assertComplete(); @@ -62,12 +92,12 @@ public class BodyInsertersTests { @Test public void ofPublisher() throws Exception { Flux body = Flux.just("foo"); - BodyInserter> inserter = BodyInserters.fromPublisher(body, String.class); + BodyInserter, ReactiveHttpOutputMessage> inserter = BodyInserters.fromPublisher(body, String.class); assertEquals(body, inserter.t()); MockServerHttpResponse response = new MockServerHttpResponse(); - Mono result = inserter.insert(response, StrategiesSupplier.builder().build()); + Mono result = inserter.insert(response, this.context); TestSubscriber.subscribe(result) .assertComplete(); @@ -81,12 +111,12 @@ public class BodyInsertersTests { @Test public void ofResource() throws Exception { Resource body = new ClassPathResource("response.txt", getClass()); - BodyInserter inserter = BodyInserters.fromResource(body); + BodyInserter inserter = BodyInserters.fromResource(body); assertEquals(body, inserter.t()); MockServerHttpResponse response = new MockServerHttpResponse(); - Mono result = inserter.insert(response, StrategiesSupplier.builder().build()); + Mono result = inserter.insert(response, this.context); TestSubscriber.subscribe(result) .assertComplete(); @@ -105,13 +135,13 @@ public class BodyInsertersTests { public void ofServerSentEventFlux() throws Exception { ServerSentEvent event = ServerSentEvent.builder("foo").build(); Flux> body = Flux.just(event); - BodyInserter>> inserter = + BodyInserter>, ServerHttpResponse> inserter = BodyInserters.fromServerSentEvents(body); assertEquals(body, inserter.t()); MockServerHttpResponse response = new MockServerHttpResponse(); - Mono result = inserter.insert(response, StrategiesSupplier.builder().build()); + Mono result = inserter.insert(response, this.context); TestSubscriber.subscribe(result) .assertComplete(); @@ -120,13 +150,13 @@ public class BodyInsertersTests { @Test public void ofServerSentEventClass() throws Exception { Flux body = Flux.just("foo"); - BodyInserter> inserter = + BodyInserter, ServerHttpResponse> inserter = BodyInserters.fromServerSentEvents(body, String.class); assertEquals(body, inserter.t()); MockServerHttpResponse response = new MockServerHttpResponse(); - Mono result = inserter.insert(response, StrategiesSupplier.builder().build()); + Mono result = inserter.insert(response, this.context); TestSubscriber.subscribe(result) .assertComplete(); diff --git a/spring-web-reactive/src/test/resources/org/springframework/web/reactive/function/response.txt b/spring-web/src/test/resources/org/springframework/http/codec/response.txt similarity index 100% rename from spring-web-reactive/src/test/resources/org/springframework/web/reactive/function/response.txt rename to spring-web/src/test/resources/org/springframework/http/codec/response.txt