Skip to content

自訂外掛程式 - Base API

程式碼範例 custom-plugin-base-api

從 v2.0.0 開始,Ktor 提供了一個新的簡化 API 用於建立自訂外掛程式

Ktor 公開了用於開發自訂外掛程式的 API,這些外掛程式可實作通用功能並可在多個應用程式中重複使用。 此 API 允許您攔截不同的管線階段,以便在請求/回應處理中加入自訂邏輯。 例如,您可以攔截 Monitoring 階段來記錄傳入的請求或收集指標。

建立外掛程式

要建立自訂外掛程式,請遵循以下步驟:

  1. 建立一個外掛程式類別並宣告一個伴隨物件,該物件需實作以下介面之一:
  2. 實作此伴隨物件的 key (金鑰) 和 install 成員。
  3. 提供外掛程式配置
  4. 透過攔截必要的管線階段來處理呼叫
  5. 安裝外掛程式

建立伴隨物件

自訂外掛程式的類別應該有一個實作 BaseApplicationPluginBaseRouteScopedPlugin 介面的伴隨物件。 BaseApplicationPlugin 介面接受三個型別參數:

  • 此外掛程式相容的管線型別。
  • 此外掛程式的配置物件型別
  • 外掛程式物件的執行個體型別。
kotlin
class CustomHeader() {
    companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
        // ...
    }
}

實作 'key' 和 'install' 成員

作為 BaseApplicationPlugin 介面的衍生,伴隨物件應實作兩個成員:

  • key 屬性用於識別外掛程式。Ktor 擁有所有屬性的對應表,每個外掛程式都會使用指定的金鑰將自己加入此對應表中。
  • install 函式允許您配置外掛程式的運作方式。在這裡您需要攔截管線並傳回外掛程式執行個體。我們將在下一章中了解如何攔截管線並處理呼叫。
kotlin
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 外掛程式將 AuthenticateChallenge 自訂階段加入到預設管線中。因此,攔截特定的管線允許您存取呼叫的不同階段,例如:

  • ApplicationCallPipeline.Monitoring:攔截此階段可用於請求記錄或收集指標。
  • ApplicationCallPipeline.Plugins:可用於修改回應參數,例如附加自訂標頭。
  • ApplicationReceivePipeline.TransformApplicationSendPipeline.Transform:允許您取得並轉換從用戶端收到的資料,以及在傳回資料前進行轉換。

下面的範例示範如何攔截 ApplicationCallPipeline.Plugins 階段並在每個回應中附加一個自訂標頭:

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

請注意,此外掛程式中的自訂標頭名稱和值是寫死的。您可以透過提供配置來傳遞所需的自訂標頭名稱/值,使此外掛程式更加靈活。

自訂外掛程式允許您共用與呼叫相關的任何值,因此您可以在處理此呼叫的任何處理常式中存取此值。您可以從共用呼叫狀態中了解更多資訊。

提供外掛程式配置

前一章展示了如何建立一個將預定義自訂標頭附加到每個回應的外掛程式。讓我們讓此外掛程式更有用,並提供一個配置來傳遞所需的自訂標頭名稱/值。首先,您需要在外掛程式類別中定義一個配置類別:

kotlin
class Configuration {
    var headerName = "Custom-Header-Name"
    var headerValue = "Default value"
}

鑑於外掛程式配置欄位是可變的,建議將它們儲存在區域變數中:

kotlin
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 函式中,您可以取得此配置並使用其屬性:

kotlin
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 函式並傳遞所需的配置參數:

kotlin
install(CustomHeader) {
    headerName = "X-Custom-Header"
    headerValue = "Hello, world!"
}

範例

下面的程式碼片段示範了自訂外掛程式的幾個範例。 您可以在此處找到可執行的專案:custom-plugin-base-api

請求記錄

下面的範例顯示如何建立一個用於記錄傳入請求的自訂外掛程式:

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

自訂標頭

此範例示範如何建立一個在每個回應中附加自訂標頭的外掛程式:

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

內容轉換

下面的範例展示了如何:

  • 轉換從用戶端收到的資料;
  • 轉換要發送到用戶端的資料。
kotlin
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(管線)是攔截器的集合,分組在一個或多個有序階段中。每個攔截器都可以在處理請求之前和之後執行自訂邏輯。

ApplicationCallPipeline 是用於執行應用程式呼叫的管線。此管線定義了 5 個階段:

  • Setup:用於準備呼叫及其屬性以進行處理的階段。
  • Monitoring:用於執行緒呼叫的階段。它對於請求記錄、收集指標、錯誤處理等可能很有用。
  • Plugins:用於處理呼叫的階段。大多數外掛程式在此階段進行攔截。
  • Call:用於完成呼叫的階段。
  • Fallback:用於處理未處理呼叫的階段。

管線階段到新 API 處理常式的對應

從 v2.0.0 開始,Ktor 提供了一個新的簡化 API 用於建立自訂外掛程式。 一般而言,此 API 不需要了解 Ktor 內部概念,例如管線、階段等。相反地,您可以使用各種處理常式(例如 onCallonCallReceiveonCallRespond 等)來存取處理請求和回應的不同階段。 下表顯示了管線階段如何對應到新 API 中的處理常式。

Base APINew API
ApplicationCallPipeline.Setup 之前on(CallFailed)
ApplicationCallPipeline.Setupon(CallSetup)
ApplicationCallPipeline.PluginsonCall
ApplicationReceivePipeline.TransformonCallReceive
ApplicationSendPipeline.TransformonCallRespond
ApplicationSendPipeline.Afteron(ResponseBodyReadyForSend)
ApplicationSendPipeline.Engineon(ResponseSent)
Authentication.ChallengePhase 之後on(AuthenticationChecked)