diff --git a/src/docs/asciidoc/web/webflux-functional.adoc b/src/docs/asciidoc/web/webflux-functional.adoc index b0ab7f12336..9da2daa29c4 100644 --- a/src/docs/asciidoc/web/webflux-functional.adoc +++ b/src/docs/asciidoc/web/webflux-functional.adoc @@ -9,60 +9,128 @@ 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.*; + +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 -Incoming HTTP requests are handled by a **`HandlerFunction`**, which is essentially a function that -takes a `ServerRequest` and returns a `Mono`. If you're familiar with the -annotation-based programming model, a handler function is the equivalent of an -`@RequestMapping` method. +`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 +<>. + -`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK-8 friendly access -to the underlying HTTP messages with http://www.reactive-streams.org[Reactive Streams] -non-blocking back pressure. The request exposes the body as Reactor `Flux` or `Mono` -types; the response accepts any Reactive Streams `Publisher` as body. The rational for this -is explained in <>. -`ServerRequest` gives access to various HTTP request elements: -the method, URI, query parameters, and headers (via a separate `ServerRequest.Headers` -interface. Access to the body is provided through the `body` methods. For instance, this is -how to extract the request body into a `Mono`: +[[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. This is how to extract +the request body to a `Mono`: Mono string = request.bodyToMono(String.class); -And here is how to extract the body into a `Flux`, where `Person` is a class that can be -deserialised from the contents of the body (i.e. `Person` is supported by Jackson if the body -contains JSON, or JAXB if XML). +This is how to extract the body into a `Flux`, where `Person` is a class that can be +deserialised, e.g. from JSON or XML: Flux people = request.bodyToFlux(Person.class); -The `bodyToMono` and `bodyToFlux` used above are in fact convenience methods that use the -generic `ServerRequest.body(BodyExtractor)` method. `BodyExtractor` is -a functional strategy interface that allows you to write your own extraction logic, but common -`BodyExtractor` instances can be found in the `BodyExtractors` utility class. So, the above -examples can also be written as follows: +`bodyToMono` and `bodyToFlux` are convenience methods that use the generic +`ServerRequest.body(BodyExtractor)` method. `BodyExtractor` is a functional strategy +interface that you can use to write your own extraction logic, but common `BodyExtractor` +instances can be obtained through the `BodyExtractors` utility class. The above examples +can also be written as follows: Mono string = request.body(BodyExtractors.toMono(String.class); Flux people = request.body(BodyExtractors.toFlux(Person.class); -Similarly, `ServerResponse` provides access to the HTTP response. Since it is immutable, you create -a `ServerResponse` with a builder. The builder allows you to set the response status, add response -headers, and provide a body. For instance, this is how to create a response with a 200 OK status, -a JSON content-type, and a body: + + +[[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); -And here is how to build a response with a 201 CREATED status, a `"Location"` header, and -empty body: +This is how to build a 201 (CREATED) response with `"Location"` header, and no body: URI location = ... ServerResponse.created(location).build(); -Putting these together allows us to create a `HandlerFunction`. For instance, here is an example -of a simple "Hello World" handler lambda, that returns a response with a 200 status and a body -based on a String: + +[[webflux-fn-handler-classes]] +=== Handler Classes + +We can write a handler function as a lambda. For example: [source,java,indent=0] [subs="verbatim,quotes"] @@ -71,15 +139,15 @@ HandlerFunction helloWorld = request -> ServerResponse.ok().body(fromObject("Hello World")); ---- -Writing handler functions as lambda's, as we do above, is convenient, but perhaps lacks in -readability and becomes less maintainable when dealing with multiple functions. Therefore, it is -recommended to group related handler functions into a handler or controller class. For example, +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 { @@ -92,21 +160,19 @@ public class PersonHandler { public Mono listPeople(ServerRequest request) { // <1> Flux people = repository.allPeople(); - return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class); + return ok().contentType(APPLICATION_JSON).body(people, Person.class); } public Mono createPerson(ServerRequest request) { // <2> Mono person = request.bodyToMono(Person.class); - return ServerResponse.ok().build(repository.savePerson(person)); + return ok().build(repository.savePerson(person)); } public Mono getPerson(ServerRequest request) { // <3> int personId = Integer.valueOf(request.pathVariable("id")); - Mono notFound = ServerResponse.notFound().build(); - Mono personMono = repository.getPerson(personId); - return personMono - .flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person))) - .switchIfEmpty(notFound); + return repository.getPerson(personId) + .flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromObject(person))) + .switchIfEmpty(ServerResponse.notFound().build()); } } ---- @@ -127,42 +193,56 @@ found. If it is not found, we use `switchIfEmpty(Mono)` to return a 404 Not F [[webflux-fn-router-functions]] == RouterFunction -Incoming requests are routed to handler functions with a **`RouterFunction`**, which is a function -that takes a `ServerRequest`, and returns a `Mono`. If a request matches a -particular route, a handler function is returned, or otherwise an empty `Mono` is returned. -`RouterFunction` has a similar purpose as the `@RequestMapping` annotation in the -annotation-based programming model. - -Typically, you do not write router functions yourself, but rather use -`RouterFunctions.route(RequestPredicate, HandlerFunction)` to -create one using a request predicate and handler function. If the predicate applies, the request is -routed to the given handler function; otherwise no routing is performed, resulting in a -404 Not Found response. -Though you can write your own `RequestPredicate`, you do not have to: the `RequestPredicates` -utility class offers commonly used predicates, such matching based on path, HTTP method, -content-type, etc. -Using `route`, we can route to our "Hello World" handler function: +`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 helloWorldRoute = +RouterFunction route = RouterFunctions.route(RequestPredicates.path("/hello-world"), request -> Response.ok().body(fromObject("Hello World"))); ---- -Two router functions can be composed into a new router function that routes to either handler -function: if the predicate of the first route does not match, the second is evaluated. -Composed router functions are evaluated in order, so it makes sense to put specific functions -before generic ones. -You can compose two router functions by calling `RouterFunction.and(RouterFunction)`, or by calling -`RouterFunction.andRoute(RequestPredicate, HandlerFunction)`, which is a convenient combination -of `RouterFunction.and()` with `RouterFunctions.route()`. +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()`. -Given the `PersonHandler` we showed above, we can now define a router function that routes to the -respective handler functions. -We use https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html[method-references] -to refer to the handler functions: +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"] @@ -176,16 +256,9 @@ 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").and(contentType(APPLICATION_JSON)), handler::createPerson); + .andRoute(POST("/person"), handler::createPerson); ---- -Besides router functions, you can also compose request predicates, by calling -`RequestPredicate.and(RequestPredicate)` or `RequestPredicate.or(RequestPredicate)`. -These work as expected: for `and` the resulting predicate matches if *both* given predicates match; -`or` matches if *either* predicate does. -Most of the predicates found in `RequestPredicates` are compositions. -For instance, `RequestPredicates.GET(String)` is a composition of -`RequestPredicates.method(HttpMethod)` and `RequestPredicates.path(String)`.