Java tutorial
/* * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.reactive.function.server; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.Assert; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; /** * <strong>Central entry point to Spring's functional web framework.</strong> * 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 * function. * * <p>Additionally, this class can {@linkplain #toHttpHandler(RouterFunction) transform} a * {@code RouterFunction} into an {@code HttpHandler}, which can be run in Servlet 3.1+, * Reactor, or Undertow. * * @author Arjen Poutsma * @since 5.0 */ public abstract class RouterFunctions { private static final Log logger = LogFactory.getLog(RouterFunctions.class); /** * Name of the {@link ServerWebExchange} attribute that contains the {@link ServerRequest}. */ public static final String REQUEST_ATTRIBUTE = RouterFunctions.class.getName() + ".request"; /** * Name of the {@link ServerWebExchange} attribute that contains the URI * templates map, mapping variable names to values. */ public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = RouterFunctions.class.getName() + ".uriTemplateVariables"; /** * Name of the {@link ServerWebExchange#getAttributes() attribute} that * contains the matching pattern, as a {@link org.springframework.web.util.pattern.PathPattern}. */ public static final String MATCHING_PATTERN_ATTRIBUTE = RouterFunctions.class.getName() + ".matchingPattern"; /** * Offers a discoverable way to create router functions through a builder-style interface. * @return a router function builder * @since 5.1 */ public static Builder route() { return new RouterFunctionBuilder(); } /** * Route to the given handler function if the given request predicate applies. * <p>For instance, the following example routes GET requests for "/user" to the * {@code listUsers} method in {@code userController}: * <pre class="code"> * RouterFunction<ServerResponse> route = * RouterFunctions.route(RequestPredicates.GET("/user"), userController::listUsers); * </pre> * @param predicate the predicate to test * @param handlerFunction the handler function to route to if the predicate applies * @param <T> the type of response returned by the handler function * @return a router function that routes to {@code handlerFunction} if * {@code predicate} evaluates to {@code true} * @see RequestPredicates */ public static <T extends ServerResponse> RouterFunction<T> route(RequestPredicate predicate, HandlerFunction<T> handlerFunction) { return new DefaultRouterFunction<>(predicate, handlerFunction); } /** * Route to the given router function if the given request predicate applies. This method can be * used to create <strong>nested routes</strong>, where a group of routes share a common path * (prefix), header, or other request predicate. * <p>For instance, the following example first creates a composed route that resolves to * {@code listUsers} for a GET, and {@code createUser} for a POST. This composed route then gets * nested with a "/user" path predicate, so that GET requests for "/user" will list users, * and POST request for "/user" will create a new user. * <pre class="code"> * RouterFunction<ServerResponse> userRoutes = * RouterFunctions.route(RequestPredicates.method(HttpMethod.GET), this::listUsers) * .andRoute(RequestPredicates.method(HttpMethod.POST), this::createUser); * RouterFunction<ServerResponse> nestedRoute = * RouterFunctions.nest(RequestPredicates.path("/user"), userRoutes); * </pre> * @param predicate the predicate to test * @param routerFunction the nested router function to delegate to if the predicate applies * @param <T> the type of response returned by the handler function * @return a router function that routes to {@code routerFunction} if * {@code predicate} evaluates to {@code true} * @see RequestPredicates */ public static <T extends ServerResponse> RouterFunction<T> nest(RequestPredicate predicate, RouterFunction<T> routerFunction) { return new DefaultNestedRouterFunction<>(predicate, routerFunction); } /** * Route requests that match the given pattern to resources relative to the given root location. * For instance * <pre class="code"> * Resource location = new FileSystemResource("public-resources/"); * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location); * </pre> * @param pattern the pattern to match * @param location the location directory relative to which resources should be resolved * @return a router function that routes to resources * @see #resourceLookupFunction(String, Resource) */ public static RouterFunction<ServerResponse> resources(String pattern, Resource location) { return resources(resourceLookupFunction(pattern, location)); } /** * Returns the resource lookup function used by {@link #resources(String, Resource)}. * The returned function can be {@linkplain Function#andThen(Function) composed} on, for * instance to return a default resource when the lookup function does not match: * <pre class="code"> * Mono<Resource> defaultResource = Mono.just(new ClassPathResource("index.html")); * Function<ServerRequest, Mono<Resource>> lookupFunction = * RouterFunctions.resourceLookupFunction("/resources/**", new FileSystemResource("public-resources/")) * .andThen(resourceMono -> resourceMono.switchIfEmpty(defaultResource)); * RouterFunction<ServerResponse> resources = RouterFunctions.resources(lookupFunction); * </pre> * @param pattern the pattern to match * @param location the location directory relative to which resources should be resolved * @return the default resource lookup function for the given parameters. */ public static Function<ServerRequest, Mono<Resource>> resourceLookupFunction(String pattern, Resource location) { return new PathResourceLookupFunction(pattern, 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 a router function that routes to resources */ public static RouterFunction<ServerResponse> resources(Function<ServerRequest, Mono<Resource>> lookupFunction) { return new ResourcesRouterFunction(lookupFunction); } /** * Convert the given {@linkplain RouterFunction router function} into a {@link HttpHandler}. * This conversion uses {@linkplain HandlerStrategies#builder() default strategies}. * <p>The returned handler can be adapted to run in * <ul> * <li>Servlet 3.1+ using the * {@link org.springframework.http.server.reactive.ServletHttpHandlerAdapter},</li> * <li>Reactor using the * {@link org.springframework.http.server.reactive.ReactorHttpHandlerAdapter},</li> * <li>Undertow using the * {@link org.springframework.http.server.reactive.UndertowHttpHandlerAdapter}.</li> * </ul> * <p>Note that {@code HttpWebHandlerAdapter} also implements {@link WebHandler}, allowing * for additional filter and exception handler registration through * {@link WebHttpHandlerBuilder}. * @param routerFunction the router function to convert * @return an http handler that handles HTTP request using the given router function */ public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction) { return toHttpHandler(routerFunction, HandlerStrategies.withDefaults()); } /** * Convert the given {@linkplain RouterFunction router function} into a {@link HttpHandler}, * using the given strategies. * <p>The returned {@code HttpHandler} can be adapted to run in * <ul> * <li>Servlet 3.1+ using the * {@link org.springframework.http.server.reactive.ServletHttpHandlerAdapter},</li> * <li>Reactor using the * {@link org.springframework.http.server.reactive.ReactorHttpHandlerAdapter},</li> * <li>Undertow using the * {@link org.springframework.http.server.reactive.UndertowHttpHandlerAdapter}.</li> * </ul> * @param routerFunction the router function to convert * @param strategies the strategies to use * @return an http handler that handles HTTP request using the given router function */ public static HttpHandler toHttpHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies) { WebHandler webHandler = toWebHandler(routerFunction, strategies); return WebHttpHandlerBuilder.webHandler(webHandler) .filters(filters -> filters.addAll(strategies.webFilters())) .exceptionHandlers(handlers -> handlers.addAll(strategies.exceptionHandlers())) .localeContextResolver(strategies.localeContextResolver()).build(); } /** * Convert the given {@linkplain RouterFunction router function} into a {@link WebHandler}. * This conversion uses {@linkplain HandlerStrategies#builder() default strategies}. * @param routerFunction the router function to convert * @return a web handler that handles web request using the given router function */ public static WebHandler toWebHandler(RouterFunction<?> routerFunction) { return toWebHandler(routerFunction, HandlerStrategies.withDefaults()); } /** * Convert the given {@linkplain RouterFunction router function} into a {@link WebHandler}, * using the given strategies. * @param routerFunction the router function to convert * @param strategies the strategies to use * @return a web handler that handles web request using the given router function */ public static WebHandler toWebHandler(RouterFunction<?> routerFunction, HandlerStrategies strategies) { Assert.notNull(routerFunction, "RouterFunction must not be null"); Assert.notNull(strategies, "HandlerStrategies must not be null"); return new RouterFunctionWebHandler(strategies, routerFunction); } /** * 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 handles all HTTP {@code GET} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code GET} requests that * match {@code pattern} * @return this builder */ Builder GET(String pattern, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code GET} requests * that match the given pattern and predicate. * <p>For instance, the following example routes GET requests for "/user" that accept JSON * to the {@code listUsers} method in {@code userController}: * <pre class="code"> * RouterFunction<ServerResponse> route = * RouterFunctions.route() * .GET("/user", RequestPredicates.accept(MediaType.APPLICATION_JSON), userController::listUsers) * .build(); * </pre> * @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 GET(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code HEAD} requests that * match {@code pattern} * @return this builder */ Builder HEAD(String pattern, HandlerFunction<ServerResponse> handlerFunction); /** * 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 HEAD(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code POST} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code POST} requests that * match {@code pattern} * @return this builder */ Builder POST(String pattern, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code POST} requests * that match the given pattern and predicate. * <p>For instance, the following example routes POST requests for "/user" that contain JSON * to the {@code addUser} method in {@code userController}: * <pre class="code"> * RouterFunction<ServerResponse> route = * RouterFunctions.route() * .POST("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON), userController::addUser) * .build(); * </pre> * @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 POST(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PUT} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code PUT} requests that * match {@code pattern} * @return this builder */ Builder PUT(String pattern, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PUT} requests * that match the given pattern and predicate. * <p>For instance, the following example routes PUT requests for "/user" that contain JSON * to the {@code editUser} method in {@code userController}: * <pre class="code"> * RouterFunction<ServerResponse> route = * RouterFunctions.route() * .PUT("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON), userController::editUser) * .build(); * </pre> * @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 PUT(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code PATCH} requests that * match {@code pattern} * @return this builder */ Builder PATCH(String pattern, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests * that match the given pattern and predicate. * <p>For instance, the following example routes PATCH requests for "/user" that contain JSON * to the {@code editUser} method in {@code userController}: * <pre class="code"> * RouterFunction<ServerResponse> route = * RouterFunctions.route() * .PATCH("/user", RequestPredicates.contentType(MediaType.APPLICATION_JSON), userController::editUser) * .build(); * </pre> * @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 PATCH(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code DELETE} requests that * match {@code pattern} * @return this builder */ Builder DELETE(String pattern, HandlerFunction<ServerResponse> handlerFunction); /** * 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 DELETE(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests * that match the given pattern. * @param pattern the pattern to match to * @param handlerFunction the handler function to handle all {@code OPTIONS} requests that * match {@code pattern} * @return this builder */ Builder OPTIONS(String pattern, HandlerFunction<ServerResponse> 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<ServerResponse> handlerFunction); /** * Adds a route to the given handler function that handles all requests that match the * given predicate. * * @param predicate the request predicate to match * @param handlerFunction the handler function to handle all requests that match the predicate * @return this builder * @since 5.2 * @see RequestPredicates */ Builder route(RequestPredicate predicate, HandlerFunction<ServerResponse> 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. * <p>For instance, the following example adds the router function returned from * {@code OrderController.routerFunction()}. * to the {@code changeUser} method in {@code userController}: * <pre class="code"> * RouterFunction<ServerResponse> route = * RouterFunctions.route() * .GET("/users", userController::listUsers) * .add(orderController.routerFunction()); * .build(); * </pre> * @param routerFunction the router function to be added * @return this builder * @see RequestPredicates */ Builder add(RouterFunction<ServerResponse> routerFunction); /** * Route requests that match the given pattern to resources relative to the given root location. * For instance * <pre class="code"> * Resource location = new FileSystemResource("public-resources/"); * RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location); * </pre> * @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<ServerRequest, Mono<Resource>> lookupFunction); /** * Route to the supplied router function if the given request predicate applies. This method * can be used to create <strong>nested routes</strong>, where a group of routes share a * common path (prefix), header, or other request predicate. * <p>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. * <pre class="code"> * RouterFunction<ServerResponse> nestedRoute = * RouterFunctions.route() * .nest(RequestPredicates.path("/user"), () -> * RouterFunctions.route() * .GET(this::listUsers) * .POST(this::createUser) * .build()) * .build(); * </pre> * @param predicate the predicate to test * @param routerFunctionSupplier supplier for the nested router function to delegate to if * the predicate applies * @return this builder * @see RequestPredicates */ Builder nest(RequestPredicate predicate, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier); /** * Route to a built router function if the given request predicate applies. * This method can be used to create <strong>nested routes</strong>, where a group of routes * share a common path (prefix), header, or other request predicate. * <p>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. * <pre class="code"> * RouterFunction<ServerResponse> nestedRoute = * RouterFunctions.route() * .nest(RequestPredicates.path("/user"), builder -> * builder.GET(this::listUsers) * .POST(this::createUser)) * .build(); * </pre> * @param predicate the predicate to test * @param builderConsumer consumer for a {@code Builder} that provides the nested router * function * @return this builder * @see RequestPredicates */ Builder nest(RequestPredicate predicate, Consumer<Builder> builderConsumer); /** * Route to the supplied router function if the given path prefix pattern applies. This method * can be used to create <strong>nested routes</strong>, where a group of routes share a * common path prefix. Specifically, this method can be used to merge externally defined * router functions under a path prefix. * <p>For instance, the following example creates a nested route with a "/user" path * predicate that delegates to the router function defined in {@code userController}, * and with a "/order" path that delegates to {@code orderController}. * <pre class="code"> * RouterFunction<ServerResponse> nestedRoute = * RouterFunctions.route() * .path("/user", userController::routerFunction) * .path("/order", orderController::routerFunction) * .build(); * </pre> * @param pattern the pattern to match to * @param routerFunctionSupplier supplier for the nested router function to delegate to if * the pattern matches * @return this builder */ Builder path(String pattern, Supplier<RouterFunction<ServerResponse>> routerFunctionSupplier); /** * Route to a built router function if the given path prefix pattern applies. * This method can be used to create <strong>nested routes</strong>, where a group of routes * share a common path prefix. * <p>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. * <pre class="code"> * RouterFunction<ServerResponse> nestedRoute = * RouterFunctions.route() * .path("/user", builder -> * builder.GET(this::listUsers) * .POST(this::createUser)) * .build(); * </pre> * @param pattern the pattern to match to * @param builderConsumer consumer for a {@code Builder} that provides the nested router * function * @return this builder */ Builder path(String pattern, Consumer<Builder> 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. * <p>For instance, the following example creates a filter that returns a 401 Unauthorized * response if the request does not contain the necessary authentication headers. * <pre class="code"> * RouterFunction<ServerResponse> filteredRoute = * RouterFunctions.route() * .GET("/user", this::listUsers) * .filter((request, next) -> { * // check for authentication headers * if (isAuthenticated(request)) { * return next.handle(request); * } * else { * return ServerResponse.status(HttpStatus.UNAUTHORIZED).build(); * } * }) * .build(); * </pre> * @param filterFunction the function to filter all routes built by this builder * @return this builder */ Builder filter(HandlerFilterFunction<ServerResponse, ServerResponse> filterFunction); /** * 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. * <p>For instance, the following example creates a filter that logs the request before * the handler function executes. * <pre class="code"> * RouterFunction<ServerResponse> filteredRoute = * RouterFunctions.route() * .GET("/user", this::listUsers) * .before(request -> { * log(request); * return request; * }) * .build(); * </pre> * @param requestProcessor a function that transforms the request * @return this builder */ Builder before(Function<ServerRequest, ServerRequest> requestProcessor); /** * 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. * <p>For instance, the following example creates a filter that logs the response after * the handler function executes. * <pre class="code"> * RouterFunction<ServerResponse> filteredRoute = * RouterFunctions.route() * .GET("/user", this::listUsers) * .after((request, response) -> { * log(response); * return response; * }) * .build(); * </pre> * @param responseProcessor a function that transforms the response * @return this builder */ Builder after(BiFunction<ServerRequest, ServerResponse, ServerResponse> responseProcessor); /** * Filters all exceptions that match the predicate by applying the given response provider * function. * <p>For instance, the following example creates a filter that returns a 500 response * status when an {@code IllegalStateException} occurs. * <pre class="code"> * RouterFunction<ServerResponse> filteredRoute = * RouterFunctions.route() * .GET("/user", this::listUsers) * .onError(e -> e instanceof IllegalStateException, * (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build()) * .build(); * </pre> * @param predicate the type of exception to filter * @param responseProvider a function that creates a response * @return this builder */ Builder onError(Predicate<? super Throwable> predicate, BiFunction<? super Throwable, ServerRequest, Mono<ServerResponse>> responseProvider); /** * Filters all exceptions of the given type by applying the given response provider * function. * <p>For instance, the following example creates a filter that returns a 500 response * status when an {@code IllegalStateException} occurs. * <pre class="code"> * RouterFunction<ServerResponse> filteredRoute = * RouterFunctions.route() * .GET("/user", this::listUsers) * .onError(IllegalStateException.class, * (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build()) * .build(); * </pre> * @param exceptionType the type of exception to filter * @param responseProvider a function that creates a response * @return this builder */ <T extends Throwable> Builder onError(Class<T> exceptionType, BiFunction<? super T, ServerRequest, Mono<ServerResponse>> responseProvider); /** * Builds the {@code RouterFunction}. All created routes are * {@linkplain RouterFunction#and(RouterFunction) composed} with one another, and filters * (if any) are applied to the result. * @return the built router function */ RouterFunction<ServerResponse> build(); } /** * Receives notifications from the logical structure of router functions. */ public interface Visitor { /** * Receive notification of the beginning of a nested router function. * @param predicate the predicate that applies to the nested router functions * @see RouterFunctions#nest(RequestPredicate, RouterFunction) */ void startNested(RequestPredicate predicate); /** * Receive notification of the end of a nested router function. * @param predicate the predicate that applies to the nested router functions * @see RouterFunctions#nest(RequestPredicate, RouterFunction) */ void endNested(RequestPredicate predicate); /** * Receive notification of a standard predicated route to a handler function. * @param predicate the predicate that applies to the handler function * @param handlerFunction the handler function. * @see RouterFunctions#route(RequestPredicate, HandlerFunction) */ void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction); /** * Receive notification of a resource router function. * @param lookupFunction the lookup function for the resources * @see RouterFunctions#resources(Function) */ void resources(Function<ServerRequest, Mono<Resource>> lookupFunction); /** * Receive notification of an unknown router function. This method is called for router * functions that were not created via the various {@link RouterFunctions} methods. * @param routerFunction the router function */ void unknown(RouterFunction<?> routerFunction); } private abstract static class AbstractRouterFunction<T extends ServerResponse> implements RouterFunction<T> { @Override public String toString() { ToStringVisitor visitor = new ToStringVisitor(); accept(visitor); return visitor.toString(); } } /** * A composed routing function that first invokes one function, and then invokes the * another function (of the same response type {@code T}) if this route had * {@linkplain Mono#empty() no result}. * @param <T> the server response type */ static final class SameComposedRouterFunction<T extends ServerResponse> extends AbstractRouterFunction<T> { private final RouterFunction<T> first; private final RouterFunction<T> second; public SameComposedRouterFunction(RouterFunction<T> first, RouterFunction<T> second) { this.first = first; this.second = second; } @Override public Mono<HandlerFunction<T>> route(ServerRequest request) { return this.first.route(request).switchIfEmpty(Mono.defer(() -> this.second.route(request))); } @Override public void accept(Visitor visitor) { this.first.accept(visitor); this.second.accept(visitor); } } /** * A composed routing function that first invokes one function, and then invokes * another function (of a different response type) if this route had * {@linkplain Mono#empty() no result}. */ static final class DifferentComposedRouterFunction extends AbstractRouterFunction<ServerResponse> { private final RouterFunction<?> first; private final RouterFunction<?> second; public DifferentComposedRouterFunction(RouterFunction<?> first, RouterFunction<?> second) { this.first = first; this.second = second; } @Override public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) { return this.first.route(request).map(this::cast) .switchIfEmpty(Mono.defer(() -> this.second.route(request).map(this::cast))); } @SuppressWarnings("unchecked") private <T extends ServerResponse> HandlerFunction<T> cast(HandlerFunction<?> handlerFunction) { return (HandlerFunction<T>) handlerFunction; } @Override public void accept(Visitor visitor) { this.first.accept(visitor); this.second.accept(visitor); } } /** * Filter the specified {@linkplain HandlerFunction handler functions} with the given * {@linkplain HandlerFilterFunction filter function}. * @param <T> the type of the {@linkplain HandlerFunction handler function} to filter * @param <S> the type of the response of the function */ static final class FilteredRouterFunction<T extends ServerResponse, S extends ServerResponse> implements RouterFunction<S> { private final RouterFunction<T> routerFunction; private final HandlerFilterFunction<T, S> filterFunction; public FilteredRouterFunction(RouterFunction<T> routerFunction, HandlerFilterFunction<T, S> filterFunction) { this.routerFunction = routerFunction; this.filterFunction = filterFunction; } @Override public Mono<HandlerFunction<S>> route(ServerRequest request) { return this.routerFunction.route(request).map(this.filterFunction::apply); } @Override public void accept(Visitor visitor) { this.routerFunction.accept(visitor); } @Override public String toString() { return this.routerFunction.toString(); } } private static final class DefaultRouterFunction<T extends ServerResponse> extends AbstractRouterFunction<T> { private final RequestPredicate predicate; private final HandlerFunction<T> handlerFunction; public DefaultRouterFunction(RequestPredicate predicate, HandlerFunction<T> handlerFunction) { Assert.notNull(predicate, "Predicate must not be null"); Assert.notNull(handlerFunction, "HandlerFunction must not be null"); this.predicate = predicate; this.handlerFunction = handlerFunction; } @Override public Mono<HandlerFunction<T>> route(ServerRequest request) { if (this.predicate.test(request)) { if (logger.isTraceEnabled()) { String logPrefix = request.exchange().getLogPrefix(); logger.trace(logPrefix + String.format("Matched %s", this.predicate)); } return Mono.just(this.handlerFunction); } else { return Mono.empty(); } } @Override public void accept(Visitor visitor) { visitor.route(this.predicate, this.handlerFunction); } } private static final class DefaultNestedRouterFunction<T extends ServerResponse> extends AbstractRouterFunction<T> { private final RequestPredicate predicate; private final RouterFunction<T> routerFunction; public DefaultNestedRouterFunction(RequestPredicate predicate, RouterFunction<T> routerFunction) { Assert.notNull(predicate, "Predicate must not be null"); Assert.notNull(routerFunction, "RouterFunction must not be null"); this.predicate = predicate; this.routerFunction = routerFunction; } @Override public Mono<HandlerFunction<T>> route(ServerRequest serverRequest) { return this.predicate.nest(serverRequest).map(nestedRequest -> { if (logger.isTraceEnabled()) { String logPrefix = serverRequest.exchange().getLogPrefix(); logger.trace(logPrefix + String.format("Matched nested %s", this.predicate)); } return this.routerFunction.route(nestedRequest).doOnNext(match -> { if (nestedRequest != serverRequest) { serverRequest.attributes().clear(); serverRequest.attributes().putAll(nestedRequest.attributes()); } }); }).orElseGet(Mono::empty); } @Override public void accept(Visitor visitor) { visitor.startNested(this.predicate); this.routerFunction.accept(visitor); visitor.endNested(this.predicate); } } private static class ResourcesRouterFunction extends AbstractRouterFunction<ServerResponse> { private final Function<ServerRequest, Mono<Resource>> lookupFunction; public ResourcesRouterFunction(Function<ServerRequest, Mono<Resource>> lookupFunction) { Assert.notNull(lookupFunction, "Function must not be null"); this.lookupFunction = lookupFunction; } @Override public Mono<HandlerFunction<ServerResponse>> route(ServerRequest request) { return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new); } @Override public void accept(Visitor visitor) { visitor.resources(this.lookupFunction); } } private static class HandlerStrategiesResponseContext implements ServerResponse.Context { private final HandlerStrategies strategies; public HandlerStrategiesResponseContext(HandlerStrategies strategies) { this.strategies = strategies; } @Override public List<HttpMessageWriter<?>> messageWriters() { return this.strategies.messageWriters(); } @Override public List<ViewResolver> viewResolvers() { return this.strategies.viewResolvers(); } } private static class RouterFunctionWebHandler implements WebHandler { private static final HandlerFunction<ServerResponse> NOT_FOUND_HANDLER = request -> ServerResponse .notFound().build(); private final HandlerStrategies strategies; private final RouterFunction<?> routerFunction; public RouterFunctionWebHandler(HandlerStrategies strategies, RouterFunction<?> routerFunction) { this.strategies = strategies; this.routerFunction = routerFunction; } @Override public Mono<Void> handle(ServerWebExchange exchange) { return Mono.defer(() -> { ServerRequest request = new DefaultServerRequest(exchange, this.strategies.messageReaders()); addAttributes(exchange, request); return this.routerFunction.route(request).defaultIfEmpty(notFound()) .flatMap(handlerFunction -> wrapException(() -> handlerFunction.handle(request))) .flatMap(response -> wrapException(() -> response.writeTo(exchange, new HandlerStrategiesResponseContext(this.strategies)))); }); } private void addAttributes(ServerWebExchange exchange, ServerRequest request) { Map<String, Object> attributes = exchange.getAttributes(); attributes.put(REQUEST_ATTRIBUTE, request); } @SuppressWarnings("unchecked") private static <T extends ServerResponse> HandlerFunction<T> notFound() { return (HandlerFunction<T>) NOT_FOUND_HANDLER; } private static <T> Mono<T> wrapException(Supplier<Mono<T>> supplier) { try { return supplier.get(); } catch (Throwable ex) { return Mono.error(ex); } } } }