カスタムプラグイン - ベース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()
// Intercept a pipeline ...
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"
}
プラグインの設定フィールドは可変であるため、それらをローカル変数に保存することをお勧めします。
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のパイプラインは、1つ以上の順序付けられたフェーズにグループ化されたインターセプターの集合です。各インターセプターは、リクエストを処理する前と後にカスタムロジックを実行できます。
ApplicationCallPipelineは、アプリケーションの呼び出しを実行するためのパイプラインです。このパイプラインは5つのフェーズを定義します。
Setup
: 呼び出しとその属性を処理のために準備するために使用されるフェーズ。Monitoring
: 呼び出しをトレースするためのフェーズ。リクエストロギング、メトリクス収集、エラーハンドリングなどに役立ちます。Plugins
: 呼び出しを処理するために使用されるフェーズ。ほとんどのプラグインはこのフェーズでインターセプトします。Call
: 呼び出しを完了するために使用されるフェーズ。Fallback
: 未処理の呼び出しを処理するためのフェーズ。
パイプラインフェーズから新しいAPIハンドラへのマッピング
v2.0.0から、Ktorはカスタムプラグインの作成のための新しい簡易化されたAPIを提供します。 一般的に、このAPIはパイプライン、フェーズなどのKtor内部概念の理解を必要としません。代わりに、onCall
、onCallReceive
、onCallRespond
などのさまざまなハンドラを使用して、リクエストとレスポンスを処理するさまざまな段階にアクセスできます。 以下の表は、パイプラインフェーズが新しいAPIのハンドラにどのようにマッピングされるかを示しています。
Base API | New 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) |