All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.servlet.function.RouterFunctions Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2020 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.servlet.function;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
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 org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.web.util.pattern.PathPatternParser;

/**
 * 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
 * function.
 *
 * @author Arjen Poutsma
 * @since 5.2
 */
public abstract class RouterFunctions {

	private static final Log logger = LogFactory.getLog(RouterFunctions.class);

	/**
	 * Name of the request attribute that contains the {@link ServerRequest}.
	 */
	public static final String REQUEST_ATTRIBUTE = RouterFunctions.class.getName() + ".request";

	/**
	 * Name of the request 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 request 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
	 */
	public static Builder route() {
		return new RouterFunctionBuilder();
	}

	/**
	 * Route to the given handler function 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.route(RequestPredicates.GET("/user"), userController::listUsers);
	 * 
* @param predicate the predicate to test * @param handlerFunction the handler function to route to if the predicate applies * @param 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 RouterFunction route( RequestPredicate predicate, HandlerFunction 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 nested routes, where a group of routes share a common path * (prefix), header, or other request predicate. *

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. *

	 * 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);
	 * 
* @param predicate the predicate to test * @param routerFunction the nested router function to delegate to if the predicate applies * @param 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 RouterFunction nest( RequestPredicate predicate, RouterFunction routerFunction) { return new DefaultNestedRouterFunction<>(predicate, 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 a router function that routes to resources * @see #resourceLookupFunction(String, Resource) */ public static RouterFunction 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: *
	 * Optional<Resource> defaultResource = Optional.of(new ClassPathResource("index.html"));
	 * Function<ServerRequest, Optional<Resource>> lookupFunction =
	 *   RouterFunctions.resourceLookupFunction("/resources/**", new FileSystemResource("public-resources/"))
	 *     .andThen(resource -> resource.or(() -> defaultResource));
	 * RouterFunction<ServerResponse> resources = RouterFunctions.resources(lookupFunction);
     * 
* @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> 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 resources(Function> lookupFunction) { return new ResourcesRouterFunction(lookupFunction); } /** * Changes the {@link PathPatternParser} on the given {@linkplain RouterFunction router function}. This method * can be used to change the {@code PathPatternParser} properties from the defaults, for instance to change * {@linkplain PathPatternParser#setCaseSensitive(boolean) case sensitivity}. * @param routerFunction the router function to change the parser in * @param parser the parser to change to. * @param the type of response returned by the handler function * @return the change router function */ public static RouterFunction changeParser(RouterFunction routerFunction, PathPatternParser parser) { Assert.notNull(routerFunction, "RouterFunction must not be null"); Assert.notNull(parser, "Parser must not be null"); ChangePathPatternParserVisitor visitor = new ChangePathPatternParserVisitor(parser); routerFunction.accept(visitor); return routerFunction; } /** * Represents a discoverable builder for router functions. * Obtained via {@link RouterFunctions#route()}. */ public interface Builder { /** * Adds a route to the given handler function that handles HTTP {@code GET} requests. * @param handlerFunction the handler function to handle all {@code GET} requests * @return this builder * @since 5.3 */ Builder GET(HandlerFunction handlerFunction); /** * 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code GET} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code GET} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder GET(RequestPredicate predicate, HandlerFunction handlerFunction); /** * 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} and the predicate * @return this builder * @see RequestPredicates */ Builder GET(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles HTTP {@code HEAD} requests. * @param handlerFunction the handler function to handle all {@code HEAD} requests * @return this builder * @since 5.3 */ Builder HEAD(HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code HEAD} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code HEAD} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder HEAD(RequestPredicate predicate, HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles HTTP {@code POST} requests. * @param handlerFunction the handler function to handle all {@code POST} requests * @return this builder * @since 5.3 */ Builder POST(HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code POST} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code POST} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder POST(RequestPredicate predicate, HandlerFunction handlerFunction); /** * 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 POST(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles HTTP {@code PUT} requests. * @param handlerFunction the handler function to handle all {@code PUT} requests * @return this builder * @since 5.3 */ Builder PUT(HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PUT} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code PUT} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder PUT(RequestPredicate predicate, HandlerFunction handlerFunction); /** * 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 PUT(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles HTTP {@code PATCH} requests. * @param handlerFunction the handler function to handle all {@code PATCH} requests * @return this builder * @since 5.3 */ Builder PATCH(HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code PATCH} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code PATCH} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder PATCH(RequestPredicate predicate, HandlerFunction handlerFunction); /** * 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 PATCH(String pattern, RequestPredicate predicate, HandlerFunction handlerFunction); /** * Adds a route to the given handler function that handles HTTP {@code DELETE} requests. * @param handlerFunction the handler function to handle all {@code DELETE} requests * @return this builder * @since 5.3 */ Builder DELETE(HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code DELETE} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code DELETE} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder DELETE(RequestPredicate predicate, HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles HTTP {@code OPTIONS} requests. * @param handlerFunction the handler function to handle all {@code OPTIONS} requests * @return this builder * @since 5.3 */ Builder OPTIONS(HandlerFunction 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 handlerFunction); /** * Adds a route to the given handler function that handles all HTTP {@code OPTIONS} requests * that match the given predicate. * @param predicate predicate to match * @param handlerFunction the handler function to handle all {@code OPTIONS} requests that * match {@code predicate} * @return this builder * @since 5.3 * @see RequestPredicates */ Builder OPTIONS(RequestPredicate predicate, 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 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 * @see RequestPredicates */ Builder route(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<ServerResponse> 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 * can be used to create nested routes, where a group of routes share a * common path (prefix), header, or other request predicate. *

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.route()
		 *     .nest(RequestPredicates.path("/user"), () ->
		 *       RouterFunctions.route()
		 *         .GET(this::listUsers)
		 *         .POST(this::createUser)
		 *         .build())
		 *     .build();
		 * 
* @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> routerFunctionSupplier); /** * Route to a built router function if the given request predicate 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. *

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.route()
		 *     .nest(RequestPredicates.path("/user"), builder ->
		 *       builder.GET(this::listUsers)
		 *              .POST(this::createUser))
		 *     .build();
		 * 
* @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 builderConsumer); /** * 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. 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 that delegates to the router function defined in {@code userController}, * and with a "/order" path that delegates to {@code orderController}. *

		 * RouterFunction<ServerResponse> nestedRoute =
		 *   RouterFunctions.route()
		 *     .path("/user", userController::routerFunction)
		 *     .path("/order", orderController::routerFunction)
		 *     .build();
		 * 
* @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> 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. *

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.route()
		 *     .path("/user", builder ->
		 *       builder.GET(this::listUsers)
		 *              .POST(this::createUser))
		 *     .build();
		 * 
* @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 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 returns a 401 Unauthorized * response if the request does not contain the necessary authentication headers. *

		 * 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();
		 * 
* @param filterFunction the function to filter all routes built by this builder * @return this builder */ Builder filter(HandlerFilterFunction 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. *

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

		 * RouterFunction<ServerResponse> filteredRoute =
		 *   RouterFunctions.route()
		 *     .GET("/user", this::listUsers)
		 *     .before(request -> {
		 *       log(request);
		 *       return request;
		 *     })
		 *     .build();
		 * 
* @param requestProcessor a function that transforms the request * @return this builder */ Builder before(Function 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. *

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

		 * RouterFunction<ServerResponse> filteredRoute =
		 *   RouterFunctions.route()
		 *     .GET("/user", this::listUsers)
		 *     .after((request, response) -> {
		 *       log(response);
		 *       return response;
		 *     })
		 *     .build();
		 * 
* @param responseProcessor a function that transforms the response * @return this builder */ Builder after(BiFunction responseProcessor); /** * Filters all exceptions that match the predicate by applying the given response provider * function. *

For instance, the following example creates a filter that returns a 500 response * status when an {@code IllegalStateException} occurs. *

		 * RouterFunction<ServerResponse> filteredRoute =
		 *   RouterFunctions.route()
		 *     .GET("/user", this::listUsers)
		 *     .onError(e -> e instanceof IllegalStateException,
		 *       (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build())
		 *     .build();
		 * 
* @param predicate the type of exception to filter * @param responseProvider a function that creates a response * @return this builder */ Builder onError(Predicate predicate, BiFunction responseProvider); /** * Filters all exceptions of the given type by applying the given response provider * function. *

For instance, the following example creates a filter that returns a 500 response * status when an {@code IllegalStateException} occurs. *

		 * RouterFunction<ServerResponse> filteredRoute =
		 *   RouterFunctions.route()
		 *     .GET("/user", this::listUsers)
		 *     .onError(IllegalStateException.class,
		 *       (e, request) -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build())
		 *     .build();
		 * 
* @param exceptionType the type of exception to filter * @param responseProvider a function that creates a response * @return this builder */ Builder onError(Class exceptionType, BiFunction responseProvider); /** * Add an attribute with the given name and value to the last route built with this builder. * @param name the attribute name * @param value the attribute value * @return this builder * @since 5.3 */ Builder withAttribute(String name, Object value); /** * Manipulate the attributes of the last route built with the given consumer. *

The map provided to the consumer is "live", so that the consumer can be used * to {@linkplain Map#put(Object, Object) overwrite} existing attributes, * {@linkplain Map#remove(Object) remove} attributes, or use any of the other * {@link Map} methods. * @param attributesConsumer a function that consumes the attributes map * @return this builder * @since 5.3 */ Builder withAttributes(Consumer> attributesConsumer); /** * 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 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> lookupFunction); /** * Receive notification of a router function with attributes. The * given attributes apply to the router notification that follows this one. * @param attributes the attributes that apply to the following router * @since 5.3 */ void attributes(Map attributes); /** * 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); } abstract static class AbstractRouterFunction implements RouterFunction { @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 Optional#empty() no result}. * @param the server response type */ static final class SameComposedRouterFunction extends AbstractRouterFunction { private final RouterFunction first; private final RouterFunction second; public SameComposedRouterFunction(RouterFunction first, RouterFunction second) { this.first = first; this.second = second; } @Override public Optional> route(ServerRequest request) { Optional> firstRoute = this.first.route(request); if (firstRoute.isPresent()) { return firstRoute; } else { return 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 Optional#empty() no result}. */ static final class DifferentComposedRouterFunction extends AbstractRouterFunction { private final RouterFunction first; private final RouterFunction second; public DifferentComposedRouterFunction(RouterFunction first, RouterFunction second) { this.first = first; this.second = second; } @Override @SuppressWarnings("unchecked") public Optional> route(ServerRequest request) { Optional> firstRoute = this.first.route(request); if (firstRoute.isPresent()) { return (Optional>) firstRoute; } else { Optional> secondRoute = this.second.route(request); return (Optional>) secondRoute; } } @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 the type of the {@linkplain HandlerFunction handler function} to filter * @param the type of the response of the function */ static final class FilteredRouterFunction implements RouterFunction { private final RouterFunction routerFunction; private final HandlerFilterFunction filterFunction; public FilteredRouterFunction( RouterFunction routerFunction, HandlerFilterFunction filterFunction) { this.routerFunction = routerFunction; this.filterFunction = filterFunction; } @Override public Optional> 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 extends AbstractRouterFunction { private final RequestPredicate predicate; private final HandlerFunction handlerFunction; public DefaultRouterFunction(RequestPredicate predicate, HandlerFunction 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 Optional> route(ServerRequest request) { if (this.predicate.test(request)) { if (logger.isTraceEnabled()) { logger.trace(String.format("Predicate \"%s\" matches against \"%s\"", this.predicate, request)); } return Optional.of(this.handlerFunction); } else { return Optional.empty(); } } @Override public void accept(Visitor visitor) { visitor.route(this.predicate, this.handlerFunction); } } private static final class DefaultNestedRouterFunction extends AbstractRouterFunction { private final RequestPredicate predicate; private final RouterFunction routerFunction; public DefaultNestedRouterFunction(RequestPredicate predicate, RouterFunction 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 Optional> route(ServerRequest serverRequest) { return this.predicate.nest(serverRequest) .map(nestedRequest -> { if (logger.isTraceEnabled()) { logger.trace( String.format( "Nested predicate \"%s\" matches against \"%s\"", this.predicate, serverRequest)); } Optional> result = this.routerFunction.route(nestedRequest); if (result.isPresent() && nestedRequest != serverRequest) { serverRequest.attributes().clear(); serverRequest.attributes().putAll(nestedRequest.attributes()); } return result; } ) .orElseGet(Optional::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 { private final Function> lookupFunction; public ResourcesRouterFunction(Function> lookupFunction) { Assert.notNull(lookupFunction, "Function must not be null"); this.lookupFunction = lookupFunction; } @Override public Optional> route(ServerRequest request) { return this.lookupFunction.apply(request).map(ResourceHandlerFunction::new); } @Override public void accept(Visitor visitor) { visitor.resources(this.lookupFunction); } } static final class AttributesRouterFunction extends AbstractRouterFunction { private final RouterFunction delegate; private final Map attributes; public AttributesRouterFunction(RouterFunction delegate, Map attributes) { this.delegate = delegate; this.attributes = initAttributes(attributes); } private static Map initAttributes(Map attributes) { if (attributes.isEmpty()) { return Collections.emptyMap(); } else { return Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); } } @Override public Optional> route(ServerRequest request) { return this.delegate.route(request); } @Override public void accept(Visitor visitor) { visitor.attributes(this.attributes); this.delegate.accept(visitor); } @Override public RouterFunction withAttribute(String name, Object value) { Assert.hasLength(name, "Name must not be empty"); Assert.notNull(value, "Value must not be null"); Map attributes = new LinkedHashMap<>(this.attributes); attributes.put(name, value); return new AttributesRouterFunction<>(this.delegate, attributes); } @Override public RouterFunction withAttributes(Consumer> attributesConsumer) { Assert.notNull(attributesConsumer, "AttributesConsumer must not be null"); Map attributes = new LinkedHashMap<>(this.attributes); attributesConsumer.accept(attributes); return new AttributesRouterFunction<>(this.delegate, attributes); } } }