1
1
[[webflux-fn]]
2
2
= Functional Endpoints
3
3
4
- Spring WebFlux includes a lightweight functional programming model in which functions
4
+ Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
5
5
are used to route and handle requests and contracts are designed for immutability.
6
6
It is an alternative to the annotation-based programming model but otherwise runs on
7
7
the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation.
@@ -12,18 +12,20 @@ the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation.
12
12
[[webflux-fn-overview]]
13
13
== Overview
14
14
15
- An HTTP request is handled with a `HandlerFunction` that takes `ServerRequest` and
16
- returns `Mono<ServerResponse>`, both of which are immutable contracts that offer
17
- JDK 8-friendly access to the HTTP request and response. `HandlerFunction` is the equivalent of
18
- a `@RequestMapping` method in the annotation-based programming model.
15
+ In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
16
+ `ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
17
+ Both the request as the response object have immutable contracts that offer JDK 8-friendly
18
+ access to the HTTP request and response.
19
+ `HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
20
+ annotation-based programming model.
19
21
20
- Requests are routed to a `HandlerFunction` with a `RouterFunction` that takes
21
- `ServerRequest` and returns `Mono<HandlerFunction>`. When a request is matched to a
22
- particular route, the `HandlerFunction` mapped to the route is used. `RouterFunction` is
23
- the equivalent of a `@RequestMapping` annotation.
22
+ Incoming requests are routed to a handler function with a `RouterFunction`: a function that
23
+ takes `ServerRequest` and returns a delayed `HandlerFunction` (i.e. `Mono<HandlerFunction>`).
24
+ When the router function matches, a handler function is returned; otherwise an empty Mono.
25
+ `RouterFunction` is the equivalent of a `@RequestMapping` annotation, but with the major
26
+ difference that router functions provide not just data, but also behavior.
24
27
25
- `RouterFunctions.route(RequestPredicate, HandlerFunction)` provides a router function
26
- default implementation that can be used with a number of built-in request predicates,
28
+ `RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
27
29
as the following example shows:
28
30
29
31
====
@@ -37,10 +39,11 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
37
39
PersonRepository repository = ...
38
40
PersonHandler handler = new PersonHandler(repository);
39
41
40
- RouterFunction<ServerResponse> route =
41
- route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
42
- .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
43
- .andRoute(POST("/person"), handler::createPerson);
42
+ RouterFunction<ServerResponse> route = route()
43
+ .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
44
+ .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
45
+ .POST("/person", handler::createPerson)
46
+ .build();
44
47
45
48
46
49
public class PersonHandler {
@@ -77,12 +80,12 @@ Most applications can run through the WebFlux Java configuration, see <<webflux-
77
80
== HandlerFunction
78
81
79
82
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
80
- access to the HTTP request and response with
81
- http://www.reactive-streams.org[Reactive Streams] back pressure against the request
82
- and response body stream. The request body is represented with a Reactor `Flux` or `Mono` .
83
- The response body is represented with any Reactive Streams `Publisher`, including `Flux`
84
- and `Mono`. For more on that, see
85
- <<web-reactive.adoc#webflux-reactive-libraries,Reactive Libraries>>.
83
+ access to the HTTP request and response.
84
+ Both request and response provide http://www.reactive-streams.org[Reactive Streams] back pressure
85
+ against the body streams .
86
+ The request body is represented with a Reactor `Flux` or `Mono`.
87
+ The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`.
88
+ For more on that, see <<web-reactive.adoc#webflux-reactive-libraries,Reactive Libraries>>.
86
89
87
90
88
91
@@ -195,9 +198,11 @@ HandlerFunction<ServerResponse> helloWorld =
195
198
----
196
199
====
197
200
198
- That is convenient, but, in an application, we need multiple functions, and it is useful to group
199
- related handler functions together into a handler (like a `@Controller`). For example,
200
- the following class exposes a reactive `Person` repository:
201
+ That is convenient, but in an application we need multiple functions, and multiple inline
202
+ lambda's can get messy.
203
+ Therefore, it is useful to group related handler functions together into a handler class, which
204
+ has a similar role as `@Controller` in an annotation-based application.
205
+ For example, the following class exposes a reactive `Person` repository:
201
206
202
207
====
203
208
[source,java,indent=0]
@@ -251,28 +256,40 @@ found. If it is not found, we use `switchIfEmpty(Mono<T>)` to return a 404 Not F
251
256
[[webflux-fn-router-functions]]
252
257
== `RouterFunction`
253
258
254
- `RouterFunction` is used to route requests to a `HandlerFunction`. Typically, you do not
255
- write router functions yourself, but rather use
256
- `RouterFunctions.route(RequestPredicate, HandlerFunction)`. If the predicate applies, the
257
- request is routed to the given `HandlerFunction`. Otherwise, no routing is performed,
258
- and that would translate to a 404 (Not Found) response.
259
+ Router functions are used to route the requests to the corresponding `HandlerFunction`.
260
+ Typically, you do not write router functions yourself, but rather use a method on the
261
+ `RouterFunctions` utility class to create one.
262
+ `RouterFunctions.route()` (no parameters) provides you with a fluent builder for creating a router
263
+ function, whereas `RouterFunctions.route(RequestPredicate, HandlerFunction)` offers a direct way
264
+ to create a router.
259
265
266
+ Generally, it is recommended to use the `route()` builder, as it provides
267
+ convenient short-cuts for typical mapping scenarios without requiring hard-to-discover
268
+ static imports.
269
+ For instance, the router function builder offers the method `GET(String, HandlerFunction)` to create a mapping for GET requests; and `POST(String, HandlerFunction)` for POSTs.
270
+
271
+ Besides HTTP method-based mapping, the route builder offers a way to introduce additional
272
+ predicates when mapping to requests.
273
+ For each HTTP method there is an overloaded variant that takes a `RequestPredicate` as a
274
+ parameter, though which additional constraints can be expressed.
260
275
261
276
262
277
[[webflux-fn-predicates]]
263
278
=== Predicates
264
279
265
280
You can write your own `RequestPredicate`, but the `RequestPredicates` utility class
266
281
offers commonly used implementations, based on the request path, HTTP method, content-type,
267
- and so on. The following example creates a request predicate based on a path:
282
+ and so on.
283
+ The following example uses a request predicate to create a constraint based on the `Accept`
284
+ header:
268
285
269
286
====
270
287
[source,java,indent=0]
271
288
[subs="verbatim,quotes"]
272
289
----
273
- RouterFunction<ServerResponse> route =
274
- RouterFunctions.route(RequestPredicates.path( "/hello-world"),
275
- request -> Response.ok().body(fromObject("Hello World")));
290
+ RouterFunction<ServerResponse> route = RouterFunctions.route()
291
+ .GET( "/hello-world", accept(MediaType.TEXT_PLAIN ),
292
+ request -> Response.ok().body(fromObject("Hello World")));
276
293
----
277
294
====
278
295
@@ -281,28 +298,34 @@ You can compose multiple request predicates together by using:
281
298
* `RequestPredicate.and(RequestPredicate)` -- both must match.
282
299
* `RequestPredicate.or(RequestPredicate)` -- either can match.
283
300
284
- Many of the predicates from `RequestPredicates` are composed. For example,
285
- `RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)`
301
+ Many of the predicates from `RequestPredicates` are composed.
302
+ For example, `RequestPredicates.GET(String)` is composed from `RequestPredicates.method(HttpMethod)`
286
303
and `RequestPredicates.path(String)`.
287
-
288
- You can compose multiple router functions into one, such that they are evaluated in order,
289
- and, if the first route does not match, the second is evaluated. You can declare more
290
- specific routes before more general ones.
304
+ The example shown above also uses two request predicates, as the builder uses
305
+ `RequestPredicates.GET` internally, and composes that with the `accept` predicate.
291
306
292
307
293
308
294
309
[[webflux-fn-routes]]
295
310
=== Routes
296
311
297
- You can compose multiple router functions together by using:
312
+ Router functions are evaluated in order: if the first route does not match, the
313
+ second is evaluated, and so on.
314
+ Therefore, it makes sense to declare more specific routes before general ones.
315
+ Note that this behavior is different from the annotation-based programming model, where the
316
+ "most specific" controller method is picked automatically.
317
+
318
+ When using the router function builder, all defined routes are composed into one
319
+ `RouterFunction` that is returned from `build()`.
320
+ There are also other ways to compose multiple router functions together:
298
321
322
+ * `add(RouterFunction)` on the `RouterFunctions.route()` builder
299
323
* `RouterFunction.and(RouterFunction)`
300
324
* `RouterFunction.andRoute(RequestPredicate, HandlerFunction)` -- shortcut for
301
325
`RouterFunction.and()` with nested `RouterFunctions.route()`.
302
326
303
- Using composed routes and predicates, we can then declare the following routes, referring
304
- to methods in the `PersonHandler` (shown in <<webflux-fn-handler-class>>) through
305
- https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html[method-references]:
327
+ The following example shows the composition of four routes:
328
+
306
329
307
330
====
308
331
[source,java,indent=0]
@@ -314,14 +337,70 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
314
337
PersonRepository repository = ...
315
338
PersonHandler handler = new PersonHandler(repository);
316
339
317
- RouterFunction<ServerResponse> personRoute =
318
- route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
319
- .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
320
- .andRoute(POST("/person"), handler::createPerson);
340
+ RouterFunction<ServerResponse> otherRoute = ...
341
+
342
+ RouterFunction<ServerResponse> route = route()
343
+ .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
344
+ .GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
345
+ .POST("/person", handler::createPerson) // <3>
346
+ .add(otherRoute) // <4>
347
+ .build();
321
348
----
349
+ <1> `GET /person/{id}` with an `Accept` header that matches JSON is routed to
350
+ `PersonHandler.getPerson`
351
+ <2> `GET /person` with an `Accept` header that matches JSON is routed to
352
+ `PersonHandler.listPeople`
353
+ <3> `POST /person` with no additional predicates is mapped to
354
+ `PersonHandler.createPerson`, and
355
+ <4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
356
+
322
357
====
323
358
324
359
360
+ === Nested Routes
361
+
362
+ It is common for a group of router functions to have a shared predicate, for instance a shared
363
+ path.
364
+ In the example above, the shared predicate would be a path predicate that matches `/person`,
365
+ used by three of the routes.
366
+ When using annotations, you would remove this duplication by using a type-level `@RequestMapping`
367
+ annotation that maps to `/person`.
368
+ In WebFlux.fn, path predicates can be shared through the `path` method on the router function builder.
369
+ For instance, the last few lines of the example above can be improved in the following way by using nested routes:
370
+
371
+ ====
372
+ [source,java,indent=0]
373
+ [subs="verbatim,quotes"]
374
+ ----
375
+ RouterFunction<ServerResponse> route = route()
376
+ .path("/person", builder -> builder
377
+ .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
378
+ .GET("", accept(APPLICATION_JSON), handler::listPeople)
379
+ .POST("/person", handler::createPerson))
380
+ .build();
381
+ ----
382
+ ====
383
+
384
+ Note that second parameter of `path` is a consumer that takes the a router builder.
385
+
386
+ Though path-based nesting is the most common, you can nest on any kind of predicate by using
387
+ the `nest` method on the builder.
388
+ The above still contains some duplication in the form of the shared `Accept`-header predicate.
389
+ We can further improve by using the `nest` method together with `accept`:
390
+
391
+ ====
392
+ [source,java,indent=0]
393
+ [subs="verbatim,quotes"]
394
+ ----
395
+ RouterFunction<ServerResponse> route = route()
396
+ .path("/person", b1 -> b1
397
+ .nest(accept(APPLICATION_JSON), b2 -> b2
398
+ .GET("/{id}", handler::getPerson)
399
+ .GET("", handler::listPeople))
400
+ .POST("/person", handler::createPerson))
401
+ .build();
402
+ ----
403
+ ====
325
404
326
405
327
406
[[webflux-fn-running]]
@@ -336,7 +415,7 @@ function to an `HttpHandler` by using one of the following:
336
415
You can then use the returned `HttpHandler` with a number of server adapters by following
337
416
<<web-reactive.adoc#webflux-httphandler,HttpHandler>> for server-specific instructions.
338
417
339
- A more advanced option is to run with a
418
+ A more typical option, also used by Spring Boot, is to run with a
340
419
<<web-reactive.adoc#webflux-dispatcher-handler,`DispatcherHandler`>>-based setup through the
341
420
<<web-reactive.adoc#webflux-config>>, which uses Spring configuration to declare the
342
421
components required to process requests. The WebFlux Java configuration declares the following
@@ -400,40 +479,74 @@ public class WebConfig implements WebFluxConfigurer {
400
479
401
480
402
481
[[webflux-fn-handler-filter-function]]
403
- == `HandlerFilterFunction`
482
+ == Filtering Handler Functions
404
483
405
- You can filter routes mapped by a router function by calling
406
- `RouterFunction.filter(HandlerFilterFunction)`, where `HandlerFilterFunction` is essentially a
407
- function that takes a `ServerRequest` and `HandlerFunction` and returns a `ServerResponse`.
408
- The handler function parameter represents the next element in the chain. This is typically the
409
- `HandlerFunction` that is routed to, but it can also be another `FilterFunction` if multiple filters
410
- are applied.
484
+ You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing
485
+ function builder.
411
486
With annotations, you can achieve similar functionality by using `@ControllerAdvice`, a `ServletFilter`, or both.
487
+ The filter will apply to all routes that are built by the builder.
488
+ This means that filters defined in nested routes do not apply to "top-level" routes.
489
+ For instance, consider the following example:
490
+
491
+ ====
492
+ [source,java,indent=0]
493
+ [subs="verbatim,quotes"]
494
+ ----
495
+ RouterFunction<ServerResponse> route = route()
496
+ .path("/person", b1 -> b1
497
+ .nest(accept(APPLICATION_JSON), b2 -> b2
498
+ .GET("/{id}", handler::getPerson)
499
+ .GET("", handler::listPeople)
500
+ .before(request -> ServerRequest.from(request) // <1>
501
+ .header("X-RequestHeader", "Value")
502
+ .build()))
503
+ .POST("/person", handler::createPerson))
504
+ .after((request, response) -> logResponse(response)) // <2>
505
+ .build();
506
+ ----
507
+ <1> The `before` filter that adds a custom request header is only applied to the two GET routes.
508
+ <2> The `after` filter that logs the response is applied to all routes, including the nested ones.
509
+ ====
510
+
511
+ The `filter` method on the router builder takes a `HandlerFilterFunction`: a
512
+ function that takes a `ServerRequest` and `HandlerFunction` and returns a `ServerResponse`.
513
+ The handler function parameter represents the next element in the chain.
514
+ This is typically the handler that is routed to, but it can also be another
515
+ filter if multiple are applied.
516
+
412
517
Now we can add a simple security filter to our route, assuming that we have a `SecurityManager` that
413
- can determine whether a particular path is allowed. The following example shows how to do so:
518
+ can determine whether a particular path is allowed.
519
+ The following example shows how to do so:
414
520
415
521
====
416
522
[source,java,indent=0]
417
523
[subs="verbatim,quotes"]
418
524
----
419
- import static org.springframework.http.HttpStatus.UNAUTHORIZED;
420
525
421
526
SecurityManager securityManager = ...
422
- RouterFunction<ServerResponse> route = ...
423
527
424
- RouterFunction<ServerResponse> filteredRoute =
425
- route.filter((request, next) -> {
528
+ RouterFunction<ServerResponse> route = route()
529
+ .path("/person", b1 -> b1
530
+ .nest(accept(APPLICATION_JSON), b2 -> b2
531
+ .GET("/{id}", handler::getPerson)
532
+ .GET("", handler::listPeople))
533
+ .POST("/person", handler::createPerson))
534
+ .filter((request, next) -> {
426
535
if (securityManager.allowAccessTo(request.path())) {
427
536
return next.handle(request);
428
537
}
429
538
else {
430
539
return ServerResponse.status(UNAUTHORIZED).build();
431
540
}
432
- });
541
+ })
542
+ .build();
433
543
----
434
544
====
435
545
436
- The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional. We
437
- allow only the handler function to be executed when access is allowed.
546
+ The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
547
+ We allow only the handler function to be executed when access is allowed.
548
+
549
+ Besides using the `filter` method on the router function builder, it is possible to apply a
550
+ filter to an existing router function via `RouterFunction.filter(HandlerFilterFunction)`.
438
551
439
552
NOTE: CORS support for functional endpoints is provided through a dedicated <<webflux-cors-webfilter,`CorsWebFilter`>>.
0 commit comments