如何在 Kotlin 中使用 Ktor 创建 RESTful API
代码示例: tutorial-server-restful-api
使用的插件:
在本教程中,我们将解释如何使用 Kotlin 和 Ktor 构建后端服务,其中包括一个生成 JSON 文件的 RESTful API 示例。
在
你将学习如何执行以下操作:
- 创建使用 JSON 序列化的 RESTful 服务。
- 理解 内容协商的过程。ContentNegotiation 插件主要有两个目的:协商客户端和服务器之间的媒体类型,以及以特定格式序列化/反序列化内容。
- 在 Ktor 中定义 REST API 的路由。
先决条件
你可以独立完成本教程, 但是,我们强烈建议你完成之前的教程,了解如何
我们建议你安装 IntelliJ IDEA,但你也可以使用你选择的其他 IDE。
Hello RESTful 任务管理器
在本教程中,你将把现有的任务管理器重写为 RESTful 服务。为此,你将使用多个 Ktor
虽然你可以手动将其添加到现有项目中,但更简单的方法是生成一个新项目,然后逐步添加之前教程中的代码。你将在过程中重复所有的代码,因此你不需要手头有之前的项目。
导航到 Ktor 项目生成器 。
在 项目构件 字段中,输入 com.example.ktor-rest-task-app 作为你的项目构件名称。
在插件部分,通过点击 添加 按钮搜索并添加以下插件:
- Routing
- Content Negotiation
- Kotlinx.serialization
- Static Content
添加插件后,你将看到所有 四个插件列在项目设置下方。
点击 下载 按钮以生成并下载你的 Ktor 项目。
在 IntelliJ IDEA 中打开你的项目,如在 IntelliJ IDEA 中打开、探索并运行你的 Ktor 项目教程中所述。
导航到 src/main/kotlin/com/example 并创建一个名为 model 的子包。
在model包内,创建一个新的 Task.kt 文件。
打开 Task.kt 文件,添加一个
enum
来表示优先级,以及一个class
来表示任务:kotlin在之前的教程中,你使用了扩展函数将
Task
转换为 HTML。在本例中,Task
类使用kotlinx.serialization
库中的Serializable
类型进行了注解。打开 Routing.kt 文件,并用以下实现替换现有代码:
kotlin与之前的教程类似,你为 URL
/tasks
的 GET 请求创建了一个路由。 这次,你不再手动转换任务列表,而是直接返回该列表。在 IntelliJ IDEA 中,点击运行按钮 (
) 来启动应用程序。
在浏览器中导航到 http://0.0.0.0:8080/tasks。你应该会看到任务列表的 JSON 版本,如下所示:

显然,有很多工作正在替我们完成。到底发生了什么?
理解内容协商
通过浏览器进行内容协商
创建项目时,你包含了
在 HTTP 中,客户端通过 Accept
标头表明它可以渲染哪些内容类型。此标头的值是一个或多个内容类型。在上述情况下,你可以使用浏览器内置的开发工具来检查此标头的值。
考虑以下示例:
请注意包含 /
。此标头表示它接受 HTML、XML 或图像——但它也会接受任何其他内容 类型。
Content Negotiation 插件需要找到一种格式将数据发送回浏览器。如果你查看项目中的生成代码,你会发现src/main/kotlin/com/example目录下有一个名为Serialization.kt的文件,其中包含以下内容:
此代码安装了 ContentNegotiation
插件,并配置了 kotlinx.serialization
插件。这样,当客户端发送请求时,服务器可以发送回序列化为 JSON 的对象。
在来自浏览器的请求中,ContentNegotiation
插件知道它只能返回 JSON,并且浏览器会尝试显示它收到的任何内容。因此请求成功。
为了模拟这一点,打开 src/main/resources/static 目录下的 index.html 页面,并用以下内容替换默认内容:
html此页面包含一个 HTML 表单和一个空表。提交表单后,JavaScript 事件处理程序会向
/tasks
端点发送请求,并将Accept
标头设置为application/json
。返回的数据随后被反序列化并添加到 HTML 表格中。在 IntelliJ IDEA 中,点击重新运行按钮 (
) 来重新启动应用程序。
导航到 URL http://0.0.0.0:8080/static/index.html。 你应该能够通过点击 查看任务 按钮来获取数据:
在生产环境中,你不会希望直接在浏览器中显示 JSON。相反,会有 JavaScript 代码在浏览器中运行,它会发出请求,然后将返回的数据作为单页应用 (SPA) 的一部分显示出来。通常,这类应用会使用诸如 React、 Angular 或 Vue.js 等框架编写。
添加 GET 路由
既然你已经熟悉了内容协商的过程,接下来请继续将
重用任务仓库
你可以不加修改地重用任务仓库,所以我们先来做这件事。
在model包内,创建一个新的 TaskRepository.kt 文件。
打开 TaskRepository.kt 并添加以下代码:
kotlin
重用 GET 请求的路由
现在你已经创建了仓库,可以实现 GET 请求的路由了。之前的代码可以简化,因为你不再需要担心将任务转换为 HTML:
导航到src/main/kotlin/com/example目录下的 Routing.kt 文件。
使用以下实现更新
Application.configureRouting()
函数中/tasks
路由的代码:kotlin有了这个,你的服务器可以响应以下 GET 请求:
/tasks
返回仓库中的所有任务。/tasks/byName/{taskName}
返回按指定taskName
过滤的任务。/tasks/byPriority/{priority}
返回按指定priority
过滤的任务。
在 IntelliJ IDEA 中,点击重新运行按钮 (
) 来重新启动应用程序。
测试功能
你可以在浏览器中测试这些路由。例如,导航到 http://0.0.0.0:8080/tasks/byPriority/Medium 以 JSON 格式查看所有具有 Medium
优先级的任务:

