カスタムプラグイン - Base API
コード例: custom-plugin-base-api
v2.0.0 以降、Ktor はカスタムプラグインを作成するための新しい簡素化された API を提供しています。
Ktor は、共通の機能を実装し、複数のアプリケーションで再利用可能なカスタムプラグインを開発するための API を公開しています。 この API を使用すると、さまざまなパイプラインフェーズをインターセプトして、リクエスト/レスポンス処理にカスタムロジックを追加できます。 例えば、Monitoring フェーズをインターセプトして、受信リクエストをログに記録したり、メトリクスを収集したりできます。
プラグインを作成する
カスタムプラグインを作成するには、以下の手順に従います。
- プラグインクラスを作成し、以下のいずれかのインターフェースを実装するコンパニオンオブジェクトを宣言します。
- プラグインをアプリケーションレベルで動作させる場合は BaseApplicationPlugin。
- プラグインを特定のルートにインストールできるようにする場合は BaseRouteScopedPlugin。
- このコンパニオンオブジェクトの
keyとinstallメンバを実装します。 - プラグインのコンフィギュレーションを提供します。
- 必要なパイプラインフェーズをインターセプトしてコールを処理します。
- プラグインをインストールします。
コンパニオンオブジェクトを作成する
カスタムプラグインのクラスには、BaseApplicationPlugin または BaseRouteScopedPlugin インターフェースを実装するコンパニオンオブジェクトが必要です。 BaseApplicationPlugin インターフェースは、次の 3 つの型パラメータを受け取ります:
- このプラグインが互換性を持つパイプラインの型。
- このプラグインのコンフィギュレーションオブジェクトの型。
- プラグインオブジェクト自体の型。
class CustomHeader() {
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
// ...
}
}'key' と 'install' メンバを実装する
BaseApplicationPlugin インターフェースを実装するコンパニオンオブジェクトは、2 つのメンバを実装する必要があります。
keyプロパティは、プラグインを識別するために使用されます。Ktor はすべての属性のマップを保持しており、各プラグインは指定されたキーを使用して自身をこのマップに追加します。install関数を使用すると、プラグインの動作を設定できます。ここではパイプラインをインターセプトし、プラグインのインスタンスを返す必要があります。パイプラインをインターセプトしてコールを処理する方法については、次の章で詳しく説明します。
class CustomHeader() {
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
override val key = AttributeKey<CustomHeader>("CustomHeader")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): CustomHeader {
val plugin = CustomHeader()
// パイプラインをインターセプトする ...
return plugin
}
}
}コールを処理する
カスタムプラグインでは、既存のパイプラインフェーズまたは新しく定義されたフェーズをインターセプトすることで、リクエストとレスポンスを処理できます。例えば、認証 (Authentication) プラグインは、デフォルトのパイプラインに Authenticate と Challenge というカスタムフェーズを追加します。このように、特定のパイプラインをインターセプトすることで、コールのさまざまなステージにアクセスできます。例:
ApplicationCallPipeline.Monitoring: このフェーズのインターセプトは、リクエストのログ記録やメトリクスの収集に使用できます。ApplicationCallPipeline.Plugins: レスポンスパラメータの変更(カスタムヘッダーの追加など)に使用できます。ApplicationReceivePipeline.TransformおよびApplicationSendPipeline.Transform: クライアントから受信したデータの取得や変換、送信前のデータ変換を行うことができます。
以下の例は、ApplicationCallPipeline.Plugins フェーズをインターセプトし、各レスポンスにカスタムヘッダーを追加する方法を示しています。
class CustomHeader() {
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
override val key = AttributeKey<CustomHeader>("CustomHeader")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): CustomHeader {
val plugin = CustomHeader()
pipeline.intercept(ApplicationCallPipeline.Plugins) {
call.response.header("X-Custom-Header", "Hello, world!")
}
return plugin
}
}
}このプラグインでは、カスタムヘッダーの名前と値がハードコードされていることに注意してください。プラグインにコンフィギュレーションを提供し、必要なカスタムヘッダー名と値を渡せるようにすることで、より柔軟にすることができます。
カスタムプラグインを使用すると、コールに関連する任意の値を共有できるため、そのコールを処理するハンドラー内からその値にアクセスできます。詳細は コールの状態を共有する を参照してください。
プラグインのコンフィギュレーションを提供する
前の章では、定義済みのカスタムヘッダーを各レスポンスに追加するプラグインの作成方法を示しました。このプラグインをより便利にするために、必要なカスタムヘッダー名と値を渡すためのコンフィギュレーションを提供しましょう。まず、プラグインのクラス内にコンフィギュレーションクラスを定義する必要があります。
class Configuration {
var headerName = "Custom-Header-Name"
var headerValue = "Default value"
}プラグインのコンフィギュレーションフィールドは可変(mutable)であるため、ローカル変数に保存することをお勧めします。
class CustomHeader(configuration: Configuration) {
private val name = configuration.headerName
private val value = configuration.headerValue
class Configuration {
var headerName = "Custom-Header-Name"
var headerValue = "Default value"
}
}最後に、install 関数内でこのコンフィギュレーションを取得し、そのプロパティを使用できます。
class CustomHeader(configuration: Configuration) {
private val name = configuration.headerName
private val value = configuration.headerValue
class Configuration {
var headerName = "Custom-Header-Name"
var headerValue = "Default value"
}
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
override val key = AttributeKey<CustomHeader>("CustomHeader")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): CustomHeader {
val configuration = Configuration().apply(configure)
val plugin = CustomHeader(configuration)
pipeline.intercept(ApplicationCallPipeline.Plugins) {
call.response.header(plugin.name, plugin.value)
}
return plugin
}
}
}プラグインをインストールする
カスタムプラグインをアプリケーションにインストールするには、install 関数を呼び出し、必要なコンフィギュレーションパラメータを渡します。
install(CustomHeader) {
headerName = "X-Custom-Header"
headerValue = "Hello, world!"
}例
以下のコードスニペットは、カスタムプラグインのいくつかの例を示しています。 実行可能なプロジェクトはこちらにあります: custom-plugin-base-api
リクエストのログ記録
以下の例は、受信リクエストをログに記録するためのカスタムプラグインを作成する方法を示しています。
package com.example.plugins
import io.ktor.serialization.*
import io.ktor.server.application.*
import io.ktor.server.plugins.*
import io.ktor.util.*
class RequestLogging {
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, RequestLogging> {
override val key = AttributeKey<RequestLogging>("RequestLogging")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): RequestLogging {
val plugin = RequestLogging()
pipeline.intercept(ApplicationCallPipeline.Monitoring) {
call.request.origin.apply {
println("Request URL: $scheme://$localHost:$localPort$uri")
}
}
return plugin
}
}
}カスタムヘッダー
この例は、各レスポンスにカスタムヘッダーを追加するプラグインを作成する方法を示しています。
package com.example.plugins
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.util.*
class CustomHeader(configuration: Configuration) {
private val name = configuration.headerName
private val value = configuration.headerValue
class Configuration {
var headerName = "Custom-Header-Name"
var headerValue = "Default value"
}
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
override val key = AttributeKey<CustomHeader>("CustomHeader")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): CustomHeader {
val configuration = Configuration().apply(configure)
val plugin = CustomHeader(configuration)
pipeline.intercept(ApplicationCallPipeline.Plugins) {
call.response.header(plugin.name, plugin.value)
}
return plugin
}
}
}ボディの変換
以下の例は、次の方法を示しています。
- クライアントから受信したデータの変換
- クライアントに送信するデータの変換
package com.example.plugins
import io.ktor.serialization.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.util.*
import io.ktor.utils.io.*
class DataTransformation {
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, DataTransformation> {
override val key = AttributeKey<DataTransformation>("DataTransformation")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): DataTransformation {
val plugin = DataTransformation()
pipeline.receivePipeline.intercept(ApplicationReceivePipeline.Transform) { data ->
val newValue = (data as ByteReadChannel).readUTF8Line()?.toInt()?.plus(1)
if (newValue != null) {
proceedWith(newValue)
}
}
pipeline.sendPipeline.intercept(ApplicationSendPipeline.Transform) { data ->
if (subject is Int) {
val newValue = data.toString().toInt() + 1
proceedWith(newValue.toString())
}
}
return plugin
}
}
}パイプライン
Ktor における Pipeline はインターセプターのコレクションであり、1 つ以上の順序付けられたフェーズにグループ化されています。各インターセプターは、リクエスト処理の前後にカスタムロジックを実行できます。
ApplicationCallPipeline は、アプリケーションコールを実行するためのパイプラインです。このパイプラインは 5 つのフェーズを定義しています:
Setup: コールとその属性を処理のために準備するために使用されるフェーズ。Monitoring: コールをトレースするためのフェーズ。リクエストのログ記録、メトリクスの収集、エラー処理などに役立ちます。Plugins: コールを処理するために使用されるフェーズ。ほとんどのプラグインはこのフェーズでインターセプトします。Call: コールを完了するために使用されるフェーズ。Fallback: 未処理のコールを処理するためのフェーズ。
パイプラインフェーズから新しい API ハンドラーへのマッピング
v2.0.0 以降、Ktor はカスタムプラグインを作成するための新しい簡素化された API を提供しています。 一般的に、この API はパイプラインやフェーズなどの内部的な Ktor の概念を理解する必要はありません。代わりに、onCall、onCallReceive、onCallRespond などのさまざまなハンドラーを使用して、リクエストとレスポンスの処理の異なるステージにアクセスできます。 以下の表は、パイプラインフェーズが新しい API のハンドラーにどのように対応するかを示しています。
| Base API | 新しい API |
|---|---|
ApplicationCallPipeline.Setup の前 | on(CallFailed) |
ApplicationCallPipeline.Setup | on(CallSetup) |
ApplicationCallPipeline.Plugins | onCall |
ApplicationReceivePipeline.Transform | onCallReceive |
ApplicationSendPipeline.Transform | onCallRespond |
ApplicationSendPipeline.After | on(ResponseBodyReadyForSend) |
ApplicationSendPipeline.Engine | on(ResponseSent) |
Authentication.ChallengePhase の後 | on(AuthenticationChecked) |
