创建 RESTful API
如何使用 Ktor 在 Kotlin 中创建 RESTful API
代码示例: tutorial-server-restful-api
使用的插件:
在本教程中,我们将说明如何使用 Kotlin 和 Ktor 构建后端服务,并以一个生成 JSON 文件的 RESTful API 为例。
在
您将学习如何执行以下操作:
- 创建使用 JSON 序列化的 RESTful 服务。
- 理解内容协商的过程。ContentNegotiation 插件有两个主要用途:在客户端和服务器之间协商媒体类型,以及以特定格式序列化/反序列化内容。
- 在 Ktor 中为 REST API 定义路由。
前提条件
您可以独立完成本教程,但我们强烈建议您先完成上一篇教程,以学习如何
我们建议您安装 IntelliJ IDEA,但您也可以使用其他自选 IDE。
你好,RESTful 任务管理器
在本教程中,您将把现有的任务管理器重写为 RESTful 服务。为此,您将使用多个 Ktor
虽然您可以手动将其添加到现有项目中,但生成一个新项目然后逐步添加上一篇教程中的代码会更简单。您将在过程中重新编写所有代码,因此无需手头备有之前的项目。
导航至 Ktor 项目生成器。
在 Project artifact 字段中,输入 com.example.ktor-rest-task-app 作为项目构件的名称。

在插件部分搜索并通过点击 Add 按钮添加以下插件:
- Content Negotiation
- kotlinx.serialization
- Static Content
添加插件后,您将看到项目设置下方列出的所有插件。 
点击 Download 按钮生成并下载您的 Ktor 项目。
在 IntelliJ IDEA 中打开您的项目,如之前的在 IntelliJ IDEA 中打开、探索并运行您的 Ktor 项目教程中所述。
导航至 src/main/kotlin 并创建一个名为 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 或图像——但它也接受任何其他内容类型。
内容协商插件需要找到一种格式将数据发送回浏览器。如果您查看项目中生成的代码,会在 src/main/kotlin 内找到一个名为 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。您应该可以通过点击 View The Tasks 按钮来获取数据:

在生产环境中,您通常不会直接在浏览器中显示 JSON。相反,在浏览器中运行的 JavaScript 代码会发出请求,然后将返回的数据作为单页应用程序 (SPA) 的一部分进行显示。通常,此类应用程序是使用 React、Angular 或 Vue.js 等框架编写的。
添加 GET 路由
现在您已经熟悉了内容协商的过程,请继续将
重用任务仓库
您可以无需任何修改地重用任务仓库,所以我们先来完成这一步。
在 model 包中创建一个新的 TaskRepository.kt 文件。
打开 TaskRepository.kt 并添加以下代码:
kotlin
重用 GET 请求的路由
现在您已经创建了仓库,可以实现 GET 请求的路由。之前的代码可以简化,因为您不再需要担心将任务转换为 HTML 的问题:
导航至 src/main/kotlin 中的 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 中,创建一个 URL 为
http://0.0.0.0:8080/tasks/byPriority/Medium的新 GET 请求。在 Headers 面板中,将 Accept 标头的值设置为
application/json。点击 Send 发送请求并在响应查看器中查看结果。

在项目根目录中,创建一个新的 REST Task Manager.http 文件。
打开 REST Task Manager.http 文件并添加以下 GET 请求:
http要在 IntelliJ IDEA 中发送请求,点击它旁边的装订区域图标(
)。
这将在 Services 工具窗口中打开并运行:

