カスタムクライアントプラグイン
コード例: client-custom-plugin
Ktor v2.2.0以降、カスタムクライアントプラグインを作成するための新しいAPIが提供されています。一般的に、このAPIはパイプラインやフェーズなどの内部的なKtorの概念を理解する必要はありません。 代わりに、onRequest
、onResponse
などの一連のハンドラーを使用して、リクエストとレスポンスの処理の異なるステージにアクセスできます。
最初のプラグインを作成してインストールする
このセクションでは、各リクエストにカスタムヘッダーを追加する最初のプラグインを作成してインストールする方法を説明します。
プラグインを作成するには、createClientPlugin関数を呼び出し、プラグイン名を引数として渡します。
kotlinpackage com.example.plugins import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { // Configure the plugin ... }
この関数は、プラグインをインストールするために使用される
ClientPlugin
インスタンスを返します。各リクエストにカスタムヘッダーを追加するには、リクエストパラメータへのアクセスを提供する
onRequest
ハンドラーを使用できます。kotlinpackage com.example.plugins import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { onRequest { request, _ -> request.headers.append("X-Custom-Header", "Default value") } }
プラグインをインストールするには、作成した
ClientPlugin
インスタンスをクライアントの設定ブロック内のinstall
関数に渡します。kotlinimport com.example.plugins.* val client = HttpClient(CIO) { install(CustomHeaderPlugin) }
完全な例はこちらで見つけることができます: CustomHeader.kt。 以下のセクションでは、プラグインの設定を提供し、リクエストとレスポンスを処理する方法を見ていきます。
プラグインの設定を提供する
前のセクションでは、事前に定義されたカスタムヘッダーを各レスポンスに追加するプラグインを作成する方法を示しました。このプラグインをより便利にし、任意のカスタムヘッダー名と値を渡すための設定を提供してみましょう。
まず、設定クラスを定義する必要があります。
kotlinclass CustomHeaderPluginConfig { var headerName: String = "X-Custom-Header" var headerValue: String = "Default value" }
この設定をプラグインで使用するには、設定クラスの参照を
createApplicationPlugin
に渡します。kotlinimport io.ktor.client.plugins.api.* val CustomHeaderConfigurablePlugin = createClientPlugin("CustomHeaderConfigurablePlugin", ::CustomHeaderPluginConfig) { val headerName = pluginConfig.headerName val headerValue = pluginConfig.headerValue onRequest { request, _ -> request.headers.append(headerName, headerValue) } }
プラグインの設定フィールドは可変であるため、それらをローカル変数に保存することをお勧めします。
最後に、次のようにプラグインをインストールして設定できます。
kotlinval client = HttpClient(CIO) { install(CustomHeaderConfigurablePlugin) { headerName = "X-Custom-Header" headerValue = "Hello, world!" } }
完全な例はこちらで見つけることができます: CustomHeaderConfigurable.kt。
リクエストとレスポンスを処理する
カスタムプラグインは、一連の専用ハンドラーを使用して、リクエストとレスポンスを処理する異なるステージへのアクセスを提供します。例えば、次のものがあります。
onRequest
とonResponse
は、それぞれリクエストとレスポンスを処理することを可能にします。transformRequestBody
とtransformResponseBody
は、リクエストとレスポンスのボディに必要な変換を適用するために使用できます。
また、on(...)
ハンドラーもあり、呼び出しの他のステージを処理するのに役立つ特定のフックを呼び出すことができます。 以下の表は、すべてのハンドラーが実行される順序でリストされています。
ハンドラー | 説明 |
onRequest | このハンドラーは、各HTTP リクエスト に対して実行され、それを変更することができます。 リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。 例: カスタムヘッダー |
transformRequestBody | リクエストボディを変換することを可能にします。 このハンドラーでは、ボディを OutgoingContent (例えば、 例: データ変換 |
onResponse | このハンドラーは、各受信HTTP レスポンス に対して実行され、 レスポンスを様々な方法で検査することができます: レスポンスのログ記録、クッキーの保存など。 リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。 |
transformResponseBody | レスポンスボディを変換することを可能にします。 このハンドラーは、各 例: データ変換 |
onClose | このプラグインによって割り当てられたリソースをクリーンアップすることを可能にします。 このハンドラーは、クライアントが閉じられたときに呼び出されます。 |
ハンドラー | 説明 |
on(SetupRequest) | SetupRequest フックは、リクエスト処理で最初に実行されます。 |
onRequest | このハンドラーは、各HTTP リクエスト に対して実行され、それを変更することができます。 リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。 例: カスタムヘッダー |
transformRequestBody | リクエストボディを変換することを可能にします。 このハンドラーでは、ボディを OutgoingContent (例えば、 例: データ変換 |
on(Send) |
例: 認証 |
on(SendingRequest) |
Console |
onResponse | このハンドラーは、各受信HTTP レスポンス に対して実行され、 レスポンスを様々な方法で検査することができます: レスポンスのログ記録、クッキーの保存など。 リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。 |
transformResponseBody | レスポンスボディを変換することを可能にします。 このハンドラーは、各 例: データ変換 |
onClose | このプラグインによって割り当てられたリソースをクリーンアップすることを可能にします。 このハンドラーは、クライアントが閉じられたときに呼び出されます。 |
呼び出しの状態を共有する
カスタムプラグインは、呼び出しに関連する任意の値を共有できるため、この呼び出しを処理する任意のハンドラー内でこの値にアクセスできます。 この値は、call.attributes
コレクション内に一意のキーを持つ属性として保存されます。 以下の例は、リクエストの送信からレスポンスの受信までの時間を計算するために属性を使用する方法を示しています。
import io.ktor.client.plugins.api.*
import io.ktor.util.*
val ResponseTimePlugin = createClientPlugin("ResponseTimePlugin") {
val onCallTimeKey = AttributeKey<Long>("onCallTimeKey")
on(SendingRequest) { request, content ->
val onCallTime = System.currentTimeMillis()
request.attributes.put(onCallTimeKey, onCallTime)
}
onResponse { response ->
val onCallTime = response.call.attributes[onCallTimeKey]
val onCallReceiveTime = System.currentTimeMillis()
println("Read response delay (ms): ${onCallReceiveTime - onCallTime}")
}
}
完全な例はこちらで見つけることができます: ResponseTime.kt。
クライアント設定にアクセスする
client
プロパティを使用してクライアント設定にアクセスできます。これはHttpClientインスタンスを返します。 以下の例は、クライアントが使用するプロキシアドレスを取得する方法を示しています。
import io.ktor.client.plugins.api.*
val SimplePlugin = createClientPlugin("SimplePlugin") {
val proxyAddress = client.engineConfig.proxy?.address()
println("Proxy address: $proxyAddress")
}
例
以下のコードサンプルは、カスタムプラグインのいくつかの例を示しています。 結果のプロジェクトはこちらで見つけることができます: client-custom-plugin。
カスタムヘッダー
各リクエストにカスタムヘッダーを追加するプラグインを作成する方法を示します。
package com.example.plugins
import io.ktor.client.plugins.api.*
val CustomHeaderConfigurablePlugin = createClientPlugin("CustomHeaderConfigurablePlugin", ::CustomHeaderPluginConfig) {
val headerName = pluginConfig.headerName
val headerValue = pluginConfig.headerValue
onRequest { request, _ ->
request.headers.append(headerName, headerValue)
}
}
class CustomHeaderPluginConfig {
var headerName: String = "X-Custom-Header"
var headerValue: String = "Default value"
}
ヘッダーのログ記録
リクエストヘッダーとレスポンスヘッダーをログに記録するプラグインを作成する方法を示します。
package com.example.plugins
import io.ktor.client.plugins.api.*
val LoggingHeadersPlugin = createClientPlugin("LoggingHeadersPlugin") {
on(SendingRequest) { request, content ->
println("Request headers:")
request.headers.entries().forEach { entry ->
printHeader(entry)
}
}
onResponse { response ->
println("Response headers:")
response.headers.entries().forEach { entry ->
printHeader(entry)
}
}
}
private fun printHeader(entry: Map.Entry<String, List<String>>) {
var headerString = entry.key + ": "
entry.value.forEach { headerValue ->
headerString += "${headerValue};"
}
println("-> $headerString")
}
レスポンス時間
リクエストの送信からレスポンスの受信までの時間を測定するプラグインを作成する方法を示します。
package com.example.plugins
import io.ktor.client.plugins.api.*
import io.ktor.util.*
val ResponseTimePlugin = createClientPlugin("ResponseTimePlugin") {
val onCallTimeKey = AttributeKey<Long>("onCallTimeKey")
on(SendingRequest) { request, content ->
val onCallTime = System.currentTimeMillis()
request.attributes.put(onCallTimeKey, onCallTime)
}
onResponse { response ->
val onCallTime = response.call.attributes[onCallTimeKey]
val onCallReceiveTime = System.currentTimeMillis()
println("Read response delay (ms): ${onCallReceiveTime - onCallTime}")
}
}
データ変換
transformRequestBody
とtransformResponseBody
フックを使用して、リクエストとレスポンスのボディを変換する方法を示します。
package com.example.plugins
import com.example.model.*
import io.ktor.client.plugins.api.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.utils.io.*
val DataTransformationPlugin = createClientPlugin("DataTransformationPlugin") {
transformRequestBody { request, content, bodyType ->
if (bodyType?.type == User::class) {
val user = content as User
TextContent(text="${user.name};${user.age}", contentType = ContentType.Text.Plain)
} else {
null
}
}
transformResponseBody { response, content, requestedType ->
if (requestedType.type == User::class) {
val receivedContent = content.readUTF8Line()!!.split(";")
User(receivedContent[0], receivedContent[1].toInt())
} else {
content
}
}
}
package com.example
import com.example.model.*
import com.example.plugins.*
import com.example.server.*
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
fun main() {
startServer()
runBlocking {
val client = HttpClient(CIO) {
install(DataTransformationPlugin)
}
val bodyAsText = client.post("http://0.0.0.0:8080/post-data") {
setBody(User("John", 42))
}.bodyAsText()
val user = client.get("http://0.0.0.0:8080/get-data").body<User>()
println("Userinfo: $bodyAsText")
println("Username: ${user.name}, age: ${user.age}")
}
}
package com.example.model
data class User(val name: String, val age: Int)
完全な例はこちらで見つけることができます: client-custom-plugin-data-transformation。
認証
サーバーから認証されていないレスポンスが返された場合に、on(Send)
フックを使用してAuthorization
ヘッダーにベアラートークンを追加する方法を示すサンプルKtorプロジェクトです。
package com.example.plugins
import io.ktor.client.plugins.api.*
import io.ktor.http.*
val AuthPlugin = createClientPlugin("AuthPlugin", ::AuthPluginConfig) {
val token = pluginConfig.token
on(Send) { request ->
val originalCall = proceed(request)
originalCall.response.run { // this: HttpResponse
if(status == HttpStatusCode.Unauthorized && headers["WWW-Authenticate"]!!.contains("Bearer")) {
request.headers.append("Authorization", "Bearer $token")
proceed(request)
} else {
originalCall
}
}
}
}
class AuthPluginConfig {
var token: String = ""
}
package com.example
import com.example.plugins.*
import com.example.server.*
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
fun main() {
startServer()
runBlocking {
val client = HttpClient(CIO) {
install(AuthPlugin) {
token = "abc123"
}
}
val response = client.get("http://0.0.0.0:8080/")
println(response.bodyAsText())
}
}
完全な例はこちらで見つけることができます: client-custom-plugin-auth。