Reactor-Netty と Kotlin coroutine を同時にいじっているブログ記事を見かけなかったので、勉強がてらブログを書いてみることにしました。

gradle
まずは、 build.gradle.kts はこんな感じ
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.jetbrains.kotlin.jvm").version("1.3.20") } repositories { jcenter() mavenCentral() } dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.1.1") implementation("io.projectreactor:reactor-core:3.2.6.RELEASE") implementation("io.projectreactor.netty:reactor-netty:0.8.5.RELEASE") testImplementation("org.junit.jupiter:junit-jupiter:5.4.0") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.3.21") testImplementation("io.projectreactor:reactor-test:3.2.6.RELEASE") } tasks.withType<Test> { useJUnitPlatform() } tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }
get リクエスト
実質のハローワールドです。なお、リクエスト先は https://httpbin.org を利用しました。
class Http { val httpClient = HttpClient.create() @Test fun get() { val path = GlobalScope.mono { // (1) "/get?query=" + URLEncoder.encode("テスト", StandardCharsets.UTF_8) } val body = httpClient.baseUrl("https://httpbin.org") .get() // (2) .uri(path) // (3) .responseContent() // (4) .aggregate() // (5) .asString(StandardCharsets.UTF_8) // (6) runBlocking { // (7) val res = body.awaitSingle() // (8) println(res) } } }
実行結果
{
"args": {
"query": "\u30c6\u30b9\u30c8"
},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "ReactorNetty/0.8.5.RELEASE"
},
"url": "https://httpbin.org/get?query=\u30c6\u30b9\u30c8"
}
Global.monoブロックの中では、Monoを透過的に扱えます。最後の式の戻り値がMonoに包まれて返ってきます(Kotlin coroutine)GETメソッドでリクエストを投げる場合にgetメソッドを呼び出しますuriメソッドでパスを指定します。StringまたはMono<String>を受け付けます- レスポンスのハンドリングを行いますが、
responseContentではヘッダーやステータスを無視して、ボディだけをByteBufのFluxで取れるようにします。なお戻り値の型はByteBufFluxというFlux<ByteBuf>のサブクラスです aggregateで複数のByteBufをまとめて一つのByteBufにまとめます。内部的にはCompositeByteBufを使っていますasStringでByteBufを文字列として扱います。この結果の戻り値はMono<String>ですrunBlockingブロックは coroutine と同じように透過的にMonoを扱いますが、現在のスレッドをブロックしますMono<String>の結果が返ってくるまで、処理を中断します(ここはrunBlockingブロックなので現在のスレッドもブロックしますが、GlobalScope.monoブロック内などの場合は、処理を一旦止めて他の処理をおこない、結果が返ってきたら処理を再開します)
というわけで、Reactor-Netty と Kotlin coroutine を同時に完全にマスターしました。
Kotlin coroutine の観点で言えば、 map/flatMap などが若干うるさくなくなったので、きれいに書けるといえばきれいに書けると言えそうですが、まあ、気持ち悪い感じではあります。
val fooMono: Mono<Foo> = operationFoo() val bazMono = fooMono.flatMap { foo -> operationBar(foo).flatMap { bar -> operationBaz(foo, bar) } }
↓
val bazMono = GlobalScope.mono { val foo = operationFoo().awaitSingle() val bar = operationBar(foo).awaitSingle() operationBaz(foo, bar).awaitSingle() }
Http Client の観点で言えば、フルーエントでモダンなAPIという感じです
post リクエスト
続いて post 投げてみます。
@Test fun post() { val objectMapper = ObjectMapper() val jsonMono = GlobalScope.mono { // (1) val example = Example(name = "test", id = 200) objectMapper.writeValueAsString(example) } val response = httpClient.baseUrl("https://httpbin.org") .headers { // (2) it["content-type"] = "application/json" it["X-USER-APP"] = "reactor-netty" it["user-agent"] = "reactor-netty-http-client" } .post() .uri("/post") .send(jsonMono.map { Unpooled.buffer().writeString(it) }) // (3) .responseSingle { response, body -> body.asString(StandardCharsets.UTF_8) // (4) .map { string -> response.status() to string } } .toMono() runBlocking { val res = response.awaitSingle() println(res.first) println(res.second) } }
実行結果
200 OK
{
"args": {},
"data": "{\"name\":\"test\",\"id\":200}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "32",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "reactor-netty-http-client",
"X-User-App": "reactor-netty"
},
"json": {
"id": 200,
"name": "test"
},
"url": "https://httpbin.org/post"
}
GlobalScope#monoブロックにてオブジェクトを json にエンコードします(結果はMono<String>)- ヘッダーを指定します。ヘッダーの値は
(Map<String,Object>) -> Unitにて指定します(正確には(HttpHeaders) -> Unit) sendにて body を指定します。 渡せる型はPublisher<out ByteBuf>/(HttpClientRequest,NettyOutBound) -> Publisher<Void>などですresponseSingleメソッドはByteBufFluxをすべて集約したByteBufMonoとHttpResponseを引数にとって何かしらのオブジェクトを返す関数でレスポンスを処理します
結論
- reactor-netty の HTTP クライアント
- わりと使いやすかった
- coroutine
- 完全にマスターした