iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0

看過了 Ktor 怎麼安裝 json 相關的套件,今天,我們要來看看實際使用的情況,以及套件會怎麼處理我們撰寫的程式內容。

首先我們定義 com.example.models.Customer 類別

這邊可以使用 Kotlin 內建的 data class 關鍵字來建立

package com.example.models

import kotlinx.serialization.Serializable

@Serializable
data class Customer(val id: String, val firstName: String, val lastName: String, val email: String)

接著,我們定義一個儲存 Customer 的列表

val customerStorage = mutableListOf<Customer>()

Ktor 框架是不包含資料庫互動的,這邊我們不討論串接資料庫的內容,所以先用一個列表處理輸入的內容。

這樣一來,我們就可以簡單的建立一個 POST API,每當收到 Customer 資料時,會試著在 customerStorage 內建立新的 Customer

post {
    val customer = call.receive<Customer>()
    customerStorage.add(customer)
    call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
}

以上這段程式碼,Ktor 框架處理請求資料的部分為

val customer = call.receive<Customer>()

我們來看看 receive 的實作

/**
 * Receives content for this request.
 * @return instance of [T] received from this call.
 * @throws ContentTransformationException when content cannot be transformed to the requested type.
 */
public suspend inline fun <reified T : Any> ApplicationCall.receive(): T = receiveNullable(typeInfo<T>())
    ?: throw CannotTransformContentToTypeException(typeInfo<T>().kotlinType!!)

我們先來看看 typeInfo 的邏輯

@OptIn(ExperimentalStdlibApi::class)
public actual inline fun <reified T> typeInfo(): TypeInfo {
    val kType = typeOf<T>()
    val reifiedType = kType.javaType
    return typeInfoImpl(reifiedType, T::class, kType)
}

public fun typeInfoImpl(reifiedType: Type, kClass: KClass<*>, kType: KType?): TypeInfo =
    TypeInfo(kClass, reifiedType, kType)

TypeInfo 則是

/**
 * Ktor type information.
 * @property type: source KClass<*>
 * @property reifiedType: type with substituted generics
 * @property kotlinType: kotlin reified type with all generic type parameters.
 */
public data class TypeInfo(
    public val type: KClass<*>,
    public val reifiedType: Type,
    public val kotlinType: KType? = null
)

到這邊我們看完 Ktor 怎麼透過一系列函數,取得傳入的型態,並將其轉換成 TypeInfo 物件

接著我們看到 receiveNullable

/**
 * Receives content for this request.
 * @param typeInfo instance specifying type to be received.
 * @return instance of [T] received from this call.
 * @throws ContentTransformationException when content cannot be transformed to the requested type.
 */
public suspend fun <T> ApplicationCall.receiveNullable(typeInfo: TypeInfo): T? {
    val token = attributes.getOrNull(DoubleReceivePreventionTokenKey)
    if (token == null) {
        attributes.put(DoubleReceivePreventionTokenKey, DoubleReceivePreventionToken)
    }

    receiveType = typeInfo
    val incomingContent = token ?: request.receiveChannel()
    val transformed = request.pipeline.execute(this, incomingContent)
    when {
        transformed == NullBody -> return null
        transformed === DoubleReceivePreventionToken -> throw RequestAlreadyConsumedException()
        !typeInfo.type.isInstance(transformed) -> throw CannotTransformContentToTypeException(typeInfo.kotlinType!!)
    }

    @Suppress("UNCHECKED_CAST")
    return transformed as T
}

到這邊我們可以看到,前面有一些處理 DoubleReceivePrevention 的邏輯

但是通過這一些邏輯之後,最後我們會透過 request.pipeline.execute(this, incomingContent) 拿到資料

when 的段落裡,如果不能轉換成 typeInfo.type 的話

我們就拋出 CannotTransformContentToTypeException 錯誤

when {
	transformed == NullBody -> return null
	transformed === DoubleReceivePreventionToken -> throw RequestAlreadyConsumedException()
	!typeInfo.type.isInstance(transformed) -> throw CannotTransformContentToTypeException(typeInfo.kotlinType!!)
}

最後 return transformed as T 拿到型態為 T 的回傳值

request.pipeline.execute 的邏輯我們之前看過了,就不多加贅述。

到這邊,我們看過了 val customer = call.receive<Customer>() 這段函數

是怎麼將收到的請求轉換成我們自定義的 Customer 物件

明天我們再來看看其他的轉換邏輯!


上一篇
Day 13:json() 如何註冊一個 Json 格式的處理器
下一篇
Day 15:call.respond 如何轉換回傳內容
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言