[[webflux-fn]] = Functional Endpoints Spring WebFlux includes a lightweight, functional programming model in which functions are used to route and handle requests and contracts are designed for immutability. It is an alternative to the annotated-based programming model but otherwise running on the same <> foundation [[webflux-fn-overview]] == Overview An HTTP request is handled with a **`HandlerFunction`** that takes `ServerRequest` and returns `Mono`, both of which are immutable contracts that offer JDK-8 friendly access to the HTTP request and response. `HandlerFunction` is the equivalent of an `@RequestMapping` method in the annotation-based programming model. Requests are routed to a `HandlerFunction` with a **`RouterFunction`** that takes `ServerRequest` and returns `Mono`. When a request is matched to a particular route, the `HandlerFunction` mapped to the route is used. `RouterFunction` is the equivalent of an `@RequestMapping` annotation. `RouterFunctions.route(RequestPredicate, HandlerFunction)` provides a router function default implementation that can be used with a number of built-in request predicates. For example: [source,java,indent=0] [subs="verbatim,quotes"] ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; import static org.springframework.web.reactive.function.server.RouterFunctions.route; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction route = route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson) .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople) .andRoute(POST("/person"), handler::createPerson); public class PersonHandler { // ... public Mono listPeople(ServerRequest request) { // ... } public Mono createPerson(ServerRequest request) { // ... } public Mono getPerson(ServerRequest request) { // ... } } ---- One way to run a `RouterFunction` is to turn it into an `HttpHandler` and install it through one of the built-in <>: * `RouterFunctions.toHttpHandler(RouterFunction)` * `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)` Most applications will run through the WebFlux Java config, see <>. [[webflux-fn-handler-functions]] == HandlerFunction `ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK-8 friendly access to the HTTP request and response with https://www.reactive-streams.org[Reactive Streams] back pressure against the request and response body stream. The request body is represented with a Reactor `Flux` or `Mono`. The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`. For more on that see <>. [[webflux-fn-request]] === ServerRequest `ServerRequest` provides access to the HTTP method, URI, headers, and query parameters while access to the body is provided through the `body` methods. To extract the request body to a `Mono`: Mono string = request.bodyToMono(String.class); To extract the body to a `Flux`, where `Person` objects are decoded from some serialized form, such as JSON or XML: Flux people = request.bodyToFlux(Person.class); The above are shortcuts that use the more general `ServerRequest.body(BodyExtractor)` which accepts the `BodyExtractor` functional, strategy interface. The utility class `BodyExtractors` provides access to a number of instances. For example, the above can also be written as follows: Mono string = request.body(BodyExtractors.toMono(String.class)); Flux people = request.body(BodyExtractors.toFlux(Person.class)); To access form data: Mono map = request.body(BodyExtractors.toFormData()); To access multipart data as a map: Mono map = request.body(BodyExtractors.toMultipartData()); To access multiparts, one at a time, in streaming fashion: Flux parts = request.body(BodyExtractos.toParts()); [[webflux-fn-response]] === ServerResponse `ServerResponse` provides access to the HTTP response and since it is immutable, you use a build to create it. The builder can be used to set the response status, to add response headers, or to provide a body. Below is an example with a 200 (OK) response with JSON content: Mono person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class); This is how to build a 201 (CREATED) response with `"Location"` header, and no body: URI location = ... ServerResponse.created(location).build(); [[webflux-fn-handler-classes]] === Handler Classes We can write a handler function as a lambda. For example: [source,java,indent=0] [subs="verbatim,quotes"] ---- HandlerFunction helloWorld = request -> ServerResponse.ok().body(fromObject("Hello World")); ---- That is convenient but in an application we need multiple functions and useful to group related handler functions together into a handler (like an `@Controller`). For example, here is a class that exposes a reactive `Person` repository: [source,java,indent=0] [subs="verbatim,quotes"] ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.ServerResponse.ok; import static org.springframework.web.reactive.function.BodyInserters.fromObject; public class PersonHandler { private final PersonRepository repository; public PersonHandler(PersonRepository repository) { this.repository = repository; } public Mono listPeople(ServerRequest request) { // <1> Flux people = repository.allPeople(); return ok().contentType(APPLICATION_JSON).body(people, Person.class); } public Mono createPerson(ServerRequest request) { // <2> Mono person = request.bodyToMono(Person.class); return ok().build(repository.savePerson(person)); } public Mono getPerson(ServerRequest request) { // <3> int personId = Integer.valueOf(request.pathVariable("id")); return repository.getPerson(personId) .flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromObject(person))) .switchIfEmpty(ServerResponse.notFound().build()); } } ---- <1> `listPeople` is a handler function that returns all `Person` objects found in the repository as JSON. <2> `createPerson` is a handler function that stores a new `Person` contained in the request body. Note that `PersonRepository.savePerson(Person)` returns `Mono`: an empty Mono that emits a completion signal when the person has been read from the request and stored. So we use the `build(Publisher)` method to send a response when that completion signal is received, i.e. when the `Person` has been saved. <3> `getPerson` is a handler function that returns a single person, identified via the path variable `id`. We retrieve that `Person` via the repository, and create a JSON response if it is found. If it is not found, we use `switchIfEmpty(Mono)` to return a 404 Not Found response. [[webflux-fn-router-functions]] == RouterFunction `RouterFunction` is used to route requests to a `HandlerFunction`. Typically, you do not write router functions yourself, but rather use `RouterFunctions.route(RequestPredicate, HandlerFunction)`. If the predicate applies, the request is routed to the given `HandlerFunction`, or otherwise no routing is performed, and that would translate to a 404 (Not Found) response. [[webflux-fn-predicates]] === Predicates You can write your own `RequestPredicate`, but the `RequestPredicates` utility class offers commonly implementations, based on the request path, HTTP method, content-type, and so on. For example: [source,java,indent=0] [subs="verbatim,quotes"] ---- RouterFunction route = RouterFunctions.route(RequestPredicates.path("/hello-world"), request -> Response.ok().body(fromObject("Hello World"))); ---- You can compose multiple request predicates together via: * `RequestPredicate.and(RequestPredicate)` -- both must match. * `RequestPredicate.or(RequestPredicate)` -- either may match. Many of the predicates from `RequestPredicates` are composed. For example `RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)` and `RequestPredicates.path(String)`. You can compose multiple router functions into one, such that they're evaluated in order, and if the first route doesn't match, the second is evaluated. You can declare more specific routes before more general ones. [[webflux-fn-routes]] === Routes You can compose multiple router functions together via: * `RouterFunction.and(RouterFunction)` * `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -- shortcut for `RouterFunction.and()` with nested `RouterFunctions.route()`. Using composed routes and predicates, we can then declare the following routes, referring to methods in the `PersonHandler`, shown in <>, through https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html[method-references]: [source,java,indent=0] [subs="verbatim,quotes"] ---- import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction personRoute = route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson) .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople) .andRoute(POST("/person"), handler::createPerson); ---- [[webflux-fn-running]] == Running a server How do you run a router function in an HTTP server? A simple option is to convert a router function to an `HttpHandler` using one of the following: * `RouterFunctions.toHttpHandler(RouterFunction)` * `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)` The returned `HttpHandler` can then be used with a number of servers adapters by following <> for server-specific instructions. A more advanced option is to run with a <>-based setup through the <> which uses Spring configuration to declare the components quired to process requests. The WebFlux Java config declares the following infrastructure components to support functional endpoints: * `RouterFunctionMapping` -- detects one or more `RouterFunction` beans in the Spring configuration, combines them via `RouterFunction.andOther`, and routes requests to the resulting composed `RouterFunction`. * `HandlerFunctionAdapter` -- simple adapter that allows the `DispatcherHandler` to invoke a `HandlerFunction` that was mapped to a request. * `ServerResponseResultHandler` -- handles the result from the invocation of a `HandlerFunction` by invoking the `writeTo` method of the `ServerResponse`. The above components allow functional endpoints to fit within the `DispatcherHandler` request processing lifecycle, and also potentially run side by side with annotated controllers, if any are declared. It is also how functional endpoints are enabled the Spring Boot WebFlux starter. Below is example WebFlux Java config (see <> for how to run): [source,java,indent=0] [subs="verbatim,quotes"] ---- @Configuration @EnableWebFlux public class WebConfig implements WebFluxConfigurer { @Bean public RouterFunction routerFunctionA() { // ... } @Bean public RouterFunction routerFunctionB() { // ... } // ... @Override public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { // configure message conversion... } @Override public void addCorsMappings(CorsRegistry registry) { // configure CORS... } @Override public void configureViewResolvers(ViewResolverRegistry registry) { // configure view resolution for HTML rendering... } } ---- [[webflux-fn-handler-filter-function]] == HandlerFilterFunction Routes mapped by a router function can be filtered by calling `RouterFunction.filter(HandlerFilterFunction)`, where `HandlerFilterFunction` is essentially a function that takes a `ServerRequest` and `HandlerFunction`, and returns a `ServerResponse`. The handler function parameter represents the next element in the chain: this is typically the `HandlerFunction` that is routed to, but can also be another `FilterFunction` if multiple filters are applied. With annotations, similar functionality can be achieved using `@ControllerAdvice` and/or a `ServletFilter`. Let's add a simple security filter to our route, assuming that we have a `SecurityManager` that can determine whether a particular path is allowed: [source,java,indent=0] [subs="verbatim,quotes"] ---- import static org.springframework.http.HttpStatus.UNAUTHORIZED; SecurityManager securityManager = ... RouterFunction route = ... RouterFunction filteredRoute = route.filter((request, next) -> { if (securityManager.allowAccessTo(request.path())) { return next.handle(request); } else { return ServerResponse.status(UNAUTHORIZED).build(); } }); ---- You can see in this example that invoking the `next.handle(ServerRequest)` is optional: we only allow the handler function to be executed when access is allowed. [NOTE] ==== CORS support for functional endpoints is provided via a dedicated <>. ====