但凡是 Web 服务,都会有接受请求和给出响应的时候,Ktor
在这一点上并不例外,而且 Ktor 允许你使用非常优雅的方法来处理请求,并且返回你所预期的内容,本篇即讲述这些内容。
在 Ktor 内接受请求只需要用以下代码即可:
fun Application.main() {
routing {
get("/demo") {
call.respondText { "respond" }
}
}
}
运行后,当你请求 http://localhost:8080/demo
时,即会触发这个响应,页面上显示 respond
。
当我们需要带上参数请求时,可以使用 call
对象内带的 parameters
,并且处理时可以按 map 来进行。
get("/demo") {
var str = ""
call.parameters.entries().forEach {
str += "${it.key} = ${it.value.joinToString(",")}\n"
}
call.respondText { str }
}
当我们请求 http://localhost:8080/demo?a=1&b=2
时,即可得到返回
a = 1
b = 2
同样的,使用 post()
即可处理 POST 请求,对于携带了文件上传的请求,可以这样处理:
post("/upload") {
val parts = call.receiveMultipart()
parts.forEachPart {
when(it) {
is PartData.FormItem -> {
println("${it.name} = ${it.value}")
}
is PartData.FileItem -> {
val file = File("", "file-${System.currentTimeMillis()}")
it.streamProvider().use { input ->
file.outputStream().buffered().use { output ->
input.copyToSuspend(output)
}
}
}
}
it.dispose()
}
}
这里需要注意的是,在 post
请求中,获取参数应当使用 call.receiveParameters()
,而不是直接使用 call.parameters
。
其中 copyToSuspend
是一个挂起函数,它的实现如下:
suspend fun InputStream.copyToSuspend(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE, yieldSize: Int = 4 * 1024 * 1024, dispatcher: CoroutineDispatcher = Dispatchers.IO): Long {
return withContext(dispatcher) {
val buffer = ByteArray(bufferSize)
var bytesCopied = 0L
var bytesAfterYield = 0L
while (true) {
val bytes = read(buffer).takeIf { it >= 0 } ?: break
out.write(buffer, 0, bytes)
if (bytesAfterYield >= yieldSize) {
yield()
bytesAfterYield %= yieldSize
}
bytesCopied += bytes
bytesAfterYield += bytes
}
return@withContext bytesCopied
}
}
可以看到,这个函数被标记为 suspend
,也就意味着它只能在协程或挂起函数中使用,而我们一直在使用的 get()
或 post()
的回调就是一个挂起函数,它借由 Ktor 的协程进行调用。
对于页面的返回,我们经常要用到模板,Ktor 提供了模板的支持,可以方便的将数据填入页面模板中。
假设有以下页面模板,命名为 index.ftl
并位于 resources/templates
下:
<html>
Hello ${user.name}
</html>
在这个页面里,需要将 user.name
替换为自己的用户名,那么我们可以这么做:
data class User(val name: String)
fun Application.main() {
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
}
routing {
get("/demo") {
val user = User(call.parameters["name"])
call.respond(FreeMarkerContent("index.ftl", mapOf("user" to user), "e"))
}
}
}
这个时候就可以编译运行了,在浏览器内请求 http://localhost:8080/demo?name=rarnu
即可看到想要的页面了。在编译前,记得在 build.gradle
添加对 freemarker
的引用:
compile "io.ktor:ktor-freemarker:$ktor_version"
到此,你已经可以快速的完成各类请求与页面的返回了,本篇也到此结束。
下一篇预告:《Ktor 从入门到放弃(四) 数据库操作》