处理请求
Ktor 允许您在路由处理器内处理传入传入请求并发送响应。处理请求时,您可以执行多种操作:
常规请求信息
在路由处理器内,您可以使用 call.request 属性访问请求。这会返回 ApplicationRequest 实例,并提供对各种请求参数的访问。例如,以下代码片段展示了如何获取请求 URI:
routing {
get("/") {
val uri = call.request.uri
call.respondText("Request uri: $uri")
}
}
call.respondText 方法用于向客户端发送响应。
ApplicationRequest 对象允许您访问各种请求数据,例如:
- 请求头
要访问所有请求头,请使用 ApplicationRequest.headers 属性。您还可以使用专用的扩展函数访问特定请求头,例如acceptEncoding
、contentType
、cacheControl
等。 - Cookie
ApplicationRequest.cookies 属性提供对与请求相关的 Cookie 的访问。要了解如何使用 Cookie 处理会话,请参阅会话部分。 - 连接详情
使用 ApplicationRequest.local 属性访问连接详情,例如主机名、端口、方案等。 X-Forwarded-
请求头
要获取通过 HTTP 代理或负载均衡器传递的请求信息,请安装 Forwarded headers 插件并使用 ApplicationRequest.origin 属性。
路径参数
处理请求时,您可以使用 call.parameters
属性访问路径参数值。例如,以下代码片段中的 call.parameters["login"]
对于 /user/admin
路径将返回 admin:
get("/user/{login}") {
if (call.parameters["login"] == "admin") {
// ...
}
}
查询参数
要访问查询字符串的参数,您可以使用 ApplicationRequest.queryParameters 属性。例如,如果向 /products?price=asc
发出请求,您可以按以下方式访问 price
查询参数:
get("/products") {
if (call.request.queryParameters["price"] == "asc") {
// 显示从最低价到最高价的产品
}
}
您还可以使用 ApplicationRequest.queryString 函数获取整个查询字符串。
正文内容
本节展示了如何接收随 POST
、PUT
或 PATCH
请求发送的正文内容。
原始有效负载
要访问原始正文有效负载并手动解析它,请使用 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) }
字节数组
要将请求正文作为字节数组接收,请调用
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().readText() call.respondText(text) }
以下示例展示了如何使用
ByteReadChannel
上传文件:kotlinpost("/upload") { val file = File("uploads/ktor_logo.png") call.receiveChannel().copyAndClose(file.writeChannel()) call.respondText("A file is uploaded") }
您可以在此处找到完整示例:post-raw-data。
对象
Ktor 提供了一个 ContentNegotiation 插件,用于协商请求的媒体类型并将内容反序列化为所需类型的对象。要接收并转换请求的内容,请调用 ApplicationCall.receive 函数,该函数接受数据类作为参数:
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。
多部分表单数据
要接收作为多部分请求一部分发送的文件,请调用 .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'")
}
}
}
默认文件大小限制
默认情况下,可接收的二进制和文件项的大小限制为 50MB。如果接收到的文件 或二进制项超过 50MB 限制,将抛出 IOException
。
要覆盖默认表单字段限制,请在调用 .receiveMultipart()
时传递 formFieldLimit
参数:
val multipartData = call.receiveMultipart(formFieldLimit = 1024 * 1024 * 100)
在此示例中,新限制设置为 100MB。
表单字段
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。