基础入门

更改默认端口

代码配置

Application.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun main() {
    embeddedServer(
        Netty,
        port = 8080, //更改端口
        host = "0.0.0.0",
        module = Application::module
    ).start(wait = true)
}

fun Application.module() {
    configureRouting()
}

yaml配置

Application.kt

1
2
3
4
5
6
7
fun main(args: Array<String>): Unit =
    io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused")
fun Application.module() {
    configureRouting()
}

application.yaml

1
2
3
4
5
6
ktor:
  application:
    modules:
      - com.example.ApplicationKt.module
  deployment:
    port: 8080

添加新端点

plugins/Routing.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }

        get("/test1") {
            val text = "<h1>Hello From Ktor</h1>"
            val type = ContentType.parse("text/html")
            call.respondText(text, type)
        }
    }
}

访问http://localhost:8080/test1即可看到相应

配置静态资源

plugins/Routing.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun Application.configureRouting() {
    routing {
        //在这里配置资源
        staticResources("/content", "mycontent")

        get("/") {
            call.respondText("Hello World!")
        }
    }
}

解释:验证 staticResources() 告诉Ktor,我们希望我们的应用程序能够提供标准的网站内容,例如HTML和JavaScript文件。虽然这些内容可以在浏览器中执行,但从服务器的角度来看,它被认为是静态的。

URL /content 指定用于获取此内容的路径。

路径 mycontent 是静态内容所在文件夹的名称。Ktor将在 resources 目录中查找此文件夹。

集成测试

src/test/kotlin/ApplicationTest.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
fun testNewEndpoint() = testApplication {
        application {
            module()
        }

        val response = client.get("/test1")

        assertEquals(HttpStatusCode.OK, response.status)
        assertEquals("html", response.contentType()?.contentSubtype)
        assertContains(response.bodyAsText(), "Hello From Ktor")
    }

自定义异常处理

依赖:implementation(“io.ktor:ktor-server-status-pages:$ktor_version”)

plugins/Routing.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fun Application.configureRouting() {
//   指定错误处理
    install(StatusPages) {
        exception<IllegalStateException> { call, cause ->
            call.respondText("App in illegal state as ${cause.message}")
        }
    }
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
        get("/error-test") {
            throw IllegalStateException("Too Busy")
//      触发异常处理
        }
    }
}

创建HTTP API

基本依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
plugins {
    kotlin("plugin.serialization") version "1.9.10"
}
dependencies {
    implementation("io.ktor:ktor-server-core-jvm")
    implementation("io.ktor:ktor-server-content-negotiation-jvm")
    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
    implementation("io.ktor:ktor-server-netty-jvm")
    implementation("ch.qos.logback:logback-classic:$logback_version")
    testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}

添加并定义程序模块

Application.kt

1
2
3
4
5
6
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

fun Application.module() {
    configureRouting()//错误处理路由
    configureSerialization()//添加新模块
}

plugins/Serialization.kt

1
2
3
4
5
fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
}

创建客户模型

Customer.kt

1
2
3
4
5
6
package com.example.models

import kotlinx.serialization.Serializable

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

@Serializable 注解。结合其 Ktor 集成,这将使我们能够自动生成 API 响应所需的 JSON 表示

创建内存存储

1
val customerStorage = mutableListOf<Customer>()

创建路由

customerRouting.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fun Route.customerRouting() {
    route("/customer") {
        get {

        }
        get("{id?}") {

        }
        post {

        }
        delete("{id?}") {

        }
    }
}

构建响应

json序列化

customerRouting.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun Route.customerRouting() {
    route("/customer") {
        get {
            if (customerStorage.isNotEmpty()) {
                call.respond(customerStorage)//自动配置json序列化
            } else {
                call.respondText("No customers found", status = HttpStatusCode.OK)
            }
        }
    }
}

路径传参

customerRouting.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
get("{id?}") {
    val id = call.parameters["id"] ?: return@get call.respondText(
        "Missing id",
        status = HttpStatusCode.BadRequest
    )
    val customer =
        customerStorage.find { it.id == id } ?: return@get call.respondText(
            "No customer with id $id",
            status = HttpStatusCode.NotFound
        )
    call.respond(customer)
}

请求解析

customerRouting.kt

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

call.receive 与配置的内容协商插件集成。使用 generic 参数调用它会 Customer 自动将 JSON 请求正文反序列化为 Kotlin Customer 对象

注册路由

plugins/Routing.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.example.plugins

import com.example.routes.*
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting() {
    routing {
        customerRouting()
    }
}

规范化路由定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fun Route.listOrdersRoute() {
    get("/order") {
        if (orderStorage.isNotEmpty()) {
            call.respond(orderStorage)
        }
    }
}
fun Route.getOrderRoute() {
    get("/order/{id?}") {
        val id = call.parameters["id"] ?: return@get call.respondText("Bad Request", status = HttpStatusCode.BadRequest)
        val order = orderStorage.find { it.number == id } ?: return@get call.respondText(
            "Not Found",
            status = HttpStatusCode.NotFound
        )
        call.respond(order)
    }
}
fun Route.totalizeOrderRoute() {
    get("/order/{id?}/total") {
        val id = call.parameters["id"] ?: return@get call.respondText("Bad Request", status = HttpStatusCode.BadRequest)
        val order = orderStorage.find { it.number == id } ?: return@get call.respondText(
            "Not Found",
            status = HttpStatusCode.NotFound
        )
        val total = order.contents.sumOf { it.price * it.amount }
        call.respond(total)
    }
}

跨域配置

Application.module()

1
2
3
install(CORS) {
    anyHost()
}