Add Coroutines support for WebClient and WebFlux.fn

This commit is the first part of a more complete Coroutines
support coming in Spring Framework 5.2. It introduces suspendable
Kotlin extensions for Mono based methods in WebFlux classes like
WebClient, ServerRequest, ServerResponse as well as a Coroutines
router usable via `coRouter { }`.

Coroutines extensions use `await` prefix or `AndAwait` suffix,
and most are using names close to their Reactive counterparts,
except `exchange` in `WebClient.RequestHeadersSpec`
which translates to `awaitResponse`.

Upcoming expected changes are:
 - Leverage `Dispatchers.Unconfined` (Kotlin/kotlinx.coroutines#972)
 - Expose extensions for `Flux` based API (Kotlin/kotlinx.coroutines#254)
 - Introduce interop with `CoroutineContext` (Kotlin/kotlinx.coroutines#284)
 - Support Coroutines in `ReactiveAdapterRegistry`
 - Support Coroutines for WebFlux annotated controllers
 - Fix return type of Kotlin suspending functions (gh-21058)

See gh-19975
This commit is contained in:
Sebastien Deleuze
2019-02-18 09:11:12 +01:00
parent 04bb114f05
commit 19f792db66
16 changed files with 1182 additions and 23 deletions

View File

@@ -20,10 +20,10 @@ https://stackoverflow.com/questions/tagged/spring+kotlin[Stackoverflow] if you n
[[kotlin-requirements]]
== Requirements
The Spring Framework supports Kotlin 1.1+ and requires
The Spring Framework supports Kotlin 1.3+ and requires
https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib[`kotlin-stdlib`]
(or one of its variants, such as https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib-jre8[`kotlin-stdlib-jre8`]
for Kotlin 1.1 or https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib-jdk8[`kotlin-stdlib-jdk8`] for Kotlin 1.2+)
(or one of its variants, such as
https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib-jdk8[`kotlin-stdlib-jdk8`])
and https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-reflect[`kotlin-reflect`]
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
https://start.spring.io/#!language=kotlin[start.spring.io].
@@ -255,17 +255,19 @@ for more details and up-to-date information.
== Web
=== WebFlux router DSL
=== WebFlux Functional DSL
Spring Framework comes with a Kotlin router DSL available in 2 flavors:
Spring Framework now comes with a
{doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/-router-function-dsl/[Kotlin routing DSL]
that lets you use the <<web-reactive#webflux-fn,WebFlux functional API>> to write clean and idiomatic Kotlin code,
as the following example shows:
- Reactive with {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/-router-function-dsl/[router { }]
- <<Coroutines>>
These DSL let you use the <<web-reactive#webflux-fn,WebFlux functional API>> to write clean and idiomatic Kotlin code
to build a `RouterFunction` instance as the following example shows:
[source,kotlin,indent=0]
----
router {
val routes: RouterFunction<ServerResponse> = router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
@@ -290,7 +292,38 @@ depending on dynamic data (for example, from a database).
See https://github.com/mixitconf/mixit/tree/dafd5ccc92dfab6d9c306fcb60b28921a1ccbf79/src/main/kotlin/mixit/web/routes[MiXiT project routes]
for a concrete example.
=== Coroutines
As of Spring Framework 5.2, https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines support]
is provided via extensions for WebFlux client and server functional API. A dedicated
{doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/-co-router-function-dsl/[`coRouter { }`]
router DSL is also available.
Coroutines extensions use `await` prefix or `AndAwait` suffix, and most are using similar name than their Reactive
counterparts except `exchange` in `WebClient.RequestHeadersSpec` which translates to `awaitResponse`.
[source,kotlin,indent=0]
----
fun routes(userHandler: UserHandler): RouterFunction<ServerResponse> = coRouter {
GET("/", userHandler::listView)
GET("/api/user", userHandler::listApi)
}
class UserHandler(private val client: WebClient) {
suspend fun listApi(request: ServerRequest): ServerResponse =
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait(
client.get().uri("...").awaitResponse().awaitBody<User>())
suspend fun listView(request: ServerRequest): ServerResponse =
ServerResponse.ok().renderAndAwait("users", mapOf("users" to
client.get().uri("...").awaitResponse().awaitBody<User>()))
}
----
Read this https://medium.com/@elizarov/structured-concurrency-722d765aa952[structured concurrency blog post]
to understand how to run code concurrently with Coroutines.
=== Kotlin Script Templates