@ -28,40 +28,74 @@ difference that router functions provide not just data, but also behavior.
@@ -28,40 +28,74 @@ difference that router functions provide not just data, but also behavior.
`RouterFunctions.route()` provides a router builder that facilitates the creation of routers,
public Mono<ServerResponse> getPerson(ServerRequest request) {
class PersonHandler(private val repository: PersonRepository) {
// ...
suspend fun listPeople(request: ServerRequest): ServerResponse {
// ...
}
suspend fun createPerson(request: ServerRequest): ServerResponse {
// ...
}
suspend fun getPerson(request: ServerRequest): ServerResponse {
// ...
}
}
}
----
<1> Create router using Coroutines router DSL, a Reactive alternative is also available via `router { }`.
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>>:
@ -95,50 +129,88 @@ while access to the body is provided through the `body` methods.
@@ -95,50 +129,88 @@ while access to the body is provided through the `body` methods.
The following example extracts the request body to a `Mono<String>`:
The following example shows how to access multiparts, one at a time, in streaming fashion:
[source,java]
[source,java,role="primary"]
.Java
----
Flux<Part> parts = request.body(BodyExtractors.toParts());
----
[source,kotlin,role="secondary"]
.Kotlin
----
val parts = request.body(BodyExtractors.toParts()).asFlow()
----
@ -150,28 +222,48 @@ a `build` method to create it. You can use the builder to set the response statu
@@ -150,28 +222,48 @@ a `build` method to create it. You can use the builder to set the response statu
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
----
That is convenient, but in an application we need multiple functions, and multiple inline
@ -193,12 +290,11 @@ Therefore, it is useful to group related handler functions together into a handl
@@ -193,12 +290,11 @@ Therefore, it is useful to group related handler functions together into a handl
has a similar role as `@Controller` in an annotation-based application.
For example, the following class exposes a reactive `Person` repository:
<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)` is a suspending function with no return type.
<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 return a 404 Not Found response.
[[webflux-fn-handler-validation]]
@ -246,34 +373,61 @@ A functional endpoint can use Spring's <<core.adoc#validation, validation facili
@@ -246,34 +373,61 @@ A functional endpoint can use Spring's <<core.adoc#validation, validation facili
apply validation to the request body. For example, given a custom Spring
<<core.adoc#validation, Validator>> implementation for a `Person`:
(GET("/hello-world") and accept(MediaType.TEXT_PLAIN)).invoke {
ServerResponse.ok().bodyAndAwait("Hello World")
}
}
----
You can compose multiple request predicates together by using:
@ -353,8 +516,8 @@ There are also other ways to compose multiple router functions together:
@@ -353,8 +516,8 @@ There are also other ways to compose multiple router functions together:
The following example shows the composition of four routes:
<1> `GET /person/{id}` with an `Accept` header that matches JSON is routed to
`PersonHandler.getPerson`
<2> `GET /person` with an `Accept` header that matches JSON is routed to
`PersonHandler.listPeople`
<3> `POST /person` with no additional predicates is mapped to
`PersonHandler.createPerson`, and
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
=== Nested Routes
@ -392,34 +578,58 @@ When using annotations, you would remove this duplication by using a type-level
@@ -392,34 +578,58 @@ When using annotations, you would remove this duplication by using a type-level
In WebFlux.fn, path predicates can be shared through the `path` method on the router function builder.
For instance, the last few lines of the example above can be improved in the following way by using nested routes:
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// configure message conversion...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure view resolution for HTML rendering...
}
}
}
----
@ -506,8 +748,8 @@ The filter will apply to all routes that are built by the builder.
@@ -506,8 +748,8 @@ The filter will apply to all routes that are built by the builder.
This means that filters defined in nested routes do not apply to "top-level" routes.
// TODO when https://github.com/spring-projects/spring-framework/issues/23526 will be fixed
----
The `filter` method on the router builder takes a `HandlerFilterFunction`: a
function that takes a `ServerRequest` and `HandlerFunction` and returns a `ServerResponse`.
@ -535,27 +783,31 @@ Now we can add a simple security filter to our route, assuming that we have a `S
@@ -535,27 +783,31 @@ Now we can add a simple security filter to our route, assuming that we have a `S
can determine whether a particular path is allowed.
The following example shows how to do so:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
@ -47,8 +47,8 @@ integration for using Spring WebFlux with FreeMarker templates.
@@ -47,8 +47,8 @@ integration for using Spring WebFlux with FreeMarker templates.
The following example shows how to configure FreeMarker as a view technology:
@ -56,7 +56,7 @@ The following example shows how to configure FreeMarker as a view technology:
@@ -56,7 +56,7 @@ The following example shows how to configure FreeMarker as a view technology:
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freemarker();
registry.freeMarker();
}
// Configure FreeMarker...
@ -69,6 +69,25 @@ The following example shows how to configure FreeMarker as a view technology:
@@ -69,6 +69,25 @@ The following example shows how to configure FreeMarker as a view technology:
Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer`,
shown in the preceding example. Given the preceding configuration, if your controller
@ -87,8 +106,8 @@ properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property
@@ -87,8 +106,8 @@ properties on the `FreeMarkerConfigurer` bean. The `freemarkerSettings` property
a `java.util.Properties` object, and the `freemarkerVariables` property requires a
`java.util.Map`. The following example shows how to use a `FreeMarkerConfigurer`:
@ -108,6 +127,22 @@ a `java.util.Properties` object, and the `freemarkerVariables` property requires
@@ -108,6 +127,22 @@ a `java.util.Properties` object, and the `freemarkerVariables` property requires
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("classpath:/templates")
setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
}
}
----
See the FreeMarker documentation for details of settings and variables as they apply to
the `Configuration` object.
@ -210,8 +245,8 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t
@@ -210,8 +245,8 @@ You can declare a `ScriptTemplateConfigurer` bean to specify the script engine t
the script files to load, what function to call to render templates, and so on.
The following example uses Mustache templates and the Nashorn JavaScript engine:
@ -233,6 +268,26 @@ The following example uses Mustache templates and the Nashorn JavaScript engine:
@@ -233,6 +268,26 @@ The following example uses Mustache templates and the Nashorn JavaScript engine:
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("mustache.js")
renderObject = "Mustache"
renderFunction = "render"
}
}
----
The `render` function is called with the following parameters:
@ -252,11 +307,11 @@ https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
@@ -252,11 +307,11 @@ https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
browser facilities not available in the server-side script engine.
The following example shows how to set a custom render function:
public class WebConfig implements WebFluxConfigurer {
@Override
@ -275,6 +330,26 @@ The following example shows how to set a custom render function:
@@ -275,6 +330,26 @@ The following example shows how to set a custom render function:
@ -296,8 +370,7 @@ This can be done on the script side, as well as any customization you need (mana
@@ -296,8 +370,7 @@ This can be done on the script side, as well as any customization you need (mana
template engine configuration for example).
The following example shows how compile a template:
@ -38,8 +38,8 @@ You can also use `WebClient.builder()` with further options:
@@ -38,8 +38,8 @@ You can also use `WebClient.builder()` with further options:
The following example configures <<web-reactive.adoc#webflux-codecs, HTTP codecs>>:
@ -51,12 +51,25 @@ The following example configures <<web-reactive.adoc#webflux-codecs, HTTP codecs
@@ -51,12 +51,25 @@ The following example configures <<web-reactive.adoc#webflux-codecs, HTTP codecs
@ -68,6 +81,19 @@ modified copy without affecting the original instance, as the following example
@@ -68,6 +81,19 @@ modified copy without affecting the original instance, as the following example
@ -76,8 +102,8 @@ modified copy without affecting the original instance, as the following example
@@ -76,8 +102,8 @@ modified copy without affecting the original instance, as the following example
To customize Reactor Netty settings, simple provide a pre-configured `HttpClient`:
@ -85,6 +111,15 @@ To customize Reactor Netty settings, simple provide a pre-configured `HttpClient
@@ -85,6 +111,15 @@ To customize Reactor Netty settings, simple provide a pre-configured `HttpClient
@ -102,26 +137,32 @@ application deployed as a WAR), you can declare a Spring-managed bean of type
@@ -102,26 +137,32 @@ application deployed as a WAR), you can declare a Spring-managed bean of type
Netty global resources are shut down when the Spring `ApplicationContext` is closed,
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setGlobalResources(false); <1>
factory.setUseGlobalResources(false); // <1>
return factory;
}
@ -133,9 +174,33 @@ instances use shared resources, as the following example shows:
@@ -133,9 +174,33 @@ instances use shared resources, as the following example shows:
};
ClientHttpConnector connector =
new ReactorClientHttpConnector(resourceFactory(), mapper); <2>
new ReactorClientHttpConnector(resourceFactory(), mapper); // <2>
@ -148,29 +213,50 @@ instances use shared resources, as the following example shows:
@@ -148,29 +213,50 @@ instances use shared resources, as the following example shows:
@ -189,6 +275,15 @@ The following example shows how to customize Jetty `HttpClient` settings:
@@ -189,6 +275,15 @@ The following example shows how to customize Jetty `HttpClient` settings:
val connector = JettyClientHttpConnector(httpClient)
val webClient = WebClient.builder().clientConnector(connector).build();
----
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
which remain active until the process exits or `stop()` is called.
@ -198,8 +293,8 @@ ensure that the resources are shut down when the Spring `ApplicationContext` is
@@ -198,8 +293,8 @@ ensure that the resources are shut down when the Spring `ApplicationContext` is
declaring a Spring-managed bean of type `JettyResourceFactory`, as the following example
By default, responses with 4xx or 5xx status codes result in an
`WebClientResponseException` or one of its HTTP status specific sub-classes, such as
@ -259,8 +389,8 @@ By default, responses with 4xx or 5xx status codes result in an
@@ -259,8 +389,8 @@ By default, responses with 4xx or 5xx status codes result in an
You can also use the `onStatus` method to customize the resulting exception,
@ -348,22 +516,46 @@ You can also have a stream of objects be encoded, as the following example shows
@@ -348,22 +516,46 @@ You can also have a stream of objects be encoded, as the following example shows
@ -374,22 +566,33 @@ To send form data, you can provide a `MultiValueMap<String, String>` as the body
@@ -374,22 +566,33 @@ To send form data, you can provide a `MultiValueMap<String, String>` as the body
content is automatically set to `application/x-www-form-urlencoded` by the
`FormHttpMessageWriter`. The following example shows how to use `MultiValueMap<String, String>`:
@ -399,6 +602,17 @@ You can also supply form data in-line by using `BodyInserters`, as the following
@@ -399,6 +602,17 @@ You can also supply form data in-line by using `BodyInserters`, as the following
@ -410,8 +624,8 @@ either `Object` instances that represent part content or `HttpEntity` instances
@@ -410,8 +624,8 @@ either `Object` instances that represent part content or `HttpEntity` instances
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
multipart request. The following example shows how to create a `MultiValueMap<String, ?>`:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
@ -421,6 +635,18 @@ multipart request. The following example shows how to create a `MultiValueMap<St
@@ -421,6 +635,18 @@ multipart request. The following example shows how to create a `MultiValueMap<St
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
@ -442,6 +668,17 @@ through the `body` method, as the following example shows:
@@ -442,6 +668,17 @@ through the `body` method, as the following example shows:
If the `MultiValueMap` contains at least one non-`String` value, which could also
represent regular form data (that is, `application/x-www-form-urlencoded`), you need not
@ -451,8 +688,8 @@ set the `Content-Type` to `multipart/form-data`. This is always the case when us
@@ -451,8 +688,8 @@ set the `Content-Type` to `multipart/form-data`. This is always the case when us
As an alternative to `MultipartBodyBuilder`, you can also provide multipart content,
inline-style, through the built-in `BodyInserters`, as the following example shows:
@ -462,7 +699,17 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
@@ -462,7 +699,17 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
@ -472,74 +719,115 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
@@ -472,74 +719,115 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
in order to intercept and modify requests, as the following example shows:
@ -64,6 +77,24 @@ Then you can map it to a URL and add a `WebSocketHandlerAdapter`, as the followi
@@ -64,6 +77,24 @@ Then you can map it to a URL and add a `WebSocketHandlerAdapter`, as the followi
override fun handle(session: WebSocketSession): Mono<Void> {
return session.receive() // <1>
.doOnNext {
// ... // <2>
}
.concatMap {
// ... // <3>
}
.then() // <4>
}
}
}
----
<1> Access the stream of inbound messages.
<2> Do something with each message.
@ -135,26 +188,50 @@ released before you have had a chance to read the data. For more background, see
@@ -135,26 +188,50 @@ released before you have had a chance to read the data. For more background, see
The following implementation combines the inbound and outbound streams:
override fun handle(session: WebSocketSession): Mono<Void> {
val input = session.receive() // <1>
.doOnNext {
// ...
}
.concatMap {
// ...
}
.then()
val source: Flux<String> = ...
val output = session.send(source.map(session::textMessage)) // <2>
return Mono.zip(input, output).then() // <3>
}
}
}
----
<1> Handle inbound message stream.
<2> Send outgoing messages.
@ -233,11 +337,11 @@ The `RequestUpgradeStrategy` for each server exposes WebSocket-related configura
@@ -233,11 +337,11 @@ The `RequestUpgradeStrategy` for each server exposes WebSocket-related configura
options available for the underlying WebSocket engine. The following example sets