昨天我們試著寫了一個 SimplePlugin
import io.ktor.server.application.*
val SimplePlugin = createApplicationPlugin(name = "SimplePlugin") {
println("SimplePlugin is installed!")
}
除了印文字之外,自定義套件當然還可以有其他的功能
我們今天來看看其中的幾項
首先呼叫時的行為,可以在 onCall
定義
val SimplePlugin = createApplicationPlugin(name = "SimplePlugin") {
onCall { call ->
call.request.origin.apply {
println("Request URL: $scheme://$localHost:$localPort$uri")
}
}
}
onCall
的實作如下
/**
* Specifies the [block] handler for every incoming [ApplicationCall].
*
* This block is invoked for every incoming call even if the call is already handled by other handler.
* There you can handle the call in a way you want: add headers, change the response status, etc. You can also
* access the external state to calculate stats.
*
* This example demonstrates how to create a plugin that appends a custom header to each response:
* ```kotlin
* val CustomHeaderPlugin = createApplicationPlugin(name = "CustomHeaderPlugin") {
* onCall { call ->
* call.response.headers.append("X-Custom-Header", "Hello, world!")
* }
* }
* ```
*
* @see [createApplicationPlugin]
*
* @param block An action that needs to be executed when your application receives an HTTP call.
**/
public fun onCall(block: suspend OnCallContext<PluginConfig>.(call: ApplicationCall) -> Unit) {
onDefaultPhase(
callInterceptions,
ApplicationCallPipeline.Plugins,
PHASE_ON_CALL,
::OnCallContext
) { call, _ ->
block(call)
}
}
可以看到 call
的型態是 ApplicationCall
繼續往下追的話,可以看到 call.request
的型態是 ApplicationRequest
call.request.origin
的型態則是 RequestConnectionPoint
/**
* Represents request address information is used to make a call.
* There are at least two possible instances: "local" is how we see request at the server application and
* "actual" is what we can recover from proxy provided headers.
*/
public interface RequestConnectionPoint
繼續往下看 onDefaultPhase
的定義如下
private fun <T : Any, ContextT : CallContext<PluginConfig>> onDefaultPhase(
interceptions: MutableList<Interception<T>>,
phase: PipelinePhase,
handlerName: String,
contextInit: (pluginConfig: PluginConfig, PipelineContext<T, ApplicationCall>) -> ContextT,
block: suspend ContextT.(call: ApplicationCall, body: T) -> Unit
) {
onDefaultPhaseWithMessage(interceptions, phase, handlerName, contextInit) { call, body -> block(call, body) }
}
onDefaultPhaseWithMessage
則是
private fun <T : Any, ContextT : CallContext<PluginConfig>> onDefaultPhaseWithMessage(
interceptions: MutableList<Interception<T>>,
phase: PipelinePhase,
handlerName: String,
contextInit: (pluginConfig: PluginConfig, PipelineContext<T, ApplicationCall>) -> ContextT,
block: suspend ContextT.(ApplicationCall, T) -> Unit
) {
interceptions.add(
Interception(
phase,
action = { pipeline ->
pipeline.intercept(phase) {
// Information about the plugin name is needed for the Intellij Idea debugger.
val key = this@PluginBuilder.key
val pluginConfig = this@PluginBuilder.pluginConfig
addToContextInDebugMode(key.name) {
ijDebugReportHandlerStarted(pluginName = key.name, handler = handlerName)
// Perform current plugin's handler
contextInit(pluginConfig, this@intercept).block(call, subject)
ijDebugReportHandlerFinished(pluginName = key.name, handler = handlerName)
}
}
}
)
)
}
除了程式所需的邏輯,我們還可以看到專門提供 IDE 除錯用的
ijDebugReportHandlerStarted
和 ijDebugReportHandlerFinished
能取得 call.request
,那麼照道理說,應該也能取得 call.response
寫法如下
val CustomHeaderPlugin = createApplicationPlugin(name = "CustomHeaderPlugin") {
onCall { call ->
call.response.headers.append("X-Custom-Header", "Hello, world!")
}
}
除了 onCall
以外,還有 onCallReceive
/**
* Specifies the [block] handler that allows you to obtain and transform data received from the client.* This [block] is invoked for every attempt to receive the request body.* @see [createApplicationPlugin]
*
* @param block An action that needs to be executed when your application receives data from a client.
**/
public fun onCallReceive(
block: suspend OnCallReceiveContext<PluginConfig>.(call: ApplicationCall) -> Unit
) {
onCallReceive { call, _ -> block(call) }
}
/**
* Specifies the [block] handler that allows you to obtain and transform data received from the client.
* This [block] is invoked for every attempt to receive the request body.
* @see [createApplicationPlugin]
*
* @param block An action that needs to be executed when your application receives data from a client.
**/
public fun onCallReceive(
block: suspend OnCallReceiveContext<PluginConfig>.(call: ApplicationCall, body: Any) -> Unit
) {
onDefaultPhase(
onReceiveInterceptions,
ApplicationReceivePipeline.Transform,
PHASE_ON_CALL_RECEIVE,
::OnCallReceiveContext,
) { call, body: Any -> block(call, body) }
}
onCallRespond
/**
* Specifies the [block] handler that allows you to transform data before sending it to the client.
* This handler is executed when the `call.respond` function is invoked in a route handler.
* @see [createApplicationPlugin]
*
* @param block An action that needs to be executed when your server is sending a response to a client.
**/
public fun onCallRespond(
block: suspend OnCallRespondContext<PluginConfig>.(call: ApplicationCall) -> Unit
) {
onCallRespond { call, _ -> block(call) }
}
/**
* Specifies the [block] handler that allows you to transform data before sending it to the client.
* This handler is executed when the `call.respond` function is invoked in a route handler.
* @see [createApplicationPlugin]
*
* @param block An action that needs to be executed when your server is sending a response to a client.
**/
public fun onCallRespond(
block: suspend OnCallRespondContext<PluginConfig>.(call: ApplicationCall, body: Any) -> Unit
) {
onDefaultPhase(
onResponseInterceptions,
ApplicationSendPipeline.Transform,
PHASE_ON_CALL_RESPOND,
::OnCallRespondContext,
block
)
}
可以看出來,基本上邏輯都跟剛剛所說的 onCall
一樣,都是利用 onDefaultPhase
來協助接收開發者撰寫的內容。
不過由於階段的不同,以及處理對象的不同,所以在參數上需要調整一下收到的內容。
這邊我們就可以看到,這裡整理了接收請求以及送出回應的流程,將這兩種流程合併成 pipeline
的定義,這樣就可以共用這一段邏輯。我們只需要調整處理的對象,以及該對象面對的 pipeline
即可。
今天針對自定義的流程控制,我們就先看到這邊。