自定义插件 - 基础 API
代码示例: custom-plugin-base-api
从 v2.0.0 开始,Ktor 为创建自定义插件提供了一个新的简化 API。
Ktor 暴露了用于开发自定义插件的 API,这些插件实现了通用功能,并可以在多个应用程序中复用。 此 API 允许您拦截不同的流水线阶段,从而为请求/响应处理添加自定义逻辑。 例如,您可以拦截 Monitoring 阶段来记录传入请求或收集指标。
创建插件
要创建自定义插件,请按照以下步骤操作:
- 创建一个插件类并声明一个伴生对象,该对象需实现以下接口之一:
- BaseApplicationPlugin:如果插件应该在应用程序级别工作。
- BaseRouteScopedPlugin:如果插件可以安装到特定路由。
- 实现此伴生对象的
key和install成员。 - 提供插件配置。
- 通过拦截所需的流水线阶段来处理调用。
- 安装插件。
创建伴生对象
自定义插件的类应具有一个实现了 BaseApplicationPlugin 或 BaseRouteScopedPlugin 接口的伴生对象。 BaseApplicationPlugin 接口接受三个类型形参:
- 此插件兼容的流水线类型。
- 此插件的配置对象类型。
- 插件对象的实例类型。
class CustomHeader() {
companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, CustomHeader> {
// ...
}
}实现 'key' 和 'install' 成员
作为 BaseApplicationPlugin 接口的子类,伴生对象应实现两个成员:
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
}
}
}处理调用
在自定义插件中,您可以通过拦截现有流水线阶段或新定义的阶段来处理请求和响应。例如,身份验证插件将 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 中的 Pipeline 是拦截器的集合,这些拦截器被分组到一个或多个有序阶段中。每个拦截器都可以在处理请求之前和之后执行自定义逻辑。
ApplicationCallPipeline 是用于执行应用程序调用的流水线。此流水线定义了 5 个阶段:
Setup:用于为处理准备调用及其属性的阶段。Monitoring:用于跟踪调用的阶段。它对于请求日志记录、收集指标、错误处理等可能很有用。Plugins:用于处理调用的阶段。大多数插件在此阶段进行拦截。Call:用于完成调用的阶段。Fallback:用于处理未处理调用的阶段。
流水线阶段到新 API 处理程序的映射
从 v2.0.0 开始,Ktor 为创建自定义插件提供了一个新的简化 API。 通常,此 API 不需要理解 Ktor 内部概念,例如流水线、阶段等。相反,您可以使用各种处理程序(如 onCall、onCallReceive、onCallRespond 等)访问处理请求和响应的不同阶段。 下表显示了流水线阶段如何映射到新 API 中的处理程序。
| 基础 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) |
