iT邦幫忙

2023 iThome 鐵人賽

DAY 3
1

看過了 embeddedServer().start(),我們接著來看看 Ktor 是如何設置 Route

首先我們看到 embeddedServer() 裡面的參數

module = Application::module

這邊 module 內輸入的是一個函數,型態為 Application.() -> Unit,也就是某個 Application 底下的函數,不接受參數也不會回傳。

這段 extension function 直接寫在專案內 main() 的下面,我們來看看程式碼裡面的實作:

fun Application.module() {
    configureRouting()
}

這段 module 非常單純,目前只有 configureRouting 而已。

configureRouting() 不是定義在 main.kt,而是定義在 plugins/Routing.kt 裡面

fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
    }
}

這邊就要提到 Ktor 的一個設計邏輯,就是包含路由在內,框架的功能功能都是以插件的形式,注入進框架使用。

這樣一來,當你不需要某個功能,你就可以自由選擇不引入該插件,減少框架本身的負擔。

那麼,這邊的 routing 是怎麼實作的呢?

@KtorDsl
public fun Application.routing(configuration: Routing.() -> Unit): Routing =
    pluginOrNull(Routing)?.apply(configuration) ?: install(Routing, configuration)

如果讀者熟悉 Kotlin 的寫法的話,應該有看過這個函數的處理方式:如果一個函數內的參數只有一個,並且是接收一個匿名函數。那麼我們在使用該函數時,不需要將該參數放進小括弧內,可以直接在大括弧內撰寫該匿名函數的實作。

這邊的實作很單純:如果 pluginOrNull(Routing) 為真,那麼就只處理收到的內容。不然就必須再進行安裝 Routing 的過程。

參考一下 pluginOrNull() 的實作以及註解

/**  
* Returns a plugin instance for this pipeline, or null if the plugin is not installed.  
*/
public fun <A : Pipeline<*, ApplicationCall>, F : Any> A.pluginOrNull(plugin: Plugin<*, *, F>): F? {
    return pluginRegistry.getOrNull(plugin.key)
}

可以得知,這段就是從 pluginRegistry 找到套件,有則回傳該套件,沒有則回傳 null。

如果有安裝過 Routing,則直接對 Routing 加上設置即可。這邊的 apply 是 Kotlin 提供的 scope function,語言本身的使用方式我們不往下延伸說明,只需要知道這可以在 Routing 加上 configuration 即可。

如果沒有安裝的話,會進入到 install(Routing, configuration) 這段,我們來看看其註解和函數簽名:

/**
 * Installs a [plugin] into this pipeline, if it is not yet installed.
 */
public fun <P : Pipeline<*, ApplicationCall>, B : Any, F : Any> P.install(
    plugin: Plugin<P, B, F>,
    configure: B.() -> Unit = {}
): F

根據註解說明,這段函數基本上就是安裝套件用

我們往下看看實作:

if (this is Route && plugin is BaseRouteScopedPlugin) {
	return installIntoRoute(plugin, configure)
}

我們可以發現,這段會 Route 以及對應插件進行特殊處理,剛好我們目前也只需要理解這段邏輯,可以直接先看 installIntoRoute

installIntoRoute 的實作如下:

private fun <B : Any, F : Any> Route.installIntoRoute(
    plugin: BaseRouteScopedPlugin<B, F>,
    configure: B.() -> Unit = {}
): F {
    if (pluginRegistry.getOrNull(plugin.key) != null) {
        throw DuplicatePluginException(
            "Please make sure that you use unique name for the plugin and don't install it twice. " +
                "Plugin `${plugin.key.name}` is already installed to the pipeline $this"
        )
    }
    if (application.pluginRegistry.getOrNull(plugin.key) != null) {
        throw DuplicatePluginException(
            "Installing RouteScopedPlugin to application and route is not supported. " +
                "Consider moving application level install to routing root."
        )
    }

前面兩段是錯誤處理,

    // we install plugin into fake pipeline and add interceptors manually
    // to avoid having multiple interceptors after pipelines are merged
    val fakePipeline = when (this) {
        is Routing -> Routing(application)
        else -> Route(parent, selector, developmentMode, environment)
    }
    val installed = plugin.install(fakePipeline, configure)
    pluginRegistry.put(plugin.key, installed)

    mergePhases(fakePipeline)
    receivePipeline.mergePhases(fakePipeline.receivePipeline)
    sendPipeline.mergePhases(fakePipeline.sendPipeline)

    addAllInterceptors(fakePipeline, plugin, installed)
    receivePipeline.addAllInterceptors(fakePipeline.receivePipeline, plugin, installed)
    sendPipeline.addAllInterceptors(fakePipeline.sendPipeline, plugin, installed)

    return installed
}

這邊解釋了函數設計的邏輯:我們先定義一個假的 Pipeline,然後將攔截器(interceptor)一個個加入,避免加入多個攔截器。

這邊我們也可以看到 pluginRegistry.put(plugin.key, installed),所以安裝過之後,前面的 pluginRegistry.getOrNull(plugin.key) 就可以知道這個套件已經安裝過了。

今天我們追到 routing() 的設計,看到 Ktor 如何透過一系列的流程,安裝上程式內撰寫的路由。

沒想到光是要加入一個路由,邏輯就已經很多了!之後的部分我們就明天再看吧!


上一篇
Day 2:開始一個 server 服務:embeddedServer().start()
下一篇
Day 04:建立路徑,來看看 get() 函數
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言