Skip to content

カスタムクライアントプラグイン

Ktor v2.2.0以降、カスタムクライアントプラグインを作成するための新しいAPIが提供されています。一般的に、このAPIはパイプラインやフェーズなどの内部的なKtorの概念を理解する必要はありません。 代わりに、onRequestonResponseなどの一連のハンドラーを使用して、リクエストとレスポンスの処理の異なるステージにアクセスできます。

最初のプラグインを作成してインストールする

このセクションでは、各リクエストにカスタムヘッダーを追加する最初のプラグインを作成してインストールする方法を説明します。

  1. プラグインを作成するには、createClientPlugin関数を呼び出し、プラグイン名を引数として渡します。

    kotlin
    package com.example.plugins
    
    import io.ktor.client.plugins.api.*
    
    val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") {
        // Configure the plugin ...
    }

    この関数は、プラグインをインストールするために使用されるClientPluginインスタンスを返します。

  2. 各リクエストにカスタムヘッダーを追加するには、リクエストパラメータへのアクセスを提供するonRequestハンドラーを使用できます。

    kotlin
    package com.example.plugins
    
    import io.ktor.client.plugins.api.*
    
    val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") {
        onRequest { request, _ ->
            request.headers.append("X-Custom-Header", "Default value")
        }
    }
  3. プラグインをインストールするには、作成したClientPluginインスタンスをクライアントの設定ブロック内のinstall関数に渡します。

    kotlin
    import com.example.plugins.*
    
    val client = HttpClient(CIO) {
        install(CustomHeaderPlugin)
    }

完全な例はこちらで見つけることができます: CustomHeader.kt。 以下のセクションでは、プラグインの設定を提供し、リクエストとレスポンスを処理する方法を見ていきます。

プラグインの設定を提供する

前のセクションでは、事前に定義されたカスタムヘッダーを各レスポンスに追加するプラグインを作成する方法を示しました。このプラグインをより便利にし、任意のカスタムヘッダー名と値を渡すための設定を提供してみましょう。

  1. まず、設定クラスを定義する必要があります。

    kotlin
    class CustomHeaderPluginConfig {
        var headerName: String = "X-Custom-Header"
        var headerValue: String = "Default value"
    }
  2. この設定をプラグインで使用するには、設定クラスの参照をcreateApplicationPluginに渡します。

    kotlin
    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)
        }
    }

    プラグインの設定フィールドは可変であるため、それらをローカル変数に保存することをお勧めします。

  3. 最後に、次のようにプラグインをインストールして設定できます。

    kotlin
    val client = HttpClient(CIO) {
        install(CustomHeaderConfigurablePlugin) {
            headerName = "X-Custom-Header"
            headerValue = "Hello, world!"
        }
    }

完全な例はこちらで見つけることができます: CustomHeaderConfigurable.kt

リクエストとレスポンスを処理する

カスタムプラグインは、一連の専用ハンドラーを使用して、リクエストとレスポンスを処理する異なるステージへのアクセスを提供します。例えば、次のものがあります。

  • onRequestonResponseは、それぞれリクエストとレスポンスを処理することを可能にします。
  • transformRequestBodytransformResponseBodyは、リクエストとレスポンスのボディに必要な変換を適用するために使用できます。

また、on(...)ハンドラーもあり、呼び出しの他のステージを処理するのに役立つ特定のフックを呼び出すことができます。 以下の表は、すべてのハンドラーが実行される順序でリストされています。

ハンドラー 説明
onRequest

このハンドラーは、各HTTP

リクエスト
リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。
に対して実行され、それを変更することができます。

例: カスタムヘッダー

transformRequestBody

リクエストボディを変換することを可能にします。 このハンドラーでは、ボディを OutgoingContent (例えば、TextContentByteArrayContent、またはFormDataContent)にシリアライズするか、 変換が適用できない場合はnullを返す必要があります。

例: データ変換

onResponse

このハンドラーは、各受信HTTP

レスポンス
リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。
に対して実行され、 レスポンスを様々な方法で検査することができます: レスポンスのログ記録、クッキーの保存など。

例: ヘッダーのログ記録レスポンス時間

transformResponseBody

レスポンスボディを変換することを可能にします。 このハンドラーは、各HttpResponse.body呼び出しに対して呼び出されます。 ボディをrequestedTypeのインスタンスにデシリアライズするか、 変換が適用できない場合はnullを返す必要があります。

