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();