diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java index 8191b0a372a..f1ff2fc1648 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctionBuilder.java @@ -26,7 +26,7 @@ import java.util.function.Supplier; import reactor.core.publisher.Mono; -import org.springframework.http.HttpMethod; +import org.springframework.core.io.Resource; import org.springframework.util.Assert; /** @@ -40,89 +40,111 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { private List> filterFunctions = new ArrayList<>(); - @Override - public RouterFunctions.Builder route(RequestPredicate predicate, + public RouterFunctions.Builder add(RouterFunction routerFunction) { + Assert.notNull(routerFunction, "RouterFunction must not be null"); + this.routerFunctions.add(routerFunction); + return this; + } + + private RouterFunctions.Builder add(RequestPredicate predicate, HandlerFunction handlerFunction) { this.routerFunctions.add(RouterFunctions.route(predicate, handlerFunction)); return this; } @Override - public RouterFunctions.Builder routeGet(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.GET), handlerFunction); + public RouterFunctions.Builder GET(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.GET(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routeGet(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.GET(pattern), handlerFunction); + public RouterFunctions.Builder GET(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.GET(pattern).and(predicate), handlerFunction); } @Override - public RouterFunctions.Builder routeHead(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.HEAD), handlerFunction); + public RouterFunctions.Builder HEAD(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.HEAD(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routeHead(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.HEAD(pattern), handlerFunction); + public RouterFunctions.Builder HEAD(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.HEAD(pattern).and(predicate), handlerFunction); } @Override - public RouterFunctions.Builder routePost(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.POST), handlerFunction); + public RouterFunctions.Builder POST(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.POST(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routePost(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.POST(pattern), handlerFunction); + public RouterFunctions.Builder POST(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.POST(pattern).and(predicate), handlerFunction); } @Override - public RouterFunctions.Builder routePut(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.PUT), handlerFunction); + public RouterFunctions.Builder PUT(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.PUT(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routePut(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.PUT(pattern), handlerFunction); + public RouterFunctions.Builder PUT(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.PUT(pattern).and(predicate), handlerFunction); } @Override - public RouterFunctions.Builder routePatch(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.PATCH), handlerFunction); + public RouterFunctions.Builder PATCH(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.PATCH(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routePatch(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.PATCH(pattern), handlerFunction); + public RouterFunctions.Builder PATCH(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.PATCH(pattern).and(predicate), handlerFunction); } @Override - public RouterFunctions.Builder routeDelete(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.DELETE), handlerFunction); + public RouterFunctions.Builder DELETE(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.DELETE(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routeDelete(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.DELETE(pattern), handlerFunction); + public RouterFunctions.Builder DELETE(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.DELETE(pattern).and(predicate), handlerFunction); } @Override - public RouterFunctions.Builder routeOptions(HandlerFunction handlerFunction) { - return route(RequestPredicates.method(HttpMethod.OPTIONS), handlerFunction); + public RouterFunctions.Builder OPTIONS(String pattern, HandlerFunction handlerFunction) { + return add(RequestPredicates.OPTIONS(pattern), handlerFunction); } @Override - public RouterFunctions.Builder routeOptions(String pattern, HandlerFunction handlerFunction) { - return route(RequestPredicates.OPTIONS(pattern), handlerFunction); + public RouterFunctions.Builder OPTIONS(String pattern, RequestPredicate predicate, + HandlerFunction handlerFunction) { + return add(RequestPredicates.OPTIONS(pattern).and(predicate), handlerFunction); + } + + @Override + public RouterFunctions.Builder resources(String pattern, Resource location) { + return add(RouterFunctions.resources(pattern, location)); + } + + @Override + public RouterFunctions.Builder resources(Function> lookupFunction) { + return add(RouterFunctions.resources(lookupFunction)); } @Override public RouterFunctions.Builder nest(RequestPredicate predicate, Consumer builderConsumer) { - Assert.notNull(builderConsumer, "'builderConsumer' must not be null"); + Assert.notNull(builderConsumer, "Consumer must not be null"); RouterFunctionBuilder nestedBuilder = new RouterFunctionBuilder(); builderConsumer.accept(nestedBuilder); @@ -136,7 +158,7 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { public RouterFunctions.Builder nest(RequestPredicate predicate, Supplier> routerFunctionSupplier) { - Assert.notNull(routerFunctionSupplier, "'routerFunctionSupplier' must not be null"); + Assert.notNull(routerFunctionSupplier, "RouterFunction Supplier must not be null"); RouterFunction nestedRoute = routerFunctionSupplier.get(); @@ -145,57 +167,56 @@ class RouterFunctionBuilder implements RouterFunctions.Builder { } @Override - public RouterFunctions.Builder nestPath(String pattern, + public RouterFunctions.Builder path(String pattern, Consumer builderConsumer) { return nest(RequestPredicates.path(pattern), builderConsumer); } @Override - public RouterFunctions.Builder nestPath(String pattern, + public RouterFunctions.Builder path(String pattern, Supplier> routerFunctionSupplier) { return nest(RequestPredicates.path(pattern), routerFunctionSupplier); } @Override public RouterFunctions.Builder filter(HandlerFilterFunction filterFunction) { - Assert.notNull(filterFunction, "'filterFunction' must not be null"); + Assert.notNull(filterFunction, "HandlerFilterFunction must not be null"); this.filterFunctions.add(filterFunction); return this; } @Override - public RouterFunctions.Builder filterBefore( - Function> requestProcessor) { - - Assert.notNull(requestProcessor, "Function must not be null"); - return filter((request, next) -> requestProcessor.apply(request).flatMap(next::handle)); + public RouterFunctions.Builder before(Function requestProcessor) { + Assert.notNull(requestProcessor, "RequestProcessor must not be null"); + return filter((request, next) -> next.handle(requestProcessor.apply(request))); } @Override - public RouterFunctions.Builder filterAfter( - BiFunction> responseProcessor) { + public RouterFunctions.Builder after( + BiFunction responseProcessor) { + Assert.notNull(responseProcessor, "ResponseProcessor must not be null"); return filter((request, next) -> next.handle(request) - .flatMap(serverResponse -> responseProcessor.apply(request, serverResponse))); + .map(serverResponse -> responseProcessor.apply(request, serverResponse))); } @Override - public RouterFunctions.Builder filterException(Predicate predicate, + public RouterFunctions.Builder onError(Predicate predicate, BiFunction> responseProvider) { - Assert.notNull(predicate, "'exceptionType' must not be null"); - Assert.notNull(responseProvider, "'fallback' must not be null"); + Assert.notNull(predicate, "Predicate must not be null"); + Assert.notNull(responseProvider, "ResponseProvider must not be null"); return filter((request, next) -> next.handle(request) .onErrorResume(predicate, t -> responseProvider.apply(t, request))); } @Override - public RouterFunctions.Builder filterException( + public RouterFunctions.Builder onError( Class exceptionType, BiFunction> responseProvider) { - Assert.notNull(exceptionType, "'exceptionType' must not be null"); - Assert.notNull(responseProvider, "'fallback' must not be null"); + Assert.notNull(exceptionType, "ExceptionType must not be null"); + Assert.notNull(responseProvider, "ResponseProvider must not be null"); return filter((request, next) -> next.handle(request) .onErrorResume(exceptionType, t -> responseProvider.apply(t, request))); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index cc7d2039e15..4bcdf053cae 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -42,6 +42,8 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder; /** * Central entry point to Spring's functional web framework. * Exposes routing functionality, such as to + * {@linkplain #route() create} a {@code RouterFunction} using a discoverable builder-style API, + * to * {@linkplain #route(RequestPredicate, HandlerFunction) create} a {@code RouterFunction} * given a {@code RequestPredicate} and {@code HandlerFunction}, and to do further * {@linkplain #nest(RequestPredicate, RouterFunction) subrouting} on an existing routing @@ -73,11 +75,11 @@ public abstract class RouterFunctions { private static final HandlerFunction NOT_FOUND_HANDLER = request -> ServerResponse.notFound().build(); /** - * Return a {@linkplain Builder builder} that offers a discoverable way to create router - * functions. + * Offers a discoverable way to create router functions through a builder-style interface. * @return a router function builder + * @since 5.1 */ - public static Builder builder() { + public static Builder route() { return new RouterFunctionBuilder(); } @@ -282,38 +284,12 @@ public abstract class RouterFunctions { } /** - * Represents a builder for router functions. - *

Each invocation of {@code route} creates a new {@link RouterFunction} that is - * {@linkplain RouterFunction#and(RouterFunction) composed} with any previously built functions. + * Represents a discoverable builder for router functions. Obtained via + * {@link RouterFunctions#route()}. * @since 5.1 */ public interface Builder { - /** - * Adds a route to the given handler function that matches if the given request predicate - * applies. - *

For instance, the following example routes GET requests for "/user" to the - * {@code listUsers} method in {@code userController}: - *

-		 * RouterFunction<ServerResponse> route =
-		 *     RouterFunctions.builder()
-		 *     .route(RequestPredicates.GET("/user"), userController::listUsers)
-		 *     .build();
-		 * 
- * @param predicate the predicate to test - * @param handlerFunction the handler function to route to if the predicate applies - * @return this builder - * @see RequestPredicates - */ - Builder route(RequestPredicate predicate, HandlerFunction handlerFunction); - - /** - * Adds a route to the given handler function that handles all HTTP {@code GET} requests. - * @param handlerFunction the handler function to handle all {@code GET} requests - * @return this builder - */ - Builder routeGet(HandlerFunction handlerFunction); - /** * Adds a route to the given handler function that handles all HTTP {@code GET} requests * that match the given pattern. @@ -322,14 +298,28 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routeGet(String pattern, HandlerFunction handlerFunction); + Builder GET(String pattern, HandlerFunction handlerFunction); /** - * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests. - * @param handlerFunction the handler function to handle all {@code HEAD} requests + * Adds a route to the given handler function that handles all HTTP {@code GET} requests + * that match the given pattern and predicate. + *

For instance, the following example routes GET requests for "/user" that accept JSON + * to the {@code listUsers} method in {@code userController}: + *

+		 * RouterFunction<ServerResponse> route =
+		 *     RouterFunctions.route()
+		 *     .GET("/user", RequestPredicates.accept(MediaType.APPLICATION_JSON),
+		 *       userController::listUsers)
+		 *     .build();
+		 * 
+ * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code GET} requests that + * match {@code pattern} * @return this builder + * @see RequestPredicates */ - Builder routeHead(HandlerFunction handlerFunction); + Builder GET(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests @@ -339,14 +329,18 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routeHead(String pattern, HandlerFunction handlerFunction); + Builder HEAD(String pattern, HandlerFunction handlerFunction); /** - * Adds a route to the given handler function that handles all HTTP {@code POST} requests. - * @param handlerFunction the handler function to handle all {@code POST} requests + * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests + * that match the given pattern and predicate. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code HEAD} requests that + * match {@code pattern} * @return this builder */ - Builder routePost(HandlerFunction handlerFunction); + Builder HEAD(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code POST} requests @@ -356,14 +350,27 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routePost(String pattern, HandlerFunction handlerFunction); + Builder POST(String pattern, HandlerFunction handlerFunction); /** - * Adds a route to the given handler function that handles all HTTP {@code PUT} requests. - * @param handlerFunction the handler function to handle all {@code PUT} requests + * Adds a route to the given handler function that handles all HTTP {@code POST} requests + * that match the given pattern and predicate. + *

For instance, the following example routes POST requests for "/user" that contain JSON + * to the {@code addUser} method in {@code userController}: + *

+		 * RouterFunction<ServerResponse> route =
+		 *     RouterFunctions.route()
+		 *     .POST("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON),
+		 *       userController::addUser)
+		 *     .build();
+		 * 
+ * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code POST} requests that + * match {@code pattern} * @return this builder */ - Builder routePut(HandlerFunction handlerFunction); + Builder POST(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PUT} requests @@ -373,14 +380,27 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routePut(String pattern, HandlerFunction handlerFunction); + Builder PUT(String pattern, HandlerFunction handlerFunction); /** - * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests. - * @param handlerFunction the handler function to handle all {@code PATCH} requests + * Adds a route to the given handler function that handles all HTTP {@code PUT} requests + * that match the given pattern and predicate. + *

For instance, the following example routes PUT requests for "/user" that contain JSON + * to the {@code editUser} method in {@code userController}: + *

+		 * RouterFunction<ServerResponse> route =
+		 *     RouterFunctions.route()
+		 *     .PUT("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON),
+		 *       userController::editUser)
+		 *     .build();
+		 * 
+ * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code PUT} requests that + * match {@code pattern} * @return this builder */ - Builder routePatch(HandlerFunction handlerFunction); + Builder PUT(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests @@ -390,14 +410,27 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routePatch(String pattern, HandlerFunction handlerFunction); + Builder PATCH(String pattern, HandlerFunction handlerFunction); /** - * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests. - * @param handlerFunction the handler function to handle all {@code DELETE} requests + * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests + * that match the given pattern and predicate. + *

For instance, the following example routes PATCH requests for "/user" that contain JSON + * to the {@code editUser} method in {@code userController}: + *

+		 * RouterFunction<ServerResponse> route =
+		 *     RouterFunctions.route()
+		 *     .PATCH("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON),
+		 *       userController::editUser)
+		 *     .build();
+		 * 
+ * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code PATCH} requests that + * match {@code pattern} * @return this builder */ - Builder routeDelete(HandlerFunction handlerFunction); + Builder PATCH(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests @@ -407,14 +440,18 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routeDelete(String pattern, HandlerFunction handlerFunction); + Builder DELETE(String pattern, HandlerFunction handlerFunction); /** - * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests. - * @param handlerFunction the handler function to handle all {@code OPTIONS} requests + * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests + * that match the given pattern and predicate. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code DELETE} requests that + * match {@code pattern} * @return this builder */ - Builder routeOptions(HandlerFunction handlerFunction); + Builder DELETE(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests @@ -424,7 +461,61 @@ public abstract class RouterFunctions { * match {@code pattern} * @return this builder */ - Builder routeOptions(String pattern, HandlerFunction handlerFunction); + Builder OPTIONS(String pattern, HandlerFunction handlerFunction); + + /** + * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests + * that match the given pattern and predicate. + * @param pattern the pattern to match to + * @param predicate additional predicate to match + * @param handlerFunction the handler function to handle all {@code OPTIONS} requests that + * match {@code pattern} + * @return this builder + */ + Builder OPTIONS(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); + + /** + * Adds the given route to this builder. Can be used to merge externally defined router + * functions into this builder, or can be combined with + * {@link RouterFunctions#route(RequestPredicate, HandlerFunction)} + * to allow for more flexible predicate matching. + *

For instance, the following example adds the router function returned from + * {@code OrderController.routerFunction()}. + * to the {@code changeUser} method in {@code userController}: + *

+		 * RouterFunction route =
+		 *   RouterFunctions.route()
+		 *   .GET("/users", userController::listUsers)
+		 *   .add(orderController.routerFunction());
+		 *   .build();
+		 * 
+ * @param routerFunction the router function to be added + * @return this builder + * @see RequestPredicates + */ + Builder add(RouterFunction routerFunction); + + /** + * Route requests that match the given pattern to resources relative to the given root location. + * For instance + *
+		 * Resource location = new FileSystemResource("public-resources/");
+		 * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
+	     * 
+ * @param pattern the pattern to match + * @param location the location directory relative to which resources should be resolved + * @return this builder + */ + Builder resources(String pattern, Resource location); + + /** + * Route to resources using the provided lookup function. If the lookup function provides a + * {@link Resource} for the given request, it will be it will be exposed using a + * {@link HandlerFunction} that handles GET, HEAD, and OPTIONS requests. + * @param lookupFunction the function to provide a {@link Resource} given the {@link ServerRequest} + * @return this builder + */ + Builder resources(Function> lookupFunction); /** * Route to the supplied router function if the given request predicate applies. This method @@ -435,11 +526,11 @@ public abstract class RouterFunctions { * and POST request for "/user" will create a new user. *
 		 * RouterFunction<ServerResponse> nestedRoute =
-		 *   RouterFunctions.builder()
+		 *   RouterFunctions.route()
 		 *     .nest(RequestPredicates.path("/user"), () ->
-		 *       RouterFunctions.builder()
-		 *       .routeGet(this::listUsers)
-		 *       .routePost(this::createUser);
+		 *       RouterFunctions.route()
+		 *       .GET(this::listUsers)
+		 *       .POST(this::createUser);
 		 *       .build();
 		 *     )
 		 *     .build();
@@ -461,10 +552,10 @@ public abstract class RouterFunctions {
 		 * and POST request for "/user" will create a new user.
 		 * 
 		 * RouterFunction<ServerResponse> nestedRoute =
-		 *   RouterFunctions.builder()
+		 *   RouterFunctions.route()
 		 *     .nest(RequestPredicates.path("/user"), builder ->
-		 *       builder.routeGet(this::listUsers)
-		 *              .routePost(this::createUser);
+		 *       builder.GET(this::listUsers)
+		 *              .POST(this::createUser);
 		 *     )
 		 *     .build();
 		 * 
@@ -479,19 +570,16 @@ public abstract class RouterFunctions { /** * Route to the supplied router function if the given path prefix pattern applies. This method * can be used to create nested routes, where a group of routes share a - * common path (prefix). + * common path prefix. Specifically, this method can be used to merge externally defined + * router functions under a path prefix. *

For instance, the following example creates a nested route with a "/user" path - * predicate, so that GET requests for "/user" will list users, - * and POST request for "/user" will create a new user. + * predicate that delegates to the router function defined in {@code userController}, + * and with a "/order" path that delegates to {@code orderController}. *

 		 * RouterFunction<ServerResponse> nestedRoute =
-		 *   RouterFunctions.builder()
-		 *     .nestPath("/user", () ->
-		 *       RouterFunctions.builder()
-		 *       .routeGet(this::listUsers)
-		 *       .routePost(this::createUser);
-		 *       .build();
-		 *     )
+		 *   RouterFunctions.route()
+		 *     .path("/user", userController::routerFunction)
+		 *     .path("/order", orderController::routerFunction)
 		 *     .build();
 		 * 
* @param pattern the pattern to match to @@ -499,21 +587,21 @@ public abstract class RouterFunctions { * the pattern matches * @return this builder */ - Builder nestPath(String pattern, Supplier> routerFunctionSupplier); + Builder path(String pattern, Supplier> routerFunctionSupplier); /** * Route to a built router function if the given path prefix pattern applies. * This method can be used to create nested routes, where a group of routes - * share a common path (prefix), header, or other request predicate. + * share a common path prefix. *

For instance, the following example creates a nested route with a "/user" path * predicate, so that GET requests for "/user" will list users, * and POST request for "/user" will create a new user. *

 		 * RouterFunction<ServerResponse> nestedRoute =
-		 *   RouterFunctions.builder()
-		 *     .nestPath("/user", builder ->
-		 *       builder.routeGet(this::listUsers)
-		 *              .routePost(this::createUser);
+		 *   RouterFunctions.route()
+		 *     .path("/user", builder ->
+		 *       builder.GET(this::listUsers)
+		 *              .POST(this::createUser);
 		 *     )
 		 *     .build();
 		 * 
@@ -522,22 +610,26 @@ public abstract class RouterFunctions { * function * @return this builder */ - Builder nestPath(String pattern, Consumer builderConsumer); + Builder path(String pattern, Consumer builderConsumer); /** * Filters all routes created by this builder with the given filter function. Filter * functions are typically used to address cross-cutting concerns, such as logging, * security, etc. - *

For instance, the following example creates a filter that logs the request before - * the handler function executes, and logs the response after. + *

For instance, the following example creates a filter that returns a 401 Unauthorized + * response if the request does not contain the necessary authentication headers. *

 		 * RouterFunction<ServerResponse> filteredRoute =
-		 *   RouterFunctions.builder()
-		 *     .routeGet("/user", this::listUsers)
+		 *   RouterFunctions.route()
+		 *     .GET("/user", this::listUsers)
 		 *     .filter((request, next) -> {
-		 *       log(request);
-         *       Mono<ServerResponse> responseMono = next.handle(request);
-		 *       return responseMono.doOnNext(response -> log(response);
+		 *       // check for authentication headers
+		 *       if (isAuthenticated(request)) {
+		 *         return next.handle(request);
+		 *       }
+		 *       else {
+		 *         return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
+		 *       }
 		 *     })
 		 *     .build();
 		 * 
@@ -547,46 +639,46 @@ public abstract class RouterFunctions { Builder filter(HandlerFilterFunction filterFunction); /** - * Filters the request for all routes created by this builder with the given request + * Filter the request object for all routes created by this builder with the given request * processing function. Filters are typically used to address cross-cutting concerns, such * as logging, security, etc. *

For instance, the following example creates a filter that logs the request before * the handler function executes. *

 		 * RouterFunction<ServerResponse> filteredRoute =
-		 *   RouterFunctions.builder()
-		 *     .routeGet("/user", this::listUsers)
-		 *     .filterBefore(request -> {
+		 *   RouterFunctions.route()
+		 *     .GET("/user", this::listUsers)
+		 *     .before(request -> {
 		 *       log(request);
-		 *       return Mono.just(request);
+		 *       return request;
 		 *     })
 		 *     .build();
 		 * 
* @param requestProcessor a function that transforms the request * @return this builder */ - Builder filterBefore(Function> requestProcessor); + Builder before(Function requestProcessor); /** - * Filters the response for all routes created by this builder with the given response + * Filter the response object for all routes created by this builder with the given response * processing function. Filters are typically used to address cross-cutting concerns, such * as logging, security, etc. *

For instance, the following example creates a filter that logs the response after * the handler function executes. *

 		 * RouterFunction<ServerResponse> filteredRoute =
-		 *   RouterFunctions.builder()
-		 *     .routeGet("/user", this::listUsers)
-		 *     .filterAfter((request, response) -> {
+		 *   RouterFunctions.route()
+		 *     .GET("/user", this::listUsers)
+		 *     .after((request, response) -> {
 		 *       log(response);
-		 *       return Mono.just(response);
+		 *       return response;
 		 *     })
 		 *     .build();
 		 * 
* @param responseProcessor a function that transforms the response * @return this builder */ - Builder filterAfter(BiFunction> responseProcessor); + Builder after(BiFunction responseProcessor); /** * Filters all exceptions that match the predicate by applying the given response provider @@ -595,9 +687,9 @@ public abstract class RouterFunctions { * status when an {@code IllegalStateException} occurs. *
 		 * RouterFunction<ServerResponse> filteredRoute =
-		 *   RouterFunctions.builder()
-		 *     .routeGet("/user", this::listUsers)
-		 *     .filterException(e -> e instanceof IllegalStateException,
+		 *   RouterFunctions.route()
+		 *     .GET("/user", this::listUsers)
+		 *     .onError(e -> e instanceof IllegalStateException,
 		 *       (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build())
 		 *     .build();
 		 * 
@@ -605,7 +697,7 @@ public abstract class RouterFunctions { * @param responseProvider a function that creates a response * @return this builder */ - Builder filterException(Predicate predicate, + Builder onError(Predicate predicate, BiFunction> responseProvider); /** @@ -615,9 +707,9 @@ public abstract class RouterFunctions { * status when an {@code IllegalStateException} occurs. *
 		 * RouterFunction<ServerResponse> filteredRoute =
-		 *   RouterFunctions.builder()
-		 *     .routeGet("/user", this::listUsers)
-		 *     .filterException(IllegalStateException.class,
+		 *   RouterFunctions.route()
+		 *     .GET("/user", this::listUsers)
+		 *     .onError(IllegalStateException.class,
 		 *       (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build())
 		 *     .build();
 		 * 
@@ -625,13 +717,13 @@ public abstract class RouterFunctions { * @param responseProvider a function that creates a response * @return this builder */ - Builder filterException(Class exceptionType, + Builder onError(Class exceptionType, BiFunction> responseProvider); /** * Builds the {@code RouterFunction}. All created routes are * {@linkplain RouterFunction#and(RouterFunction) composed} with one another, and filters - * (f any) are applied to the result. + * (if any) are applied to the result. * @return the built router function */ RouterFunction build(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java index e1208c1c72f..3d2210916f2 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RouterFunctionBuilderTests.java @@ -23,8 +23,11 @@ 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 org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import static org.junit.Assert.*; @@ -35,10 +38,11 @@ public class RouterFunctionBuilderTests { @Test public void route() { - RouterFunction route = RouterFunctions.builder() - .routeGet("/foo", request -> ServerResponse.ok().build()) - .routePost(request -> ServerResponse.noContent().build()) + RouterFunction route = RouterFunctions.route() + .GET("/foo", request -> ServerResponse.ok().build()) + .POST("/", RequestPredicates.contentType(MediaType.TEXT_PLAIN), request -> ServerResponse.noContent().build()) .build(); + System.out.println(route); MockServerRequest fooRequest = MockServerRequest.builder(). method(HttpMethod.GET). @@ -56,7 +60,8 @@ public class RouterFunctionBuilderTests { MockServerRequest barRequest = MockServerRequest.builder(). method(HttpMethod.POST). - uri(URI.create("http://localhost")) + uri(URI.create("http://localhost/")) + .header("Content-Type", "text/plain") .build(); responseMono = route.route(barRequest) @@ -68,15 +73,65 @@ public class RouterFunctionBuilderTests { .expectNext(204) .verifyComplete(); + MockServerRequest invalidRequest = MockServerRequest.builder(). + method(HttpMethod.POST). + uri(URI.create("http://localhost/")) + .build(); + + responseMono = route.route(invalidRequest) + .flatMap(handlerFunction -> handlerFunction.handle(invalidRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(responseMono) + .verifyComplete(); + + } + + @Test + public void resources() { + Resource resource = new ClassPathResource("/org/springframework/web/reactive/function/server/"); + assertTrue(resource.exists()); + + RouterFunction route = RouterFunctions.route() + .resources("/resources/**", resource) + .build(); + + MockServerRequest resourceRequest = MockServerRequest.builder(). + method(HttpMethod.GET). + uri(URI.create("http://localhost/resources/response.txt")) + .build(); + + Mono responseMono = route.route(resourceRequest) + .flatMap(handlerFunction -> handlerFunction.handle(resourceRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(responseMono) + .expectNext(200) + .verifyComplete(); + + MockServerRequest invalidRequest = MockServerRequest.builder(). + method(HttpMethod.POST). + uri(URI.create("http://localhost/resources/foo.txt")) + .build(); + + responseMono = route.route(invalidRequest) + .flatMap(handlerFunction -> handlerFunction.handle(invalidRequest)) + .map(ServerResponse::statusCode) + .map(HttpStatus::value); + + StepVerifier.create(responseMono) + .verifyComplete(); } @Test public void nest() { - RouterFunction route = RouterFunctions.builder() - .nestPath("/foo", builder -> - builder.nestPath("/bar", - () -> RouterFunctions.builder() - .routeGet("/baz", request -> ServerResponse.ok().build()) + RouterFunction route = RouterFunctions.route() + .path("/foo", builder -> + builder.path("/bar", + () -> RouterFunctions.route() + .GET("/baz", request -> ServerResponse.ok().build()) .build())) .build(); @@ -99,18 +154,18 @@ public class RouterFunctionBuilderTests { public void filters() { AtomicInteger filterCount = new AtomicInteger(); - RouterFunction route = RouterFunctions.builder() - .routeGet("/foo", request -> ServerResponse.ok().build()) - .routeGet("/bar", request -> Mono.error(new IllegalStateException())) - .filterBefore(request -> { + RouterFunction route = RouterFunctions.route() + .GET("/foo", request -> ServerResponse.ok().build()) + .GET("/bar", request -> Mono.error(new IllegalStateException())) + .before(request -> { int count = filterCount.getAndIncrement(); assertEquals(0, count); - return Mono.just(request); + return request; }) - .filterAfter((request, response) -> { + .after((request, response) -> { int count = filterCount.getAndIncrement(); assertEquals(3, count); - return Mono.just(response); + return response; }) .filter((request, next) -> { int count = filterCount.getAndIncrement(); @@ -120,7 +175,7 @@ public class RouterFunctionBuilderTests { assertEquals(2, count); return responseMono; }) - .filterException(IllegalStateException.class, (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build()) + .onError(IllegalStateException.class, (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build()) .build(); MockServerRequest fooRequest = MockServerRequest.builder().