處理請求
Ktor 允許您在 路由處理常式 中處理傳入的請求並傳送 回應。
路由處理常式在 ApplicationCall 上運作,其代表用戶端與伺服器之間的一次 HTTP 交換。在路由處理常式中,可以透過 call 屬性存取它,其中包含傳入的請求 (ApplicationRequest) 和傳出的回應 (ApplicationResponse)。
在路由處理常式中,您可以使用 ApplicationCall 來:
一般請求資訊
您可以使用 call.request 屬性存取請求資料。這會傳回 ApplicationRequest 執行個體,並提供對底層 HTTP 請求資訊的存取。
例如,您可以在 GET 請求處理常式中使用 call.request.uri 獲取請求 URI:
routing {
get("/") {
val uri = call.request.uri
call.respondText("Request uri: $uri")
}
}call.respondText() 函式會將純文字回應傳回給用戶端。
頁首
要存取所有 HTTP 請求頁首,請使用 ApplicationRequest.headers 屬性。
為了方便起見,Ktor 還提供了專用的擴充函式來存取常用的頁首,例如 .acceptEncoding()、 .contentType() 和 .cacheControl()。
Cookies
要存取隨請求傳送的 Cookies,請使用 ApplicationRequest.cookies 屬性。
關於使用 Cookies 處理工作階段的更多資訊,請參閱 Sessions 章節。
連線詳細資訊
使用 ApplicationRequest.local 屬性來獲取連線詳細資訊,例如主機名稱、埠 (port) 和 scheme。
X-Forwarded- 頁首
要獲取透過 HTTP 代理或負載平衡器傳遞的請求資訊,請安裝 Forwarded headers 外掛程式並使用 ApplicationRequest.origin 屬性。
路徑參數
處理請求時,您可以使用 ApplicationCall.parameters 屬性獲取 路徑參數 值。
例如,在下方的程式碼片段中,對於 /user/admin 路徑,call.parameters["login"] 將傳回 "admin":
get("/user/{login}") {
if (call.parameters["login"] == "admin") {
// ...
}
}查詢參數
要獲取 URL 查詢字串的參數,您可以使用 ApplicationRequest.queryParameters 屬性。
以下範例存取對 /products?price=asc 請求中的 price 查詢參數:
get("/products") {
if (call.request.queryParameters["price"] == "asc") {
// 顯示價格從最低到最高的產品
}
}您也可以使用 ApplicationRequest.queryString() 函式獲取整個查詢字串。
必要的請求參數
處理請求時,通常會從 路徑參數、查詢參數、頁首 或 Cookies 中提取值,並在繼續處理請求之前驗證它們是否存在。
Ktor 提供以下輔助函式來簡化必要請求資料的存取,而不是在每個路由處理常式中手動檢查缺失的值:
ApplicationCall.requireQueryParameter()— 從請求 URL 中獲取必要的查詢參數。若參數缺失則會拋出例外。ApplicationCall.requireHeader()— 獲取必要的 HTTP 頁首值。若請求中不存在該頁首則會拋出例外。ApplicationCall.requireCookie()— 獲取必要的 Cookie 值,並可選擇使用指定的編碼對其進行解碼。若 Cookie 缺失則會拋出例外。RoutingCall.requirePathParameter()— 從路由定義中獲取必要的路徑參數。若匹配的路由中不存在該參數則會拋出例外。
每個函式都會傳回非 null 值,或在值缺失時拋出 MissingRequestParameterException。
post("/checkout/{cartId}") {
val userId = call.requireCookie("userId")
val cartId = call.requirePathParameter("cartId")
val amount = call.requireQueryParameter("amount").toLong()
// 業務邏輯
}主體內容
本節說明如何接收隨 POST、PUT 或 PATCH 傳送的主體內容。
原始負載 (Raw payload)
要存取原始主體負載並手動剖析,請使用 ApplicationCall.receive() 函式,該函式接受要接收的負載型別。假設您有以下 HTTP 請求:
POST http://localhost:8080/text
Content-Type: text/plain
Hello, world!您可以透過以下方式之一,將此請求的主體作為指定型別的物件接收:
字串 (String)
要將請求主體作為字串值接收,請使用
call.receive<String>()。您也可以使用.receiveText()來達到相同的結果:kotlinpost("/text") { val text = call.receiveText() call.respondText(text) }ByteArray
要將請求的主體作為位元組陣列接收,請呼叫
call.receive<ByteArray>():kotlinpost("/bytes") { val bytes = call.receive<ByteArray>() call.respond(String(bytes)) }ByteReadChannel
您可以使用
call.receive<ByteReadChannel>()或.receiveChannel()來接收ByteReadChannel,它能實現位元組序列的非同步讀取:kotlinpost("/channel") { val readChannel = call.receiveChannel() val text = readChannel.readRemaining().readString() call.respondText(text) }下方範例顯示如何使用
ByteReadChannel來上傳檔案:kotlinpost("/upload") { val file = File("uploads/ktor_logo.png") call.receiveChannel().copyAndClose(file.writeChannel()) call.respondText("A file is uploaded") }
關於 Ktor 通道 (channels) 與
RawSink、RawSource或OutputStream等型別之間的轉換,請參閱 I/O 互通性。
如需完整範例,請參閱 post-raw-data。
物件
Ktor 提供 ContentNegotiation 外掛程式來交涉請求的媒體類型,並將內容反序列化為所需型別的物件。
要接收並轉換請求內容,請呼叫 ApplicationCall.receive() 函式,並傳入一個資料類別 (data class) 作為參數:
post("/customer") {
val customer = call.receive<Customer>()
customerStorage.add(customer)
call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
}若要了解更多資訊,請參閱 Ktor Server 中的內容交涉與序列化。
表單參數
Ktor 允許您使用 receiveParameters 函式接收以 x-www-form-urlencoded 和 multipart/form-data 類型傳送的表單參數。下方範例顯示了一個 HTTP 用戶端 POST 請求,其表單參數在主體中傳遞:
POST http://localhost:8080/signup
Content-Type: application/x-www-form-urlencoded
username=JetBrains&[email protected]&password=foobar&confirmation=foobar您可以在程式碼中按如下方式獲取參數值:
post("/signup") {
val formParameters = call.receiveParameters()
val username = formParameters["username"].toString()
call.respondText("The '$username' account is created")
}如需完整範例,請參閱 post-form-parameters。
多部分表單資料 (Multipart form data)
要接收作為多部分請求的一部分傳送的檔案,請呼叫 .receiveMultipart() 函式,然後根據需要對每個部分進行迴圈處理。
多部分請求資料是循序處理的,因此您無法直接存取其中的特定部分。此外,這些請求可能包含不同類型的部分,例如表單欄位、檔案或二進位資料,需要以不同方式處理。
此範例示範如何接收檔案並將其儲存到檔案系統:
import io.ktor.server.application.*
import io.ktor.http.content.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import java.io.File
fun Application.main() {
routing {
post("/upload") {
var fileDescription = ""
var fileName = ""
val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100)
multipartData.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
fileDescription = part.value
}
is PartData.FileItem -> {
fileName = part.originalFileName as String
val file = File("uploads/$fileName")
part.provider().copyAndClose(file.writeChannel())
}
else -> {}
}
part.dispose()
}
call.respondText("$fileDescription is uploaded to 'uploads/$fileName'")
}
}
}預設檔案大小限制
預設情況下,允許接收的二進位和檔案項目大小限制為 50 MiB。如果接收的檔案或二進位項目超過 50 MiB 限制,則會拋出 IOException。
要覆寫預設表單欄位限制,請在呼叫 .receiveMultipart() 時傳遞 formFieldLimit 參數:
val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100)在此範例中,新的限制設定為 100 MiB。
表單欄位
PartData.FormItem 代表表單欄位,其值可以透過 value 屬性存取:
when (part) {
is PartData.FormItem -> {
fileDescription = part.value
}
}檔案上傳
PartData.FileItem 代表檔案項目。您可以將檔案上傳作為位元組串流處理:
when (part) {
is PartData.FileItem -> {
fileName = part.originalFileName as String
val file = File("uploads/$fileName")
part.provider().copyAndClose(file.writeChannel())
}
}.provider() 函式會傳回 ByteReadChannel,這允許您增量讀取資料。使用 .copyAndClose() 函式,您隨後可以將檔案內容寫入指定的目的地,同時確保正確的資源清理。
要確定上傳檔案的大小,您可以在 post 處理常式中獲取 Content-Length 頁首值:
post("/upload") {
val contentLength = call.request.header(HttpHeaders.ContentLength)
// ...
}資源清理
一旦表單處理完成,會使用 .dispose() 函式處理掉每個部分以釋放資源。
part.dispose()要了解如何執行此範例,請參閱 upload-file。
