spring-framework/src/docs/asciidoc/web/webflux-functional.adoc

435 lines
14 KiB
Plaintext
Raw Normal View History

[[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 annotation-based programming model but otherwise runs on
the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation.
[[webflux-fn-overview]]
== Overview
An HTTP request is handled with a `HandlerFunction` that takes `ServerRequest` and
returns `Mono<ServerResponse>`, both of which are immutable contracts that offer
JDK 8-friendly access to the HTTP request and response. `HandlerFunction` is the equivalent of
a `@RequestMapping` method in the annotation-based programming model.
Requests are routed to a `HandlerFunction` with a `RouterFunction` that takes
`ServerRequest` and returns `Mono<HandlerFunction>`. When a request is matched to a
particular route, the `HandlerFunction` mapped to the route is used. `RouterFunction` is
the equivalent of a `@RequestMapping` annotation.
`RouterFunctions.route(RequestPredicate, HandlerFunction)` provides a router function
default implementation that can be used with a number of built-in request predicates,
as the following example shows:
====
[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<ServerResponse> 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<ServerResponse> listPeople(ServerRequest request) {
// ...
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
// ...
}
public Mono<ServerResponse> 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 <<web-reactive.adoc#webflux-httphandler,server adapters>>:
* `RouterFunctions.toHttpHandler(RouterFunction)`
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
Most applications can run through the WebFlux Java configuration, see <<webflux-fn-running>>.
[[webflux-fn-handler-functions]]
== HandlerFunction
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
access to the HTTP request and response with
http://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
<<web-reactive.adoc#webflux-reactive-libraries,Reactive Libraries>>.
[[webflux-fn-request]]
=== Using `ServerRequest`
`ServerRequest` provides access to the HTTP method, URI, headers, and query parameters,
while access to the body is provided through the `body` methods.
The following example extracts the request body to a `Mono<String>`:
====
[source,java]
----
Mono<String> string = request.bodyToMono(String.class);
----
====
The following example extracts the body to a `Flux<Person>`, where `Person` objects are decoded from some
serialized form, such as JSON or XML:
====
[source,java]
----
Flux<Person> people = request.bodyToFlux(Person.class);
----
====
The preceding examples 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 preceding examples can
also be written as follows:
====
[source,java]
----
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
----
====
The following example shows how to access form data:
====
[source,java]
----
Mono<MultiValueMap<String, String> map = request.body(BodyExtractors.toFormData());
----
====
The following example shows how to access multipart data as a map:
====
[source,java]
----
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
----
====
The following example shows how to access multiparts, one at a time, in streaming fashion:
====
[source,java]
----
Flux<Part> parts = request.body(BodyExtractos.toParts());
----
====
[[webflux-fn-response]]
=== Using `ServerResponse`
`ServerResponse` provides access to the HTTP response and, since it is immutable, you can use
a `build` method to create it. You can use the builder to set the response status, to add response
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
content:
====
[source,java]
----
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
----
====
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
====
[source,java]
----
URI location = ...
ServerResponse.created(location).build();
----
====
[[webflux-fn-handler-classes]]
=== Handler Classes
We can write a handler function as a lambda, as the following example shows:
====
[source,java,indent=0]
[subs="verbatim,quotes"]
----
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body(fromObject("Hello World"));
----
====
That is convenient, but, in an application, we need multiple functions, and it is useful to group
related handler functions together into a handler (like a `@Controller`). For example,
the following class 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<ServerResponse> listPeople(ServerRequest request) { // <1>
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) { // <2>
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> 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<Void>`: 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<Void>)` method to send a response when that completion signal is received (that is,
when the `Person` has been saved).
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
found. If it is not found, we use `switchIfEmpty(Mono<T>)` to return a 404 Not Found response.
====
[[webflux-fn-router-functions]]
== Using `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`. Otherwise, no routing is performed,
and that would translate to a 404 (Not Found) response.
[[webflux-fn-predicates]]
=== Using Predicates
You can write your own `RequestPredicate`, but the `RequestPredicates` utility class
offers commonly used implementations, based on the request path, HTTP method, content-type,
and so on. The following example creates a request predicate based on a path:
====
[source,java,indent=0]
[subs="verbatim,quotes"]
----
RouterFunction<ServerResponse> route =
RouterFunctions.route(RequestPredicates.path("/hello-world"),
request -> Response.ok().body(fromObject("Hello World")));
----
====
You can compose multiple request predicates together by using:
* `RequestPredicate.and(RequestPredicate)` -- both must match.
* `RequestPredicate.or(RequestPredicate)` -- either can 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 are evaluated in order,
and, if the first route does not 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 by using:
* `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 <<webflux-fn-handler-class>>) 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<ServerResponse> 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` by using one of the following:
* `RouterFunctions.toHttpHandler(RouterFunction)`
* `RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)`
You can then use the returned `HttpHandler` with a number of server adapters by following
<<web-reactive.adoc#webflux-httphandler,HttpHandler>> for server-specific instructions.
A more advanced option is to run with a
<<web-reactive.adoc#webflux-dispatcher-handler,`DispatcherHandler`>>-based setup through the
<<web-reactive.adoc#webflux-config>>, which uses Spring configuration to declare the
components required to process requests. The WebFlux Java configuration declares the following
infrastructure components to support functional endpoints:
* `RouterFunctionMapping`: Detects one or more `RouterFunction<?>` beans in the Spring
configuration, combines them through `RouterFunction.andOther`, and routes requests to the
resulting composed `RouterFunction`.
* `HandlerFunctionAdapter`: Simple adapter that lets `DispatcherHandler` 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 preceding components let functional endpoints 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 by the Spring Boot WebFlux
starter.
The following example shows a WebFlux Java configuration (see
<<web-reactive.adoc#webflux-dispatcher-handler,DispatcherHandler>> for how to run it):
====
[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]]
== Using `HandlerFilterFunction`
You can filter routes mapped by a router function 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 it can also be another `FilterFunction` if multiple filters
are applied.
With annotations, you can achieve similar functionality by using `@ControllerAdvice`, a `ServletFilter`, or both.
Now we can add a simple security filter to our route, assuming that we have a `SecurityManager` that
can determine whether a particular path is allowed. The following example shows how to do so:
====
[source,java,indent=0]
[subs="verbatim,quotes"]
----
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...
RouterFunction<ServerResponse> filteredRoute =
route.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
----
====
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional. We
allow only the handler function to be executed when access is allowed.
NOTE: CORS support for functional endpoints is provided through a dedicated <<webflux-cors-webfilter,`CorsWebFilter`>>.