From 582e625fcf340f76964888e93d1ba19df5742f2b Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 7 Dec 2016 09:34:57 +0100 Subject: [PATCH] Allow HandlerFunction to return Mono This commit makes it possible for handler functions to return asynchronous status codes and headers, by making HandlerFunction.handle return a Mono instead of a ServerResponse. As a consequence, all other types that deal with HandlerFunctions (RouterFunction, HandlerFilterFunction, etc.) had to change as well. However, when combining the above change with method references (a very typical use case), resulting signatures would have been something like: ``` public Mono>> getPerson(ServerRequest request) ``` which was too ugly to consider, especially the two uses of Mono. It was considered to merge ServerResponse with the last Mono, essentialy making ServerResponse always contain a Publisher, but this had unfortunate consequences in view rendering. It was therefore decided to drop the parameterization of ServerResponse, as the only usage of the extra type information was to manipulate the response objects in a filter. Even before the above change this was suggested; it just made the change even more necessary. As a consequence, `BodyInserter` could be turned into a real `FunctionalInterface`, which resulted in changes in ClientRequest. We did, however, make HandlerFunction.handle return a `Mono`, adding little complexity, but allowing for future `ServerResponse` subtypes that do expose type information, if it's needed. For instance, a RenderingResponse could expose the view name and model. Issue: SPR-14870 --- .../DefaultServerResponseBuilder.java | 98 ++++--- .../function/HandlerFilterFunction.java | 16 +- .../reactive/function/HandlerFunction.java | 6 +- .../function/PathResourceLookupFunction.java | 15 +- .../web/reactive/function/Rendering.java | 40 --- .../function/ResourceHandlerFunction.java | 6 +- .../web/reactive/function/RouterFunction.java | 30 +-- .../reactive/function/RouterFunctions.java | 44 +-- .../web/reactive/function/ServerResponse.java | 48 ++-- .../support/HandlerFunctionAdapter.java | 7 +- .../support/ServerResponseResultHandler.java | 4 +- .../DefaultServerResponseBuilderTests.java | 255 ++++++++++++------ .../DispatcherHandlerIntegrationTests.java | 24 +- .../reactive/function/MockServerRequest.java | 68 ++--- .../PathResourceLookupFunctionTests.java | 56 ++-- ...lisherHandlerFunctionIntegrationTests.java | 7 +- .../ResourceHandlerFunctionTests.java | 46 ++-- .../function/RouterFunctionTests.java | 91 ++++--- .../function/RouterFunctionsTests.java | 75 +++--- .../SseHandlerFunctionIntegrationTests.java | 7 +- .../http/codec/BodyExtractor.java | 2 +- .../http/codec/BodyInserter.java | 28 +- .../http/codec/BodyInserters.java | 87 ++---- .../web/client/reactive/ClientRequest.java | 5 - .../reactive/DefaultClientRequestBuilder.java | 9 +- .../http/codec/BodyInsertersTests.java | 13 - .../DefaultClientRequestBuilderTests.java | 8 +- 27 files changed, 570 insertions(+), 525 deletions(-) delete mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/function/Rendering.java diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultServerResponseBuilder.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultServerResponseBuilder.java index 5a240842022..861bb7a9871 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultServerResponseBuilder.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultServerResponseBuilder.java @@ -21,11 +21,13 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -150,43 +152,54 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { } @Override - public ServerResponse build() { - return body(BodyInserters.empty()); + public Mono build() { + return build((exchange, handlerStrategies) -> exchange.getResponse().setComplete()); } @Override - public > ServerResponse build(T voidPublisher) { + public Mono build(Publisher voidPublisher) { Assert.notNull(voidPublisher, "'voidPublisher' must not be null"); - return body(BodyInserter.of( - (response, context) -> Flux.from(voidPublisher).thenEmpty(response.setComplete()), - () -> null)); + return build((exchange, handlerStrategies) -> + Mono.from(voidPublisher).then(exchange.getResponse().setComplete())); } @Override - public ServerResponse body(BodyInserter inserter) { - Assert.notNull(inserter, "'inserter' must not be null"); - return new BodyInserterServerResponse(this.statusCode, this.headers, inserter); + public Mono build( + BiFunction> writeFunction) { + + Assert.notNull(writeFunction, "'writeFunction' must not be null"); + return Mono.just(new WriterFunctionServerResponse(this.statusCode, this.headers, + writeFunction)); } @Override - public , T> ServerResponse body(S publisher, Class elementClass) { + public > Mono body(P publisher, + Class elementClass) { return body(BodyInserters.fromPublisher(publisher, elementClass)); } @Override - public ServerResponse render(String name, Object... modelAttributes) { + public Mono body(BodyInserter inserter) { + Assert.notNull(inserter, "'inserter' must not be null"); + return Mono + .just(new BodyInserterServerResponse(this.statusCode, this.headers, inserter)); + } + + @Override + public Mono render(String name, Object... modelAttributes) { Assert.hasLength(name, "'name' must not be empty"); return render(name, toModelMap(modelAttributes)); } @Override - public ServerResponse render(String name, Map model) { + public Mono render(String name, Map model) { Assert.hasLength(name, "'name' must not be empty"); Map modelMap = new LinkedHashMap<>(); if (model != null) { modelMap.putAll(model); } - return new RenderingServerResponse(this.statusCode, this.headers, name, modelMap); + return Mono + .just(new RenderingServerResponse(this.statusCode, this.headers, name, modelMap)); } private Map toModelMap(Object[] modelAttributes) { @@ -199,7 +212,7 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { } - static abstract class AbstractServerResponse implements ServerResponse { + private static abstract class AbstractServerResponse implements ServerResponse { private final HttpStatus statusCode; @@ -207,7 +220,13 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { protected AbstractServerResponse(HttpStatus statusCode, HttpHeaders headers) { this.statusCode = statusCode; - this.headers = HttpHeaders.readOnlyHttpHeaders(headers); + this.headers = readOnlyCopy(headers); + } + + private static HttpHeaders readOnlyCopy(HttpHeaders headers) { + HttpHeaders copy = new HttpHeaders(); + copy.putAll(headers); + return HttpHeaders.readOnlyHttpHeaders(copy); } @Override @@ -233,8 +252,27 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { } } + private static final class WriterFunctionServerResponse extends AbstractServerResponse { + + private final BiFunction> writeFunction; + + public WriterFunctionServerResponse(HttpStatus statusCode, + HttpHeaders headers, + BiFunction> writeFunction) { + super(statusCode, headers); + this.writeFunction = writeFunction; + } + + @Override + public Mono writeTo(ServerWebExchange exchange, HandlerStrategies strategies) { + writeStatusAndHeaders(exchange.getResponse()); + return this.writeFunction.apply(exchange, strategies); + } + } + + - private static final class BodyInserterServerResponse extends AbstractServerResponse { + private static final class BodyInserterServerResponse extends AbstractServerResponse { private final BodyInserter inserter; @@ -245,11 +283,6 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { this.inserter = inserter; } - @Override - public T body() { - return this.inserter.t(); - } - @Override public Mono writeTo(ServerWebExchange exchange, HandlerStrategies strategies) { ServerHttpResponse response = exchange.getResponse(); @@ -264,26 +297,19 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { } - private static final class RenderingServerResponse extends AbstractServerResponse { + private static final class RenderingServerResponse extends AbstractServerResponse { private final String name; private final Map model; - private final Rendering rendering; public RenderingServerResponse(HttpStatus statusCode, HttpHeaders headers, String name, Map model) { super(statusCode, headers); this.name = name; - this.model = model; - this.rendering = new DefaultRendering(); - } - - @Override - public Rendering body() { - return this.rendering; + this.model = Collections.unmodifiableMap(model); } @Override @@ -301,18 +327,6 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { .then(view -> view.render(this.model, contentType, exchange)); } - private class DefaultRendering implements Rendering { - - @Override - public String name() { - return name; - } - - @Override - public Map model() { - return model; - } - } } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFilterFunction.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFilterFunction.java index 7e17b96abf3..7e367b1340a 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFilterFunction.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFilterFunction.java @@ -18,6 +18,8 @@ package org.springframework.web.reactive.function; import java.util.function.Function; +import reactor.core.publisher.Mono; + import org.springframework.util.Assert; import org.springframework.web.reactive.function.support.ServerRequestWrapper; @@ -31,7 +33,7 @@ import org.springframework.web.reactive.function.support.ServerRequestWrapper; * @see RouterFunction#filter(HandlerFilterFunction) */ @FunctionalInterface -public interface HandlerFilterFunction { +public interface HandlerFilterFunction { /** * Apply this filter to the given handler function. The given @@ -44,7 +46,7 @@ public interface HandlerFilterFunction { * @return the filtered response * @see ServerRequestWrapper */ - ServerResponse filter(ServerRequest request, HandlerFunction next); + Mono filter(ServerRequest request, HandlerFunction next); /** * Return a composed filter function that first applies this filter, and then applies the @@ -79,10 +81,10 @@ public interface HandlerFilterFunction { * @return the filter adaptation of the request processor */ static HandlerFilterFunction ofRequestProcessor(Function requestProcessor) { + Mono> requestProcessor) { Assert.notNull(requestProcessor, "'requestProcessor' must not be null"); - return (request, next) -> next.handle(requestProcessor.apply(request)); + return (request, next) -> requestProcessor.apply(request).then(next::handle); } /** @@ -91,11 +93,11 @@ public interface HandlerFilterFunction { * @param responseProcessor the response processor * @return the filter adaptation of the request processor */ - static HandlerFilterFunction ofResponseProcessor(Function, - ServerResponse> responseProcessor) { + static HandlerFilterFunction ofResponseProcessor(Function responseProcessor) { Assert.notNull(responseProcessor, "'responseProcessor' must not be null"); - return (request, next) -> responseProcessor.apply(next.handle(request)); + return (request, next) -> next.handle(request).map(responseProcessor); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFunction.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFunction.java index 64c61683feb..be0643e7f43 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFunction.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/HandlerFunction.java @@ -16,6 +16,8 @@ package org.springframework.web.reactive.function; +import reactor.core.publisher.Mono; + /** * Represents a function that handles a {@linkplain ServerRequest request}. * @@ -24,13 +26,13 @@ package org.springframework.web.reactive.function; * @since 5.0 */ @FunctionalInterface -public interface HandlerFunction { +public interface HandlerFunction { /** * Handle the given request. * @param request the request to handle * @return the response */ - ServerResponse handle(ServerRequest request); + Mono handle(ServerRequest request); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/PathResourceLookupFunction.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/PathResourceLookupFunction.java index cef07a83475..7b094aaeb52 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/PathResourceLookupFunction.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/PathResourceLookupFunction.java @@ -19,9 +19,10 @@ package org.springframework.web.reactive.function; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; -import java.util.Optional; import java.util.function.Function; +import reactor.core.publisher.Mono; + import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; @@ -37,7 +38,7 @@ import org.springframework.web.util.UriUtils; * @author Arjen Poutsma * @since 5.0 */ -class PathResourceLookupFunction implements Function> { +class PathResourceLookupFunction implements Function> { private static final PathMatcher PATH_MATCHER = new AntPathMatcher(); @@ -51,16 +52,16 @@ class PathResourceLookupFunction implements Function apply(ServerRequest request) { + public Mono apply(ServerRequest request) { String path = processPath(request.path()); if (path.contains("%")) { path = UriUtils.decode(path, StandardCharsets.UTF_8); } if (!StringUtils.hasLength(path) || isInvalidPath(path)) { - return Optional.empty(); + return Mono.empty(); } if (!PATH_MATCHER.match(this.pattern, path)) { - return Optional.empty(); + return Mono.empty(); } else { path = PATH_MATCHER.extractPathWithinPattern(this.pattern, path); @@ -68,10 +69,10 @@ class PathResourceLookupFunction implements Function model(); - - -} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ResourceHandlerFunction.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ResourceHandlerFunction.java index c7a2964660a..de2811fdf05 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ResourceHandlerFunction.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ResourceHandlerFunction.java @@ -25,6 +25,8 @@ import java.net.URL; import java.util.EnumSet; import java.util.Set; +import reactor.core.publisher.Mono; + import org.springframework.core.io.Resource; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -34,7 +36,7 @@ import org.springframework.http.codec.BodyInserters; * @author Arjen Poutsma * @since 5.0 */ -class ResourceHandlerFunction implements HandlerFunction { +class ResourceHandlerFunction implements HandlerFunction { private static final Set SUPPORTED_METHODS = @@ -48,7 +50,7 @@ class ResourceHandlerFunction implements HandlerFunction { } @Override - public ServerResponse handle(ServerRequest request) { + public Mono handle(ServerRequest request) { switch (request.method()) { case GET: return ServerResponse.ok() diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunction.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunction.java index fa80fd0ce99..6902c6af95e 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunction.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunction.java @@ -18,6 +18,8 @@ package org.springframework.web.reactive.function; import java.util.Optional; +import reactor.core.publisher.Mono; + /** * Represents a function that routes to a {@linkplain HandlerFunction handler function}. * @@ -27,30 +29,27 @@ import java.util.Optional; * @see RouterFunctions */ @FunctionalInterface -public interface RouterFunction { +public interface RouterFunction { /** * Return the {@linkplain HandlerFunction handler function} that matches the given request. * @param request the request to route to - * @return an {@code Optional} describing the {@code HandlerFunction} that matches this request, - * or an empty {@code Optional} if there is no match + * @return an {@code Mono} describing the {@code HandlerFunction} that matches this request, + * or an empty {@code Mono} if there is no match */ - Optional> route(ServerRequest request); + Mono> route(ServerRequest request); /** * Return a composed routing function that first invokes this function, * and then invokes the {@code other} function (of the same type {@code T}) if this route had - * {@linkplain Optional#empty() no result}. + * {@linkplain Mono#empty() no result}. * * @param other the function of type {@code T} to apply when this function has no result * @return a composed function that first routes with this function and then the {@code other} function if this * function has no result */ default RouterFunction andSame(RouterFunction other) { - return request -> { - Optional> result = this.route(request); - return result.isPresent() ? result : other.route(request); - }; + return request -> this.route(request).otherwiseIfEmpty(other.route(request)); } /** @@ -63,12 +62,9 @@ public interface RouterFunction { * function has no result */ default RouterFunction and(RouterFunction other) { - return request -> { - Optional> result = this.route(request). - map(RouterFunctions::cast); - return result.isPresent() ? result : other.route(request) - .map(RouterFunctions::cast); - }; + return request -> this.route(request) + .map(RouterFunctions::cast) + .otherwiseIfEmpty(other.route(request).map(RouterFunctions::cast)); } /** @@ -83,7 +79,7 @@ public interface RouterFunction { * created from {@code predicate} and {@code handlerFunction} if this * function has no result */ - default RouterFunction andRoute(RequestPredicate predicate, + default RouterFunction andRoute(RequestPredicate predicate, HandlerFunction handlerFunction) { return and(RouterFunctions.route(predicate, handlerFunction)); } @@ -96,7 +92,7 @@ public interface RouterFunction { * @param the filter return type * @return the filtered routing function */ - default RouterFunction filter(HandlerFilterFunction filterFunction) { + default RouterFunction filter(HandlerFilterFunction filterFunction) { return request -> this.route(request).map(filterFunction::apply); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunctions.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunctions.java index bff092c87a5..4d112cd1d8e 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunctions.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/RouterFunctions.java @@ -17,7 +17,6 @@ package org.springframework.web.reactive.function; import java.util.Map; -import java.util.Optional; import java.util.function.Function; import reactor.core.publisher.Mono; @@ -63,7 +62,7 @@ public abstract class RouterFunctions { public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = RouterFunctions.class.getName() + ".uriTemplateVariables"; - private static final HandlerFunction NOT_FOUND_HANDLER = request -> ServerResponse.notFound().build(); + private static final HandlerFunction NOT_FOUND_HANDLER = request -> ServerResponse.notFound().build(); /** @@ -75,11 +74,13 @@ public abstract class RouterFunctions { * {@code predicate} evaluates to {@code true} * @see RequestPredicates */ - public static RouterFunction route(RequestPredicate predicate, HandlerFunction handlerFunction) { + public static RouterFunction route(RequestPredicate predicate, + HandlerFunction handlerFunction) { + Assert.notNull(predicate, "'predicate' must not be null"); Assert.notNull(handlerFunction, "'handlerFunction' must not be null"); - return request -> predicate.test(request) ? Optional.of(handlerFunction) : Optional.empty(); + return request -> predicate.test(request) ? Mono.just(handlerFunction) : Mono.empty(); } /** @@ -91,7 +92,9 @@ public abstract class RouterFunctions { * {@code predicate} evaluates to {@code true} * @see RequestPredicates */ - public static RouterFunction subroute(RequestPredicate predicate, RouterFunction routerFunction) { + public static RouterFunction subroute(RequestPredicate predicate, + RouterFunction routerFunction) { + Assert.notNull(predicate, "'predicate' must not be null"); Assert.notNull(routerFunction, "'routerFunction' must not be null"); @@ -101,7 +104,7 @@ public abstract class RouterFunctions { return routerFunction.route(subRequest); } else { - return Optional.empty(); + return Mono.empty(); } }; } @@ -117,7 +120,7 @@ public abstract class RouterFunctions { * @param location the location directory relative to which resources should be resolved * @return a router function that routes to resources */ - public static RouterFunction resources(String pattern, Resource location) { + public static RouterFunction resources(String pattern, Resource location) { Assert.hasLength(pattern, "'pattern' must not be empty"); Assert.notNull(location, "'location' must not be null"); @@ -131,12 +134,10 @@ public abstract class RouterFunctions { * @param lookupFunction the function to provide a {@link Resource} given the {@link ServerRequest} * @return a router function that routes to resources */ - public static RouterFunction resources(Function> lookupFunction) { + public static RouterFunction resources(Function> lookupFunction) { Assert.notNull(lookupFunction, "'lookupFunction' must not be null"); - // TODO: make lookupFunction return Mono once SPR-14870 is resolved return request -> lookupFunction.apply(request).map(ResourceHandlerFunction::new); - } /** @@ -190,9 +191,10 @@ public abstract class RouterFunctions { return new HttpWebHandlerAdapter(exchange -> { ServerRequest request = new DefaultServerRequest(exchange, strategies); addAttributes(exchange, request); - HandlerFunction handlerFunction = routerFunction.route(request).orElse(notFound()); - ServerResponse response = handlerFunction.handle(request); - return response.writeTo(exchange, strategies); + return routerFunction.route(request) + .defaultIfEmpty(notFound()) + .then(handlerFunction -> handlerFunction.handle(request)) + .then(response -> response.writeTo(exchange, strategies)); }); } @@ -225,11 +227,13 @@ public abstract class RouterFunctions { Assert.notNull(routerFunction, "RouterFunction must not be null"); Assert.notNull(strategies, "HandlerStrategies must not be null"); - return exchange -> { - ServerRequest request = new DefaultServerRequest(exchange, strategies); - addAttributes(exchange, request); - Optional> route = routerFunction.route(request); - return Mono.justOrEmpty(route); + return new HandlerMapping() { + @Override + public Mono getHandler(ServerWebExchange exchange) { + ServerRequest request = new DefaultServerRequest(exchange, strategies); + addAttributes(exchange, request); + return routerFunction.route(request).map(handlerFunction -> (Object)handlerFunction); + } }; } @@ -240,12 +244,12 @@ public abstract class RouterFunctions { } @SuppressWarnings("unchecked") - private static HandlerFunction notFound() { + private static HandlerFunction notFound() { return (HandlerFunction) NOT_FOUND_HANDLER; } @SuppressWarnings("unchecked") - static HandlerFunction cast(HandlerFunction handlerFunction) { + static HandlerFunction cast(HandlerFunction handlerFunction) { return (HandlerFunction) handlerFunction; } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ServerResponse.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ServerResponse.java index 7293ac98e04..4e883a0bf6a 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ServerResponse.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/ServerResponse.java @@ -21,6 +21,7 @@ import java.time.ZonedDateTime; import java.util.Collection; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -42,9 +43,8 @@ import org.springframework.web.server.ServerWebExchange; * * @author Arjen Poutsma * @since 5.0 - * @param the type of the body that this response contains */ -public interface ServerResponse { +public interface ServerResponse { // Instance methods @@ -58,11 +58,6 @@ public interface ServerResponse { */ HttpHeaders headers(); - /** - * Return the body of this response. - */ - T body(); - /** * Writes this response to the given web exchange. * @@ -80,7 +75,7 @@ public interface ServerResponse { * @param other the response to copy the status and headers from * @return the created builder */ - static BodyBuilder from(ServerResponse other) { + static BodyBuilder from(ServerResponse other) { Assert.notNull(other, "'other' must not be null"); DefaultServerResponseBuilder builder = new DefaultServerResponseBuilder(other.statusCode()); return builder.headers(other.headers()); @@ -270,7 +265,7 @@ public interface ServerResponse { * * @return the built response */ - ServerResponse build(); + Mono build(); /** * Build the response entity with no body. @@ -279,7 +274,16 @@ public interface ServerResponse { * @param voidPublisher publisher publisher to indicate when the response should be committed * @return the built response */ - > ServerResponse build(T voidPublisher); + Mono build(Publisher voidPublisher); + + /** + * Build the response entity with a custom writer function. + * + * @param writeFunction the function used to write to the {@link ServerWebExchange} + * @return the built response + */ + Mono build(BiFunction> writeFunction); } @@ -308,14 +312,6 @@ public interface ServerResponse { */ BodyBuilder contentType(MediaType contentType); - /** - * Set the body of the response to the given {@code BodyInserter} and return it. - * @param inserter the {@code BodyInserter} that writes to the response - * @param the type contained in the body - * @return the built response - */ - ServerResponse body(BodyInserter inserter); - /** * Set the body of the response to the given {@code Publisher} and return it. This * convenience method combines {@link #body(BodyInserter)} and @@ -323,10 +319,18 @@ public interface ServerResponse { * @param publisher the {@code Publisher} to write to the response * @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 the built request */ - , T> ServerResponse body(S publisher, Class elementClass); + > Mono body(P publisher, Class elementClass); + + /** + * Set the body of the response to the given {@code BodyInserter} and return it. + * @param inserter the {@code BodyInserter} that writes to the response + * @param the type contained in the body + * @return the built response + */ + Mono body(BodyInserter inserter); /** * Render the template with the given {@code name} using the given {@code modelAttributes}. @@ -339,7 +343,7 @@ public interface ServerResponse { * @param modelAttributes the modelAttributes used to render the template * @return the built response */ - ServerResponse render(String name, Object... modelAttributes); + Mono render(String name, Object... modelAttributes); /** * Render the template with the given {@code name} using the given {@code model}. @@ -347,7 +351,7 @@ public interface ServerResponse { * @param model the model used to render the template * @return the built response */ - ServerResponse render(String name, Map model); + Mono render(String name, Map model); } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/HandlerFunctionAdapter.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/HandlerFunctionAdapter.java index 6daa7038153..96de8df7063 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/HandlerFunctionAdapter.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/HandlerFunctionAdapter.java @@ -26,7 +26,6 @@ import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.function.HandlerFunction; import org.springframework.web.reactive.function.RouterFunctions; import org.springframework.web.reactive.function.ServerRequest; -import org.springframework.web.reactive.function.ServerResponse; import org.springframework.web.server.ServerWebExchange; /** @@ -62,9 +61,7 @@ public class HandlerFunctionAdapter implements HandlerAdapter { .orElseThrow(() -> new IllegalStateException( "Could not find ServerRequest in exchange attributes")); - ServerResponse response = handlerFunction.handle(request); - HandlerResult handlerResult = - new HandlerResult(handlerFunction, response, HANDLER_FUNCTION_RETURN_TYPE); - return Mono.just(handlerResult); + return handlerFunction.handle(request) + .map(response -> new HandlerResult(handlerFunction, response, HANDLER_FUNCTION_RETURN_TYPE)); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/ServerResponseResultHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/ServerResponseResultHandler.java index 0257f6bdd40..a447fee3ba1 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/ServerResponseResultHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/support/ServerResponseResultHandler.java @@ -21,8 +21,8 @@ import reactor.core.publisher.Mono; import org.springframework.util.Assert; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResultHandler; -import org.springframework.web.reactive.function.ServerResponse; import org.springframework.web.reactive.function.HandlerStrategies; +import org.springframework.web.reactive.function.ServerResponse; import org.springframework.web.server.ServerWebExchange; /** @@ -59,7 +59,7 @@ public class ServerResponseResultHandler implements HandlerResultHandler { @Override public Mono handleResult(ServerWebExchange exchange, HandlerResult result) { - ServerResponse response = (ServerResponse) result.getReturnValue().orElseThrow( + ServerResponse response = (ServerResponse) result.getReturnValue().orElseThrow( IllegalStateException::new); return response.writeTo(exchange, this.strategies); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultServerResponseBuilderTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultServerResponseBuilderTests.java index 423b67ccb8b..f978ac0f593 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultServerResponseBuilderTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/DefaultServerResponseBuilderTests.java @@ -17,44 +17,26 @@ package org.springframework.web.reactive.function; import java.net.URI; -import java.nio.ByteBuffer; import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Supplier; +import java.util.Set; import org.junit.Test; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; -import org.springframework.core.codec.CharSequenceEncoder; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.CacheControl; 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; -import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; -import org.springframework.web.reactive.result.view.View; -import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.adapter.DefaultServerWebExchange; -import org.springframework.web.server.session.MockWebSessionManager; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -65,130 +47,203 @@ public class DefaultServerResponseBuilderTests { @Test public void from() throws Exception { - ServerResponse other = ServerResponse.ok().header("foo", "bar").build(); - ServerResponse result = ServerResponse.from(other).build(); - assertEquals(HttpStatus.OK, result.statusCode()); - assertEquals("bar", result.headers().getFirst("foo")); + ServerResponse other = ServerResponse.ok().header("foo", "bar").build().block(); + Mono result = ServerResponse.from(other).build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.OK.equals(response.statusCode()) && + "bar".equals(response.headers().getFirst("foo"))) + .expectComplete() + .verify(); } @Test public void status() throws Exception { - ServerResponse result = ServerResponse.status(HttpStatus.CREATED).build(); - assertEquals(HttpStatus.CREATED, result.statusCode()); + Mono result = ServerResponse.status(HttpStatus.CREATED).build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.CREATED.equals(response.statusCode())) + .expectComplete() + .verify(); } @Test public void ok() throws Exception { - ServerResponse result = ServerResponse.ok().build(); - assertEquals(HttpStatus.OK, result.statusCode()); + Mono result = ServerResponse.ok().build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.OK.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void created() throws Exception { URI location = URI.create("http://example.com"); - ServerResponse result = ServerResponse.created(location).build(); - assertEquals(HttpStatus.CREATED, result.statusCode()); - assertEquals(location, result.headers().getLocation()); + Mono result = ServerResponse.created(location).build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.CREATED.equals(response.statusCode()) && + location.equals(response.headers().getLocation())) + .expectComplete() + .verify(); } @Test public void accepted() throws Exception { - ServerResponse result = ServerResponse.accepted().build(); - assertEquals(HttpStatus.ACCEPTED, result.statusCode()); + Mono result = ServerResponse.accepted().build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.ACCEPTED.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void noContent() throws Exception { - ServerResponse result = ServerResponse.noContent().build(); - assertEquals(HttpStatus.NO_CONTENT, result.statusCode()); + Mono result = ServerResponse.noContent().build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.NO_CONTENT.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void badRequest() throws Exception { - ServerResponse result = ServerResponse.badRequest().build(); - assertEquals(HttpStatus.BAD_REQUEST, result.statusCode()); + Mono result = ServerResponse.badRequest().build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.BAD_REQUEST.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void notFound() throws Exception { - ServerResponse result = ServerResponse.notFound().build(); - assertEquals(HttpStatus.NOT_FOUND, result.statusCode()); + Mono result = ServerResponse.notFound().build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.NOT_FOUND.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void unprocessableEntity() throws Exception { - ServerResponse result = ServerResponse.unprocessableEntity().build(); - assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, result.statusCode()); + Mono result = ServerResponse.unprocessableEntity().build(); + StepVerifier.create(result) + .expectNextMatches(response -> HttpStatus.UNPROCESSABLE_ENTITY.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void allow() throws Exception { - ServerResponse result = ServerResponse.ok().allow(HttpMethod.GET).build(); - assertEquals(Collections.singleton(HttpMethod.GET), result.headers().getAllow()); + Mono result = ServerResponse.ok().allow(HttpMethod.GET).build(); + Set expected = EnumSet.of(HttpMethod.GET); + StepVerifier.create(result) + .expectNextMatches(response -> expected.equals(response.headers().getAllow())) + .expectComplete() + .verify(); + } @Test public void contentLength() throws Exception { - ServerResponse result = ServerResponse.ok().contentLength(42).build(); - assertEquals(42, result.headers().getContentLength()); + Mono result = ServerResponse.ok().contentLength(42).build(); + StepVerifier.create(result) + .expectNextMatches(response -> Long.valueOf(42).equals(response.headers().getContentLength())) + .expectComplete() + .verify(); + } @Test public void contentType() throws Exception { - ServerResponse result = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).build(); - assertEquals(MediaType.APPLICATION_JSON, result.headers().getContentType()); + Mono + result = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).build(); + StepVerifier.create(result) + .expectNextMatches(response -> MediaType.APPLICATION_JSON.equals(response.headers().getContentType())) + .expectComplete() + .verify(); } @Test public void eTag() throws Exception { - ServerResponse result = ServerResponse.ok().eTag("foo").build(); - assertEquals("\"foo\"", result.headers().getETag()); + Mono result = ServerResponse.ok().eTag("foo").build(); + StepVerifier.create(result) + .expectNextMatches(response -> "\"foo\"".equals(response.headers().getETag())) + .expectComplete() + .verify(); + } @Test public void lastModified() throws Exception { ZonedDateTime now = ZonedDateTime.now(); - ServerResponse result = ServerResponse.ok().lastModified(now).build(); - assertEquals(now.toInstant().toEpochMilli()/1000, result.headers().getLastModified()/1000); + Mono result = ServerResponse.ok().lastModified(now).build(); + Long expected = now.toInstant().toEpochMilli() / 1000; + StepVerifier.create(result) + .expectNextMatches(response -> expected.equals(response.headers().getLastModified() / 1000)) + .expectComplete() + .verify(); } @Test public void cacheControlTag() throws Exception { - ServerResponse result = ServerResponse.ok().cacheControl(CacheControl.noCache()).build(); - assertEquals("no-cache", result.headers().getCacheControl()); + Mono + result = ServerResponse.ok().cacheControl(CacheControl.noCache()).build(); + StepVerifier.create(result) + .expectNextMatches(response -> "no-cache".equals(response.headers().getCacheControl())) + .expectComplete() + .verify(); } @Test public void varyBy() throws Exception { - ServerResponse result = ServerResponse.ok().varyBy("foo").build(); - assertEquals(Collections.singletonList("foo"), result.headers().getVary()); + Mono result = ServerResponse.ok().varyBy("foo").build(); + List expected = Collections.singletonList("foo"); + StepVerifier.create(result) + .expectNextMatches(response -> expected.equals(response.headers().getVary())) + .expectComplete() + .verify(); + } @Test public void statusCode() throws Exception { HttpStatus statusCode = HttpStatus.ACCEPTED; - ServerResponse result = ServerResponse.status(statusCode).build(); - assertSame(statusCode, result.statusCode()); + Mono result = ServerResponse.status(statusCode).build(); + StepVerifier.create(result) + .expectNextMatches(response -> statusCode.equals(response.statusCode())) + .expectComplete() + .verify(); + } @Test public void headers() throws Exception { HttpHeaders headers = new HttpHeaders(); - ServerResponse result = ServerResponse.ok().headers(headers).build(); - assertEquals(headers, result.headers()); + Mono result = ServerResponse.ok().headers(headers).build(); + StepVerifier.create(result) + .expectNextMatches(response -> headers.equals(response.headers())) + .expectComplete() + .verify(); + } @Test public void build() throws Exception { - ServerResponse result = ServerResponse.status(HttpStatus.CREATED).header("MyKey", "MyValue").build(); + Mono + result = ServerResponse.status(HttpStatus.CREATED).header("MyKey", "MyValue").build(); ServerWebExchange exchange = mock(ServerWebExchange.class); MockServerHttpResponse response = new MockServerHttpResponse(); when(exchange.getResponse()).thenReturn(response); HandlerStrategies strategies = mock(HandlerStrategies.class); - result.writeTo(exchange, strategies).block(); - assertEquals(201, response.getStatusCode().value()); + result.then(res -> res.writeTo(exchange, strategies)).block(); + + assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertEquals("MyValue", response.getHeaders().getFirst("MyKey")); assertNull(response.getBody()); @@ -197,21 +252,25 @@ public class DefaultServerResponseBuilderTests { @Test public void buildVoidPublisher() throws Exception { Mono mono = Mono.empty(); - ServerResponse> result = ServerResponse.ok().build(mono); + Mono result = ServerResponse.ok().build(mono); ServerWebExchange exchange = mock(ServerWebExchange.class); MockServerHttpResponse response = new MockServerHttpResponse(); when(exchange.getResponse()).thenReturn(response); HandlerStrategies strategies = mock(HandlerStrategies.class); - result.writeTo(exchange, strategies).block(); + result.then(res -> res.writeTo(exchange, strategies)).block(); + assertNull(response.getBody()); } +/* +TODO: enable when ServerEntityResponse is reintroduced + @Test public void bodyInserter() throws Exception { String body = "foo"; - Supplier supplier = () -> body; + Publisher publisher = Mono.just(body); BiFunction> writer = (response, strategies) -> { byte[] bodyBytes = body.getBytes(UTF_8); @@ -221,14 +280,13 @@ public class DefaultServerResponseBuilderTests { return response.writeWith(Mono.just(buffer)); }; - ServerResponse result = ServerResponse.ok().body(BodyInserter.of(writer, supplier)); - assertEquals(body, result.body()); + Mono result = ServerResponse.ok().body(BodyInserter.of(writer, publisher)); MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "http://localhost"); - MockServerHttpResponse response = new MockServerHttpResponse(); + MockServerHttpResponse mockResponse = new MockServerHttpResponse(); ServerWebExchange exchange = - new DefaultServerWebExchange(request, response, new MockWebSessionManager()); + new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager()); List> messageWriters = new ArrayList<>(); messageWriters.add(new EncoderHttpMessageWriter(new CharSequenceEncoder())); @@ -236,21 +294,32 @@ public class DefaultServerResponseBuilderTests { HandlerStrategies strategies = mock(HandlerStrategies.class); when(strategies.messageWriters()).thenReturn(messageWriters::stream); - result.writeTo(exchange, strategies).block(); - assertNotNull(response.getBody()); + StepVerifier.create(result) + .consumeNextWith(response -> { + StepVerifier.create(response.body()) + .expectNext(body) + .expectComplete() + .verify(); + response.writeTo(exchange, strategies); + }) + .expectComplete() + .verify(); + + assertNotNull(mockResponse.getBody()); } +*/ +/* +TODO: enable when ServerEntityResponse is reintroduced @Test public void render() throws Exception { Map model = Collections.singletonMap("foo", "bar"); - ServerResponse result = ServerResponse.ok().render("view", model); + Mono result = ServerResponse.ok().render("view", model); - assertEquals("view", result.body().name()); - assertEquals(model, result.body().model()); MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, URI.create("http://localhost")); - MockServerHttpResponse response = new MockServerHttpResponse(); - ServerWebExchange exchange = new DefaultServerWebExchange(request, response, new MockWebSessionManager()); + MockServerHttpResponse mockResponse = new MockServerHttpResponse(); + ServerWebExchange exchange = new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager()); ViewResolver viewResolver = mock(ViewResolver.class); View view = mock(View.class); when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view)); @@ -262,17 +331,37 @@ public class DefaultServerResponseBuilderTests { HandlerStrategies mockConfig = mock(HandlerStrategies.class); when(mockConfig.viewResolvers()).thenReturn(viewResolvers::stream); - result.writeTo(exchange, mockConfig).block(); + StepVerifier.create(result) + .consumeNextWith(response -> { + StepVerifier.create(response.body()) + .expectNextMatches(rendering -> "view".equals(rendering.name()) + && model.equals(rendering.model())) + .expectComplete() + .verify(); + }) + .expectComplete() + .verify(); } +*/ + +/* +TODO: enable when ServerEntityResponse is reintroduced @Test public void renderObjectArray() throws Exception { - ServerResponse result = + Mono result = ServerResponse.ok().render("name", this, Collections.emptyList(), "foo"); - Map model = result.body().model(); - assertEquals(2, model.size()); - assertEquals(this, model.get("defaultServerResponseBuilderTests")); - assertEquals("foo", model.get("string")); + Flux map = result.flatMap(ServerResponse::body); + + Map expected = new HashMap<>(2); + expected.put("defaultServerResponseBuilderTests", this); + expected.put("string", "foo"); + + StepVerifier.create(map) + .expectNextMatches(rendering -> expected.equals(rendering.model())) + .expectComplete() + .verify(); } +*/ } \ No newline at end of file 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 ed41d5fba44..2fba9ca6aa6 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 @@ -16,14 +16,12 @@ package org.springframework.web.reactive.function; -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 org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -50,6 +48,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.fromObject; import static org.springframework.http.codec.BodyInserters.fromPublisher; import static org.springframework.web.reactive.function.RouterFunctions.route; @@ -84,7 +83,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr @Test public void mono() throws Exception { ResponseEntity result = - restTemplate.getForEntity("http://localhost:" + port + "/mono", Person.class); + this.restTemplate.getForEntity("http://localhost:" + this.port + "/mono", Person.class); assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals("John", result.getBody().getName()); @@ -94,7 +93,8 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr public void flux() throws Exception { ParameterizedTypeReference> reference = new ParameterizedTypeReference>() {}; ResponseEntity> result = - restTemplate.exchange("http://localhost:" + port + "/flux", HttpMethod.GET, null, reference); + this.restTemplate + .exchange("http://localhost:" + this.port + "/flux", HttpMethod.GET, null, reference); assertEquals(HttpStatus.OK, result.getStatusCode()); List body = result.getBody(); @@ -134,7 +134,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr @Override public Supplier> viewResolvers() { - return () -> Collections.emptySet().stream(); + return Stream::empty; } }); } @@ -154,18 +154,22 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr private static class PersonHandler { - public ServerResponse> mono(ServerRequest request) { + public Mono mono(ServerRequest request) { Person person = new Person("John"); - return ServerResponse.ok().body(fromPublisher(Mono.just(person), Person.class)); + return ServerResponse.ok().body(fromObject(person)); } - public ServerResponse> flux(ServerRequest request) { + public Mono flux(ServerRequest request) { Person person1 = new Person("John"); Person person2 = new Person("Jane"); return ServerResponse.ok().body( fromPublisher(Flux.just(person1, person2), Person.class)); } + public Mono view() { + return ServerResponse.ok().render("foo", "bar"); + } + } private static class Person { @@ -181,7 +185,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr } public String getName() { - return name; + return this.name; } public void setName(String name) { @@ -209,7 +213,7 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr @Override public String toString() { return "Person{" + - "name='" + name + '\'' + + "name='" + this.name + '\'' + '}'; } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockServerRequest.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockServerRequest.java index 991eb0657e0..7dce98f1916 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockServerRequest.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/MockServerRequest.java @@ -45,7 +45,7 @@ import org.springframework.util.MultiValueMap; /** * @author Arjen Poutsma */ -public class MockServerRequest implements ServerRequest { +public class MockServerRequest implements ServerRequest { private final HttpMethod method; @@ -53,7 +53,7 @@ public class MockServerRequest implements ServerRequest { private final MockHeaders headers; - private final T body; + private final Object body; private final Map attributes; @@ -62,7 +62,7 @@ public class MockServerRequest implements ServerRequest { private final Map pathVariables; private MockServerRequest(HttpMethod method, URI uri, - MockHeaders headers, T body, Map attributes, + MockHeaders headers, Object body, Map attributes, MultiValueMap queryParams, Map pathVariables) { this.method = method; @@ -74,8 +74,8 @@ public class MockServerRequest implements ServerRequest { this.pathVariables = pathVariables; } - public static Builder builder() { - return new BuilderImpl(); + public static Builder builder() { + return new BuilderImpl(); } @Override @@ -127,35 +127,35 @@ public class MockServerRequest implements ServerRequest { return Collections.unmodifiableMap(this.pathVariables); } - public interface Builder { + public interface Builder { - Builder method(HttpMethod method); + Builder method(HttpMethod method); - Builder uri(URI uri); + Builder uri(URI uri); - Builder header(String key, String value); + Builder header(String key, String value); - Builder headers(HttpHeaders headers); + Builder headers(HttpHeaders headers); - Builder attribute(String name, Object value); + Builder attribute(String name, Object value); - Builder attributes(Map attributes); + Builder attributes(Map attributes); - Builder queryParam(String key, String value); + Builder queryParam(String key, String value); - Builder queryParams(MultiValueMap queryParams); + Builder queryParams(MultiValueMap queryParams); - Builder pathVariable(String key, String value); + Builder pathVariable(String key, String value); - Builder pathVariables(Map pathVariables); + Builder pathVariables(Map pathVariables); - MockServerRequest body(T body); + MockServerRequest body(Object body); - MockServerRequest build(); + MockServerRequest build(); } - private static class BuilderImpl implements Builder { + private static class BuilderImpl implements Builder { private HttpMethod method = HttpMethod.GET; @@ -163,7 +163,7 @@ public class MockServerRequest implements ServerRequest { private MockHeaders headers = new MockHeaders(new HttpHeaders()); - private T body; + private Object body; private Map attributes = new LinkedHashMap<>(); @@ -172,21 +172,21 @@ public class MockServerRequest implements ServerRequest { private Map pathVariables = new LinkedHashMap<>(); @Override - public Builder method(HttpMethod method) { + public Builder method(HttpMethod method) { Assert.notNull(method, "'method' must not be null"); this.method = method; return this; } @Override - public Builder uri(URI uri) { + public Builder uri(URI uri) { Assert.notNull(uri, "'uri' must not be null"); this.uri = uri; return this; } @Override - public Builder header(String key, String value) { + public Builder header(String key, String value) { Assert.notNull(key, "'key' must not be null"); Assert.notNull(value, "'value' must not be null"); this.headers.header(key, value); @@ -194,14 +194,14 @@ public class MockServerRequest implements ServerRequest { } @Override - public Builder headers(HttpHeaders headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "'headers' must not be null"); this.headers = new MockHeaders(headers); return this; } @Override - public Builder attribute(String name, Object value) { + public Builder attribute(String name, Object value) { Assert.notNull(name, "'name' must not be null"); Assert.notNull(value, "'value' must not be null"); this.attributes.put(name, value); @@ -209,14 +209,14 @@ public class MockServerRequest implements ServerRequest { } @Override - public Builder attributes(Map attributes) { + public Builder attributes(Map attributes) { Assert.notNull(attributes, "'attributes' must not be null"); this.attributes = attributes; return this; } @Override - public Builder queryParam(String key, String value) { + public Builder queryParam(String key, String value) { Assert.notNull(key, "'key' must not be null"); Assert.notNull(value, "'value' must not be null"); this.queryParams.add(key, value); @@ -224,14 +224,14 @@ public class MockServerRequest implements ServerRequest { } @Override - public Builder queryParams(MultiValueMap queryParams) { + public Builder queryParams(MultiValueMap queryParams) { Assert.notNull(queryParams, "'queryParams' must not be null"); this.queryParams = queryParams; return this; } @Override - public Builder pathVariable(String key, String value) { + public Builder pathVariable(String key, String value) { Assert.notNull(key, "'key' must not be null"); Assert.notNull(value, "'value' must not be null"); this.pathVariables.put(key, value); @@ -239,22 +239,22 @@ public class MockServerRequest implements ServerRequest { } @Override - public Builder pathVariables(Map pathVariables) { + public Builder pathVariables(Map pathVariables) { Assert.notNull(pathVariables, "'pathVariables' must not be null"); this.pathVariables = pathVariables; return this; } @Override - public MockServerRequest body(T body) { + public MockServerRequest body(Object body) { this.body = body; - return new MockServerRequest(this.method, this.uri, this.headers, this.body, + return new MockServerRequest(this.method, this.uri, this.headers, this.body, this.attributes, this.queryParams, this.pathVariables); } @Override - public MockServerRequest build() { - return new MockServerRequest(this.method, this.uri, this.headers, null, + public MockServerRequest build() { + return new MockServerRequest(this.method, this.uri, this.headers, null, this.attributes, this.queryParams, this.pathVariables); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PathResourceLookupFunctionTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PathResourceLookupFunctionTests.java index f0e7c9db4b2..f05ef215cd8 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PathResourceLookupFunctionTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/PathResourceLookupFunctionTests.java @@ -16,18 +16,17 @@ package org.springframework.web.reactive.function; +import java.io.File; +import java.io.IOException; import java.net.URI; -import java.util.Optional; import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - /** * @author Arjen Poutsma */ @@ -38,14 +37,23 @@ public class PathResourceLookupFunctionTests { ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/"); PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location); - MockServerRequest request = MockServerRequest.builder() + MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/response.txt")) .build(); - Optional result = function.apply(request); - assertTrue(result.isPresent()); + Mono result = function.apply(request); - ClassPathResource expected = new ClassPathResource("response.txt", getClass()); - assertEquals(expected.getFile(), result.get().getFile()); + File expected = new ClassPathResource("response.txt", getClass()).getFile(); + StepVerifier.create(result) + .expectNextMatches(resource -> { + try { + return expected.equals(resource.getFile()); + } + catch (IOException ex) { + return false; + } + }) + .expectComplete() + .verify(); } @Test @@ -53,14 +61,22 @@ public class PathResourceLookupFunctionTests { ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/"); PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location); - MockServerRequest request = MockServerRequest.builder() + MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/child/response.txt")) .build(); - Optional result = function.apply(request); - assertTrue(result.isPresent()); - - ClassPathResource expected = new ClassPathResource("org/springframework/web/reactive/function/child/response.txt"); - assertEquals(expected.getFile(), result.get().getFile()); + Mono result = function.apply(request); + File expected = new ClassPathResource("org/springframework/web/reactive/function/child/response.txt").getFile(); + StepVerifier.create(result) + .expectNextMatches(resource -> { + try { + return expected.equals(resource.getFile()); + } + catch (IOException ex) { + return false; + } + }) + .expectComplete() + .verify(); } @Test @@ -68,11 +84,13 @@ public class PathResourceLookupFunctionTests { ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/"); PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location); - MockServerRequest request = MockServerRequest.builder() + MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/foo")) .build(); - Optional result = function.apply(request); - assertFalse(result.isPresent()); + Mono result = function.apply(request); + StepVerifier.create(result) + .expectComplete() + .verify(); } } \ No newline at end of file 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 a10df729146..62aa5016ecf 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 @@ -21,7 +21,6 @@ import java.util.List; import org.junit.Before; import org.junit.Test; -import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -97,17 +96,17 @@ public class PublisherHandlerFunctionIntegrationTests private static class PersonHandler { - public ServerResponse> mono(ServerRequest request) { + public Mono mono(ServerRequest request) { Person person = new Person("John"); return ServerResponse.ok().body(fromPublisher(Mono.just(person), Person.class)); } - public ServerResponse> postMono(ServerRequest request) { + public Mono postMono(ServerRequest request) { Mono personMono = request.body(toMono(Person.class)); return ServerResponse.ok().body(fromPublisher(personMono, Person.class)); } - public ServerResponse> flux(ServerRequest request) { + public Mono flux(ServerRequest request) { Person person1 = new Person("John"); Person person2 = new Person("Jane"); return ServerResponse.ok().body( diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/ResourceHandlerFunctionTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/ResourceHandlerFunctionTests.java index 76b2f23287b..62c3a74f560 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/ResourceHandlerFunctionTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/ResourceHandlerFunctionTests.java @@ -57,18 +57,24 @@ public class ResourceHandlerFunctionTests { ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults()); - ServerResponse response = this.handlerFunction.handle(request); - assertEquals(HttpStatus.OK, response.statusCode()); - assertEquals(this.resource, response.body()); + Mono responseMono = this.handlerFunction.handle(request); - Mono result = response.writeTo(exchange, HandlerStrategies.withDefaults()); + Mono result = responseMono.then(response -> { + assertEquals(HttpStatus.OK, response.statusCode()); +/* +TODO: enable when ServerEntityResponse is reintroduced + StepVerifier.create(response.body()) + .expectNext(this.resource) + .expectComplete() + .verify(); +*/ + return response.writeTo(exchange, HandlerStrategies.withDefaults()); + }); StepVerifier.create(result) .expectComplete() .verify(); - StepVerifier.create(result).expectComplete().verify(); - byte[] expectedBytes = Files.readAllBytes(this.resource.getFile().toPath()); StepVerifier.create(mockResponse.getBody()) @@ -93,10 +99,12 @@ public class ResourceHandlerFunctionTests { ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults()); - ServerResponse response = this.handlerFunction.handle(request); - assertEquals(HttpStatus.OK, response.statusCode()); + Mono response = this.handlerFunction.handle(request); - Mono result = response.writeTo(exchange, HandlerStrategies.withDefaults()); + Mono result = response.then(res -> { + assertEquals(HttpStatus.OK, res.statusCode()); + return res.writeTo(exchange, HandlerStrategies.withDefaults()); + }); StepVerifier.create(result) .expectComplete() @@ -121,14 +129,20 @@ public class ResourceHandlerFunctionTests { ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults()); - ServerResponse response = this.handlerFunction.handle(request); - - assertEquals(HttpStatus.OK, response.statusCode()); - assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS), - response.headers().getAllow()); - assertNull(response.body()); + Mono responseMono = this.handlerFunction.handle(request); + Mono result = responseMono.then(response -> { + assertEquals(HttpStatus.OK, response.statusCode()); + assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS), + response.headers().getAllow()); +/* +TODO: enable when ServerEntityResponse is reintroduced + StepVerifier.create(response.body()) + .expectComplete() + .verify(); +*/ + return response.writeTo(exchange, HandlerStrategies.withDefaults()); + }); - Mono result = response.writeTo(exchange, HandlerStrategies.withDefaults()); StepVerifier.create(result) .expectComplete() 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 8d62e2b5acb..59e2e1c50b3 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 @@ -16,13 +16,11 @@ package org.springframework.web.reactive.function; -import java.util.Optional; - import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.http.codec.BodyInserters.fromObject; /** @@ -33,69 +31,96 @@ public class RouterFunctionTests { @Test public void andSame() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); - RouterFunction routerFunction1 = request -> Optional.empty(); - RouterFunction routerFunction2 = request -> Optional.of(handlerFunction); + HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); + RouterFunction routerFunction1 = request -> Mono.empty(); + RouterFunction routerFunction2 = request -> Mono.just(handlerFunction); - RouterFunction result = routerFunction1.andSame(routerFunction2); + RouterFunction result = routerFunction1.andSame(routerFunction2); assertNotNull(result); MockServerRequest request = MockServerRequest.builder().build(); - Optional> resultHandlerFunction = result.route(request); - assertTrue(resultHandlerFunction.isPresent()); - assertEquals(handlerFunction, resultHandlerFunction.get()); + Mono> resultHandlerFunction = result.route(request); + + StepVerifier.create(resultHandlerFunction) + .expectNext(handlerFunction) + .expectComplete() + .verify(); } @Test public void and() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().body(fromObject("42")); - RouterFunction routerFunction1 = request -> Optional.empty(); - RouterFunction routerFunction2 = request -> Optional.of(handlerFunction); + HandlerFunction handlerFunction = + request -> ServerResponse.ok().body(fromObject("42")); + RouterFunction routerFunction1 = request -> Mono.empty(); + RouterFunction routerFunction2 = + request -> Mono.just(handlerFunction); RouterFunction result = routerFunction1.and(routerFunction2); assertNotNull(result); MockServerRequest request = MockServerRequest.builder().build(); - Optional> resultHandlerFunction = result.route(request); - assertTrue(resultHandlerFunction.isPresent()); - assertEquals(handlerFunction, resultHandlerFunction.get()); + Mono> resultHandlerFunction = result.route(request); + + StepVerifier.create(resultHandlerFunction) + .expectNextMatches(o -> o.equals(handlerFunction)) + .expectComplete() + .verify(); } @Test public void andRoute() throws Exception { - RouterFunction routerFunction1 = request -> Optional.empty(); + RouterFunction routerFunction1 = request -> Mono.empty(); RequestPredicate requestPredicate = request -> true; RouterFunction result = routerFunction1.andRoute(requestPredicate, this::handlerMethod); assertNotNull(result); MockServerRequest request = MockServerRequest.builder().build(); - Optional> resultHandlerFunction = result.route(request); - assertTrue(resultHandlerFunction.isPresent()); + Mono> resultHandlerFunction = result.route(request); + + StepVerifier.create(resultHandlerFunction) + .expectNextCount(1) + .expectComplete() + .verify(); } - private ServerResponse handlerMethod(ServerRequest request) { + private Mono handlerMethod(ServerRequest request) { return ServerResponse.ok().body(fromObject("42")); } + /* + TODO: enable when ServerEntityResponse is reintroduced + @Test public void filter() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().body(fromObject("42")); - RouterFunction routerFunction = request -> Optional.of(handlerFunction); - - HandlerFilterFunction filterFunction = (request, next) -> { - ServerResponse response = next.handle(request); - int i = Integer.parseInt(response.body()); - return ServerResponse.ok().body(fromObject(i)); - }; + HandlerFunction handlerFunction = request -> ServerResponse.ok().body(fromObject("42")); + RouterFunction routerFunction = request -> Mono.just(handlerFunction); + + HandlerFilterFunction filterFunction = + (request, next) -> next.handle(request).then( + response -> { + Flux body = Flux.from(response.body()) + .map(Integer::parseInt); + return ServerResponse.ok().body(body, Integer.class); + }); RouterFunction result = routerFunction.filter(filterFunction); assertNotNull(result); MockServerRequest request = MockServerRequest.builder().build(); - Optional> resultHandlerFunction = result.route(request); - assertTrue(resultHandlerFunction.isPresent()); - ServerResponse resultResponse = resultHandlerFunction.get().handle(request); - assertEquals(42, resultResponse.body()); + Mono> responseMono = + result.route(request).then(hf -> hf.handle(request)); + + StepVerifier.create(responseMono) + .consumeNextWith( + serverResponse -> { + StepVerifier.create(serverResponse.body()) + .expectNext(42) + .expectComplete() + .verify(); + }) + .expectComplete() + .verify(); } + */ } \ No newline at end of file diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionsTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionsTests.java index b918e264914..aac09977530 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionsTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/function/RouterFunctionsTests.java @@ -16,11 +16,11 @@ package org.springframework.web.reactive.function; -import java.util.Collections; -import java.util.Optional; +import java.util.stream.Stream; import org.junit.Test; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import org.springframework.http.HttpMethod; import org.springframework.http.codec.HttpMessageReader; @@ -31,8 +31,11 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * @author Arjen Poutsma @@ -42,87 +45,97 @@ public class RouterFunctionsTests { @Test public void routeMatch() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); + HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); MockServerRequest request = MockServerRequest.builder().build(); RequestPredicate requestPredicate = mock(RequestPredicate.class); when(requestPredicate.test(request)).thenReturn(true); - RouterFunction result = RouterFunctions.route(requestPredicate, handlerFunction); + RouterFunction result = RouterFunctions.route(requestPredicate, handlerFunction); assertNotNull(result); - Optional> resultHandlerFunction = result.route(request); - assertTrue(resultHandlerFunction.isPresent()); - assertEquals(handlerFunction, resultHandlerFunction.get()); + Mono> resultHandlerFunction = result.route(request); + + StepVerifier.create(resultHandlerFunction) + .expectNext(handlerFunction) + .expectComplete() + .verify(); } @Test public void routeNoMatch() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); + HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); MockServerRequest request = MockServerRequest.builder().build(); RequestPredicate requestPredicate = mock(RequestPredicate.class); when(requestPredicate.test(request)).thenReturn(false); - RouterFunction result = RouterFunctions.route(requestPredicate, handlerFunction); + RouterFunction result = RouterFunctions.route(requestPredicate, handlerFunction); assertNotNull(result); - Optional> resultHandlerFunction = result.route(request); - assertFalse(resultHandlerFunction.isPresent()); + Mono> resultHandlerFunction = result.route(request); + StepVerifier.create(resultHandlerFunction) + .expectComplete() + .verify(); } @Test public void subrouteMatch() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); - RouterFunction routerFunction = request -> Optional.of(handlerFunction); + HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); + RouterFunction routerFunction = request -> Mono.just(handlerFunction); MockServerRequest request = MockServerRequest.builder().build(); RequestPredicate requestPredicate = mock(RequestPredicate.class); when(requestPredicate.test(request)).thenReturn(true); - RouterFunction result = RouterFunctions.subroute(requestPredicate, routerFunction); + RouterFunction result = RouterFunctions.subroute(requestPredicate, routerFunction); assertNotNull(result); - Optional> resultHandlerFunction = result.route(request); - assertTrue(resultHandlerFunction.isPresent()); - assertEquals(handlerFunction, resultHandlerFunction.get()); + Mono> resultHandlerFunction = result.route(request); + StepVerifier.create(resultHandlerFunction) + .expectNext(handlerFunction) + .expectComplete() + .verify(); } @Test public void subrouteNoMatch() throws Exception { - HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); - RouterFunction routerFunction = request -> Optional.of(handlerFunction); + HandlerFunction handlerFunction = request -> ServerResponse.ok().build(); + RouterFunction routerFunction = request -> Mono.just(handlerFunction); MockServerRequest request = MockServerRequest.builder().build(); RequestPredicate requestPredicate = mock(RequestPredicate.class); when(requestPredicate.test(request)).thenReturn(false); - RouterFunction result = RouterFunctions.subroute(requestPredicate, routerFunction); + RouterFunction result = RouterFunctions.subroute(requestPredicate, routerFunction); assertNotNull(result); - Optional> resultHandlerFunction = result.route(request); - assertFalse(resultHandlerFunction.isPresent()); + Mono> resultHandlerFunction = result.route(request); + StepVerifier.create(resultHandlerFunction) + .expectComplete() + .verify(); + } @Test public void toHttpHandler() throws Exception { HandlerStrategies strategies = mock(HandlerStrategies.class); when(strategies.messageReaders()).thenReturn( - () -> Collections.>emptyList().stream()); + Stream::>empty); when(strategies.messageWriters()).thenReturn( - () -> Collections.>emptyList().stream()); + Stream::>empty); when(strategies.viewResolvers()).thenReturn( - () -> Collections.emptyList().stream()); + Stream::empty); ServerRequest request = mock(ServerRequest.class); ServerResponse response = mock(ServerResponse.class); when(response.writeTo(any(ServerWebExchange.class), eq(strategies))).thenReturn(Mono.empty()); - HandlerFunction handlerFunction = mock(HandlerFunction.class); - when(handlerFunction.handle(any(ServerRequest.class))).thenReturn(response); + HandlerFunction handlerFunction = mock(HandlerFunction.class); + when(handlerFunction.handle(any(ServerRequest.class))).thenReturn(Mono.just(response)); - RouterFunction routerFunction = mock(RouterFunction.class); - when(routerFunction.route(any(ServerRequest.class))).thenReturn(Optional.of(handlerFunction)); + RouterFunction routerFunction = mock(RouterFunction.class); + when(routerFunction.route(any(ServerRequest.class))).thenReturn(Mono.just(handlerFunction)); RequestPredicate requestPredicate = mock(RequestPredicate.class); when(requestPredicate.test(request)).thenReturn(false); 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 c0d98e5463b..f7900f2c176 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 @@ -20,7 +20,6 @@ import java.time.Duration; import org.junit.Before; import org.junit.Test; -import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -127,18 +126,18 @@ public class SseHandlerFunctionIntegrationTests private static class SseHandler { - public ServerResponse> string(ServerRequest request) { + public Mono string(ServerRequest request) { Flux flux = Flux.interval(Duration.ofMillis(100)).map(l -> "foo " + l).take(2); return ServerResponse.ok().body(fromServerSentEvents(flux, String.class)); } - public ServerResponse> person(ServerRequest request) { + public Mono person(ServerRequest request) { Flux flux = Flux.interval(Duration.ofMillis(100)) .map(l -> new Person("foo " + l)).take(2); return ServerResponse.ok().body(fromServerSentEvents(flux, Person.class)); } - public ServerResponse>> sse(ServerRequest request) { + public Mono sse(ServerRequest request) { Flux> flux = Flux.interval(Duration.ofMillis(100)) .map(l -> ServerSentEvent.builder().data("foo") .id(Long.toString(l)) diff --git a/spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java b/spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java index a2c469b5405..79d0f233923 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyExtractor.java @@ -33,7 +33,7 @@ import org.springframework.http.ReactiveHttpInputMessage; public interface BodyExtractor { /** - * Extract from the given request. + * Extract from the given input message. * @param inputMessage request to extract from * @param context the configuration to use * @return the extracted data diff --git a/spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java b/spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java index 20a34a4a340..ac8b0324137 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyInserter.java @@ -16,14 +16,12 @@ 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.ReactiveHttpOutputMessage; -import org.springframework.util.Assert; /** * A combination of functions that can populate a {@link ReactiveHttpOutputMessage} body. @@ -32,39 +30,17 @@ import org.springframework.util.Assert; * @since 5.0 * @see BodyInserters */ +@FunctionalInterface public interface BodyInserter { /** - * Insert into the given response. + * Insert into the given output message. * @param outputMessage the response to insert into * @param context the context to use * @return a {@code Mono} that indicates completion or error */ Mono insert(M outputMessage, Context context); - /** - * Return the type contained in the body. - * @return the type contained in the body - */ - T t(); - - /** - * Return a new {@code BodyInserter} described by the given writer and supplier functions. - * @param writer the writer function for the new inserter - * @param supplier the supplier function for the new inserter - * @param the type supplied and written by the inserter - * @return the new {@code BodyInserter} - */ - 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); - } - /** * Defines the context used during the insertion. */ diff --git a/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java b/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java index 7f962f96b60..da33391f755 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java @@ -18,7 +18,6 @@ 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; @@ -48,14 +47,17 @@ public abstract class BodyInserters { private static final ResolvableType SERVER_SIDE_EVENT_TYPE = ResolvableType.forClass(ServerSentEvent.class); + private static final BodyInserter EMPTY = + (response, context) -> response.setComplete(); + + /** * Return an empty {@code BodyInserter} that writes nothing. * @return an empty {@code BodyInserter} */ + @SuppressWarnings("unchecked") public static BodyInserter empty() { - return BodyInserter.of( - (response, context) -> response.setComplete(), - () -> null); + return (BodyInserter)EMPTY; } /** @@ -65,9 +67,7 @@ public abstract class BodyInserters { */ public static BodyInserter fromObject(T body) { Assert.notNull(body, "'body' must not be null"); - return BodyInserter.of( - writeFunctionFor(Mono.just(body), ResolvableType.forInstance(body)), - () -> body); + return bodyInserterFor(Mono.just(body), ResolvableType.forInstance(body)); } /** @@ -75,18 +75,15 @@ 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 > BodyInserter fromPublisher(P publisher, Class elementClass) { Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(elementClass, "'elementClass' must not be null"); - return BodyInserter.of( - writeFunctionFor(publisher, ResolvableType.forClass(elementClass)), - () -> publisher - ); + return bodyInserterFor(publisher, ResolvableType.forClass(elementClass)); } /** @@ -94,18 +91,15 @@ 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 > BodyInserter fromPublisher(P publisher, ResolvableType elementType) { Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(elementType, "'elementType' must not be null"); - return BodyInserter.of( - writeFunctionFor(publisher, elementType), - () -> publisher - ); + return bodyInserterFor(publisher, elementType); } /** @@ -119,14 +113,11 @@ public abstract class BodyInserters { */ public static BodyInserter fromResource(T resource) { Assert.notNull(resource, "'resource' must not be null"); - return BodyInserter.of( - (response, context) -> { + return (response, context) -> { HttpMessageWriter messageWriter = resourceHttpMessageWriter(context); return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, null, response, Collections.emptyMap()); - }, - () -> resource - ); + }; } private static HttpMessageWriter resourceHttpMessageWriter(BodyInserter.Context context) { @@ -149,14 +140,11 @@ public abstract class BodyInserters { S eventsPublisher) { Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null"); - return BodyInserter.of( - (response, context) -> { + return (response, context) -> { HttpMessageWriter> messageWriter = sseMessageWriter(context); return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE, MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap()); - }, - () -> eventsPublisher - ); + }; } /** @@ -192,15 +180,12 @@ public abstract class BodyInserters { Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null"); Assert.notNull(eventType, "'eventType' must not be null"); - return BodyInserter.of( - (outputMessage, context) -> { + return (outputMessage, context) -> { HttpMessageWriter messageWriter = sseMessageWriter(context); return messageWriter.write(eventsPublisher, eventType, MediaType.TEXT_EVENT_STREAM, outputMessage, Collections.emptyMap()); - }, - () -> eventsPublisher - ); + }; } /** @@ -214,10 +199,7 @@ public abstract class BodyInserters { public static > BodyInserter fromDataBuffers(T publisher) { Assert.notNull(publisher, "'publisher' must not be null"); - return BodyInserter.of( - (outputMessage, context) -> outputMessage.writeWith(publisher), - () -> publisher - ); + return (outputMessage, context) -> outputMessage.writeWith(publisher); } private static HttpMessageWriter sseMessageWriter(BodyInserter.Context context) { @@ -231,8 +213,7 @@ public abstract class BodyInserters { MediaType.TEXT_EVENT_STREAM_VALUE)); } - private static BiFunction> - writeFunctionFor(Publisher body, ResolvableType bodyType) { + private static , M extends ReactiveHttpOutputMessage> BodyInserter bodyInserterFor(P body, ResolvableType bodyType) { return (m, context) -> { @@ -261,31 +242,5 @@ public abstract class BodyInserters { return (HttpMessageWriter) messageWriter; } - static class DefaultBodyInserter - implements BodyInserter { - - private final BiFunction> writer; - - private final Supplier supplier; - - public DefaultBodyInserter( - BiFunction> writer, - Supplier supplier) { - this.writer = writer; - this.supplier = supplier; - } - - @Override - public Mono insert(M outputMessage, Context context) { - return this.writer.apply(outputMessage, context); - } - - @Override - public T t() { - return this.supplier.get(); - } - - } - } diff --git a/spring-web/src/main/java/org/springframework/web/client/reactive/ClientRequest.java b/spring-web/src/main/java/org/springframework/web/client/reactive/ClientRequest.java index 53c8860d0c6..ec16e156b47 100644 --- a/spring-web/src/main/java/org/springframework/web/client/reactive/ClientRequest.java +++ b/spring-web/src/main/java/org/springframework/web/client/reactive/ClientRequest.java @@ -67,11 +67,6 @@ public interface ClientRequest { */ MultiValueMap cookies(); - /** - * Return the body of this request. - */ - T body(); - /** * Return the body inserter of this request. */ diff --git a/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultClientRequestBuilder.java b/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultClientRequestBuilder.java index fd53a1164e5..83d786c5b40 100644 --- a/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultClientRequestBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultClientRequestBuilder.java @@ -121,9 +121,7 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder { @Override public ClientRequest build() { - return body(BodyInserter.of( - (response, configuration) -> response.setComplete(), - () -> null)); + return body(BodyInserters.empty()); } @Override @@ -192,11 +190,6 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder { return this.cookies; } - @Override - public T body() { - return this.inserter.t(); - } - @Override public BodyInserter inserter() { return this.inserter; diff --git a/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java b/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java index d8f1005a559..582b6b1a232 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java @@ -46,7 +46,6 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; /** * @author Arjen Poutsma @@ -83,8 +82,6 @@ public class BodyInsertersTests { String body = "foo"; BodyInserter inserter = BodyInserters.fromObject(body); - assertEquals(body, inserter.t()); - MockServerHttpResponse response = new MockServerHttpResponse(); Mono result = inserter.insert(response, this.context); StepVerifier.create(result).expectComplete().verify(); @@ -102,8 +99,6 @@ public class BodyInsertersTests { Flux body = Flux.just("foo"); BodyInserter, ReactiveHttpOutputMessage> inserter = BodyInserters.fromPublisher(body, String.class); - assertEquals(body, inserter.t()); - MockServerHttpResponse response = new MockServerHttpResponse(); Mono result = inserter.insert(response, this.context); StepVerifier.create(result).expectComplete().verify(); @@ -121,8 +116,6 @@ public class BodyInsertersTests { Resource body = new ClassPathResource("response.txt", getClass()); BodyInserter inserter = BodyInserters.fromResource(body); - assertEquals(body, inserter.t()); - MockServerHttpResponse response = new MockServerHttpResponse(); Mono result = inserter.insert(response, this.context); StepVerifier.create(result).expectComplete().verify(); @@ -146,8 +139,6 @@ public class BodyInsertersTests { BodyInserter>, ServerHttpResponse> inserter = BodyInserters.fromServerSentEvents(body); - assertEquals(body, inserter.t()); - MockServerHttpResponse response = new MockServerHttpResponse(); Mono result = inserter.insert(response, this.context); StepVerifier.create(result).expectNextCount(0).expectComplete().verify(); @@ -159,8 +150,6 @@ public class BodyInsertersTests { BodyInserter, ServerHttpResponse> inserter = BodyInserters.fromServerSentEvents(body, String.class); - assertEquals(body, inserter.t()); - MockServerHttpResponse response = new MockServerHttpResponse(); Mono result = inserter.insert(response, this.context); StepVerifier.create(result).expectNextCount(0).expectComplete().verify(); @@ -175,8 +164,6 @@ public class BodyInsertersTests { BodyInserter, ReactiveHttpOutputMessage> inserter = BodyInserters.fromDataBuffers(body); - assertEquals(body, inserter.t()); - MockServerHttpResponse response = new MockServerHttpResponse(); Mono result = inserter.insert(response, this.context); StepVerifier.create(result).expectComplete().verify(); diff --git a/spring-web/src/test/java/org/springframework/web/client/reactive/DefaultClientRequestBuilderTests.java b/spring-web/src/test/java/org/springframework/web/client/reactive/DefaultClientRequestBuilderTests.java index 76d2a285b62..7a50e02264b 100644 --- a/spring-web/src/test/java/org/springframework/web/client/reactive/DefaultClientRequestBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/reactive/DefaultClientRequestBuilderTests.java @@ -24,8 +24,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Supplier; import org.junit.Test; import reactor.core.publisher.Mono; @@ -186,8 +184,7 @@ public class DefaultClientRequestBuilderTests { @Test public void bodyInserter() throws Exception { String body = "foo"; - Supplier supplier = () -> body; - BiFunction> writer = + BodyInserter inserter = (response, strategies) -> { byte[] bodyBytes = body.getBytes(UTF_8); ByteBuffer byteBuffer = ByteBuffer.wrap(bodyBytes); @@ -197,8 +194,7 @@ public class DefaultClientRequestBuilderTests { }; ClientRequest result = ClientRequest.POST("http://example.com") - .body(BodyInserter.of(writer, supplier)); - assertEquals(body, result.body()); + .body(inserter); MockClientHttpRequest request = new MockClientHttpRequest();