在 IntelliJ IDEA Ultimate 中,您可以在 HTTP 请求文件中执行相同的步骤。
NOTE
测试路由的另一种方法是在 Kotlin Notebook 中使用 khttp 库。为 POST 请求添加路由
在上一篇教程中,任务是通过 HTML 表单创建的。然而,既然您现在正在构建 RESTful 服务,就不再需要那样做了。相反,您将利用 kotlinx.serialization 框架,它将完成大部分繁重的工作。
打开 src/main/kotlin 内的 Routing.kt 文件。
向
Application.configureRouting()函数添加一个新的 POST 路由,如下所示:kotlin添加以下新导入:
kotlin当向
/tasks发送 POST 请求时,kotlinx.serialization框架被用于将请求体转换为Task对象。如果成功,任务将被添加到仓库中。如果反序列化过程失败,服务器将处理SerializationException,而如果任务重复,则处理IllegalStateException。重启应用程序。
要在 Postman 中测试此功能,请创建一个指向 URL
http://0.0.0.0:8080/tasks的新 POST 请求。在 Body 面板中,添加以下代表新任务的 JSON 文档:
json
点击 Send 发送请求。
您可以通过向 http://0.0.0.0:8080/tasks 发送 GET 请求来验证任务是否已被添加。
在 IntelliJ IDEA Ultimate 中,您可以通过在 HTTP 请求文件中添加以下内容来执行相同的步骤:
http
添加删除支持
您即将完成向服务添加基础操作的工作。这些操作通常被简称为 CRUD(创建、读取、更新和删除)操作。现在您将实现删除操作。
在 TaskRepository.kt 文件中,在
TaskRepository对象内添加以下方法以根据名称移除任务:kotlin打开 Routing.kt 文件并在
routing()函数中添加一个端点来处理 DELETE 请求:kotlin重启应用程序。
将以下 DELETE 请求添加到您的 HTTP 请求文件:
http要在 IntelliJ IDEA 中发送 DELETE 请求,点击它旁边的装订区域图标(
)。
您将在 Services 工具窗口中看到响应:

使用 Ktor Client 创建单元测试
到目前为止,您已经手动测试了应用程序,但如您所见,这种方法耗时且无法扩展。相反,您可以实现
client 对象来获取并反序列化 JSON。 打开 src/test/kotlin 内的 ServerTest.kt 文件。
将 ServerTest.kt 文件的内容替换为以下内容:
kotlin请注意,您需要将
ContentNegotiation和kotlinx.serialization插件安装到 插件中,方式与在服务器上相同。在 build.gradle.kts 文件中添加以下依赖项:
kotlin
使用 JsonPath 创建单元测试
使用 Ktor client 或类似库测试服务非常方便,但从质量保证 (QA) 的角度来看,它也有一个缺点。服务器由于不直接处理 JSON,无法确定其关于 JSON 结构的假设是否正确。
例如,此类假设包括:
- 值存储在
array(数组)中,而实际上使用了object(对象)。 - 属性存储为
numbers(数字),而实际上是strings(字符串)。 - 成员按照声明顺序序列化,而实际上并非如此。
如果您的服务旨在供多个客户端使用,那么对 JSON 结构有信心至关重要。为此,请使用 Ktor Client 从服务器检索文本,然后使用 JSONPath 库分析此内容。
在您的 build.gradle.kts 文件的
dependencies块中添加 JSONPath 库:kotlin导航至 src/test/kotlin 文件夹并创建一个新的 ApplicationJsonPathTest.kt 文件。
打开 ApplicationJsonPathTest.kt 文件并向其中添加以下内容:
kotlinJsonPath 查询的工作原理如下:
$[*].name表示“将文档视为数组并返回每个条目的 name 属性值”。$[?(@.priority == '$priority')].name表示“返回数组中优先级等于所提供值的每个条目的 name 属性值”。
您可以使用此类查询来确认您对返回 JSON 的理解。当您进行代码重构和服务重新部署时,即使序列化中的任何修改不会破坏当前框架的反序列化,它们也会被识别出来。这让您能够放心地重新发布公开可用的 API。
后续步骤
恭喜!您现在已完成为任务管理器应用程序创建 RESTful API 服务,并学习了使用 Ktor Client 和 JsonPath 进行单元测试的细节与要点。
继续阅读