鉴于这些类型的请求通常来自 JavaScript,更精细的测试是更优的选择。为此,你可以使用像 Postman 这样的专业工具。
在 Postman 中,创建一个新的 GET 请求,URL 为
http://0.0.0.0:8080/tasks/byPriority/Medium
。在 Headers 窗格中,将 Accept 标头的值设置为
application/json
。点击 发送 以发送请求并在响应查看器中查看响应。
在项目根目录中,创建一个新的 REST Task Manager.http 文件。
打开 REST Task Manager.http 文件并添加以下 GET 请求:
http要在 IntelliJ IDE 中发送请求,请点击其旁边的边栏图标 (
)。
这将在 Services 工具窗口中打开并运行:
在 IntelliJ IDEA Ultimate 中,你可以在 HTTP 请求文件中执行相同的步骤。
NOTE
另一种测试路由的方法是在 Kotlin Notebook 中使用 khttp 库。为 POST 请求添加路由
在之前的教程中,任务是通过 HTML 表单创建的。然而,由于你现在正在构建一个 RESTful 服务,你不再需要那样做。相反,你将利用 kotlinx.serialization
框架,它将完成大部分繁重的工作。
打开 src/main/kotlin/com/example 目录下的 Routing.kt 文件。
按如下方式向
Application.configureRouting()
函数添加一个新的 POST 路由:kotlin添加以下新的导入:
kotlin当 POST 请求发送到
/tasks
时,kotlinx.serialization
框架用于将请求正文转换为Task
对象。如果成功,任务将被添加到仓库。如果反序列化过程失败,服务器将需要处理SerializationException
,而如果任务重复,则需要处理IllegalStateException
。重新启动应用程序。
要在 Postman 中测试此功能,创建一个新的 POST 请求,URL 为
http://0.0.0.0:8080/tasks
。在 Body 窗格中,添加以下 JSON 文档以表示新任务:
json点击 发送 以发送请求。
你可以通过向 http://0.0.0.0:8080/tasks 发送 GET 请求来验证任务是否已添加。
在 IntelliJ IDEA Ultimate 中,你可以通过将以下内容添加到你的 HTTP 请求文件来执行相同的步骤:
http
添加移除支持
你已接近完成向服务添加基本操作。这些操作通常被概括为 CRUD 操作——即创建(Create)、读取(Read)、更新(Update)和删除(Delete)的缩写。现在你将实现删除操作。
在 TaskRepository.kt 文件中,在
TaskRepository
对象中添加以下方法,以根据任务名称移除任务:kotlin打开 Routing.kt 文件,并在
routing()
函数中添加一个端点来处理 DELETE 请求:kotlin重新启动应用程序。
将以下 DELETE 请求添加到你的 HTTP 请求文件:
http要在 IntelliJ IDE 中发送 DELETE 请求,请点击其旁边的边栏图标 (
)。
你将在 Services 工具窗口中看到响应:
使用 Ktor 客户端创建单元测试
到目前为止,你都是手动测试应用程序,但正如你已经注意到的那样,这种方法耗时且无法扩展。相反,你可以实现
client
对象来获取和反序列化 JSON。 打开 src/test/kotlin/com/example 目录下的 ApplicationTest.kt 文件。
用以下内容替换 ApplicationTest.kt 文件的内容:
kotlin请注意,你需要将
ContentNegotiation
和kotlinx.serialization
插件安装到插件中,与你在服务器上安装的方式相同。将以下依赖项添加到位于 gradle/libs.versions.toml 的版本目录中:
yaml将新依赖项添加到你的 build.gradle.kts 文件:
kotlin
使用 JsonPath 创建单元测试
使用 Ktor 客户端或类似库测试你的服务很方便,但从质量保证 (QA) 的角度来看,它有一个缺点。服务器不直接处理 JSON,因此无法确定其对 JSON 结构的假设。
例如,假设如下:
- 值存储在
array
中,而实际上使用了object
。 - 属性存储为
numbers
,而实际上它们是strings
。 - 成员按声明顺序序列化,而实际上并非如此。
如果你的服务旨在供多个客户端使用,那么对 JSON 结构有信心至关重要。为了实现这一点,请使用 Ktor 客户端从服务器检索文本,然后使用 JSONPath 库分析此内容。
在你的 build.gradle.kts 文件中,将 JSONPath 库添加到
dependencies
代码块:kotlin导航到 src/test/kotlin/com/example 文件夹并创建一个新的 ApplicationJsonPathTest.kt 文件。
打开 ApplicationJsonPathTest.kt 文件并添加以下内容:
kotlinJsonPath 查询的工作方式如下:
$[*].name
表示“将文档视为一个数组,并返回每个条目的 name 属性值”。$[?(@.priority == '$priority')].name
表示“返回数组中每个 priority 等于所提供值的条目的 name 属性值”。
你可以使用这些查询来确认你对返回 JSON 的理解。当你进行代码重构和服务重新部署时,即使当前的框架不会中断反序列化,序列化中的任何修改也会被识别出来。这让你能够放心地重新发布公共可用的 API。
后续步骤
恭喜!你现在已经完成了为你的任务管理器应用程序创建 RESTful API 服务,并学习了使用 Ktor 客户端和 JsonPath 进行单元测试的要点。
继续学习