例: データ変換

onClose このプラグインによって割り当てられたリソースをクリーンアップすることを可能にします。 このハンドラーは、クライアントが閉じられたときに呼び出されます。
ハンドラー 説明
on(SetupRequest)SetupRequestフックは、リクエスト処理で最初に実行されます。
onRequest

このハンドラーは、各HTTP

リクエスト
リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。
に対して実行され、それを変更することができます。

例: カスタムヘッダー

transformRequestBody

リクエストボディを変換することを可能にします。 このハンドラーでは、ボディを OutgoingContent (例えば、TextContentByteArrayContent、またはFormDataContent)にシリアライズするか、 変換が適用できない場合はnullを返す必要があります。

例: データ変換

on(Send)

Sendフックは、レスポンスを検査し、必要に応じて追加のリクエストを開始する機能を提供します。 これは、リダイレクトの処理、リクエストの再試行、認証などに役立ちます。

例: 認証

on(SendingRequest)

SendingRequestフックは、ユーザーによって開始されたものでないリクエストであっても、すべてのリクエストに対して実行されます。 例えば、リクエストがリダイレクトされた場合、onRequestハンドラーは元のリクエストに対してのみ実行されますが、on(SendingRequest)は元のリクエストとリダイレクトされたリクエストの両方に対して実行されます。 同様に、追加のリクエストを開始するためにon(Send)を使用した場合は、ハンドラーは次のように順序付けされます。

Console

例: ヘッダーのログ記録レスポンス時間

onResponse

このハンドラーは、各受信HTTP

レスポンス
リクエストのURL、HTTPメソッド、ヘッダー、ボディなど、さまざまなリクエストパラメータを作成および指定する方法を学びます。
に対して実行され、 レスポンスを様々な方法で検査することができます: レスポンスのログ記録、クッキーの保存など。

例: ヘッダーのログ記録レスポンス時間

transformResponseBody

レスポンスボディを変換することを可能にします。 このハンドラーは、各HttpResponse.body呼び出しに対して呼び出されます。 ボディをrequestedTypeのインスタンスにデシリアライズするか、 変換が適用できない場合はnullを返す必要があります。

例: データ変換

onClose このプラグインによって割り当てられたリソースをクリーンアップすることを可能にします。 このハンドラーは、クライアントが閉じられたときに呼び出されます。

呼び出しの状態を共有する

カスタムプラグインは、呼び出しに関連する任意の値を共有できるため、この呼び出しを処理する任意のハンドラー内でこの値にアクセスできます。 この値は、call.attributesコレクション内に一意のキーを持つ属性として保存されます。 以下の例は、リクエストの送信からレスポンスの受信までの時間を計算するために属性を使用する方法を示しています。

kotlin
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インスタンスを返します。 以下の例は、クライアントが使用するプロキシアドレスを取得する方法を示しています。

kotlin
import io.ktor.client.plugins.api.*

val SimplePlugin = createClientPlugin("SimplePlugin") {
    val proxyAddress = client.engineConfig.proxy?.address()
    println("Proxy address: $proxyAddress")
}

以下のコードサンプルは、カスタムプラグインのいくつかの例を示しています。 結果のプロジェクトはこちらで見つけることができます: client-custom-plugin

カスタムヘッダー

各リクエストにカスタムヘッダーを追加するプラグインを作成する方法を示します。

kotlin
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"
}

ヘッダーのログ記録

リクエストヘッダーとレスポンスヘッダーをログに記録するプラグインを作成する方法を示します。

kotlin
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")
}

レスポンス時間

リクエストの送信からレスポンスの受信までの時間を測定するプラグインを作成する方法を示します。

kotlin
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}")
    }
}

データ変換

transformRequestBodytransformResponseBodyフックを使用して、リクエストとレスポンスのボディを変換する方法を示します。

kotlin
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
        }
    }
}
kotlin
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}")
    }
}
kotlin
package com.example.model

data class User(val name: String, val age: Int)

完全な例はこちらで見つけることができます: client-custom-plugin-data-transformation

認証

サーバーから認証されていないレスポンスが返された場合に、on(Send)フックを使用してAuthorizationヘッダーにベアラートークンを追加する方法を示すサンプルKtorプロジェクトです。

kotlin
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 = ""
}
kotlin
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