org.springframework.web.reactive.function.server.RouterFunctions Maven / Gradle / Ivy
/*
* Copyright 2002-2018 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
*
* http://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.Function;
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;
/**
* Central entry point to Spring's functional web framework.
* Exposes routing functionality, such as 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.
*
* 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";
private static final HandlerFunction NOT_FOUND_HANDLER = request -> ServerResponse.notFound().build();
/**
* 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/");
* RoutingFunction<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
*/
public static RouterFunction resources(String pattern, Resource location) {
return resources(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);
}
/**
* Convert the given {@linkplain RouterFunction router function} into a {@link HttpHandler}.
* This conversion uses {@linkplain HandlerStrategies#builder() default strategies}.
* The returned handler can be adapted to run in
*
* - Servlet 3.1+ using the
* {@link org.springframework.http.server.reactive.ServletHttpHandlerAdapter},
* - Reactor using the
* {@link org.springframework.http.server.reactive.ReactorHttpHandlerAdapter},
* - Undertow using the
* {@link org.springframework.http.server.reactive.UndertowHttpHandlerAdapter}.
*
* 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.
*
The returned {@code HttpHandler} can be adapted to run in
*
* - Servlet 3.1+ using the
* {@link org.springframework.http.server.reactive.ServletHttpHandlerAdapter},
* - Reactor using the
* {@link org.springframework.http.server.reactive.ReactorHttpHandlerAdapter},
* - Undertow using the
* {@link org.springframework.http.server.reactive.UndertowHttpHandlerAdapter}.
*
* @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 exchange -> {
ServerRequest request = new DefaultServerRequest(exchange, strategies.messageReaders());
addAttributes(exchange, request);
return routerFunction.route(request)
.defaultIfEmpty(notFound())
.flatMap(handlerFunction -> wrapException(() -> handlerFunction.handle(request)))
.flatMap(response -> wrapException(() -> response.writeTo(exchange,
new HandlerStrategiesResponseContext(strategies))));
};
}
private static Mono wrapException(Supplier> supplier) {
try {
return supplier.get();
}
catch (Throwable t) {
return Mono.error(t);
}
}
private static void addAttributes(ServerWebExchange exchange, ServerRequest request) {
Map attributes = exchange.getAttributes();
attributes.put(REQUEST_ATTRIBUTE, request);
}
@SuppressWarnings("unchecked")
private static HandlerFunction notFound() {
return (HandlerFunction) NOT_FOUND_HANDLER;
}
@SuppressWarnings("unchecked")
static HandlerFunction cast(HandlerFunction handlerFunction) {
return (HandlerFunction) handlerFunction;
}
/**
* 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 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 implements RouterFunction {
@Override
public String toString() {
ToStringVisitor visitor = new ToStringVisitor();
accept(visitor);
return visitor.toString();
}
}
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 Mono> 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);
}
}
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
public Mono> route(ServerRequest request) {
return this.first.route(request)
.map(RouterFunctions::cast)
.switchIfEmpty(Mono.defer(() -> this.second.route(request).map(RouterFunctions::cast)));
}
@Override
public void accept(Visitor visitor) {
this.first.accept(visitor);
this.second.accept(visitor);
}
}
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 Mono> route(ServerRequest request) {
return this.routerFunction.route(request).map(this.filterFunction::apply);
}
@Override
public void accept(Visitor visitor) {
this.routerFunction.accept(visitor);
}
}
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 Mono> route(ServerRequest request) {
if (this.predicate.test(request)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Predicate \"%s\" matches against \"%s\"", this.predicate, request));
}
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
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 Mono> route(ServerRequest serverRequest) {
return this.predicate.nest(serverRequest)
.map(nestedRequest -> {
if (logger.isDebugEnabled()) {
logger.debug(
String.format(
"Nested predicate \"%s\" matches against \"%s\"",
this.predicate, serverRequest));
}
return this.routerFunction.route(nestedRequest);
}
)
.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 {
private final Function> lookupFunction;
public ResourcesRouterFunction(Function> lookupFunction) {
Assert.notNull(lookupFunction, "Function must not be null");
this.lookupFunction = lookupFunction;
}
@Override
public Mono> 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> messageWriters() {
return this.strategies.messageWriters();
}
@Override
public List viewResolvers() {
return this.strategies.viewResolvers();
}
}
}