看過了 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
物件
明天我們再來看看其他的轉換邏輯!