Skip to content

自訂外掛程式 - 基礎 API

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

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

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

建立外掛程式

若要建立自訂外掛程式,請依照以下步驟操作:

  1. 建立一個外掛程式類別,並宣告一個伴生物件來實作以下其中一個介面:
  2. 實作此伴生物件的 keyinstall 成員。
  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()
            // Intercept a pipeline ...
            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 中的管線是攔截器的集合,這些攔截器被分組在一個或多個有序的階段中。每個攔截器都可以在處理請求之前和之後執行自訂邏輯。

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

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

管線階段到新 API 處理常式的映射

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

基礎 API新 API
before ApplicationCallPipeline.Setupon(CallFailed)
ApplicationCallPipeline.Setupon(CallSetup)
ApplicationCallPipeline.PluginsonCall
ApplicationReceivePipeline.TransformonCallReceive
ApplicationSendPipeline.TransformonCallRespond
ApplicationSendPipeline.Afteron(ResponseBodyReadyForSend)
ApplicationSendPipeline.Engineon(ResponseSent)
after Authentication.ChallengePhaseon(AuthenticationChecked)