<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>web-and-openapi-howto</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<vertx.version>4.0.0</vertx.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-openapi</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration>
<mainClass>io.vertx.howtos.openapi.APIVerticle</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
基于 OpenAPI 契约构建 Web 路由器
本指南将向你展示如何基于 OpenAPI 3 规范契约文档,自动化构建具备请求参数校验与类型提取能力的 Vert.x Web Router(路由分发器)。
你将构建什么
你将构建一个 Vert.x 应用程序,在内存中维护一组宠物(Pets)列表,并通过标准的 Petstore API 接口向外提供服务:https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml
前置要求
-
文本编辑器或 IDE
-
Java 8 或更高版本
-
Maven
创建工程项目
以下是该项目所使用的 pom.xml 构建配置文件内容:
pom.xml加载 OpenAPI 规范契约
Vert.x Web API Contract 模块提供了 RouterBuilder,帮助你直接从 OpenAPI 规范文件起步组装 Vert.x Web Router。
在 Verticle 的 start 生命周期方法中加载规范文件:
RouterBuilder.create(this.vertx, "petstore.yaml")
.onSuccess(routerBuilder -> { (1)
// You can start building the router using routerBuilder
}).onFailure(cause -> { (2)
// Something went wrong during router factory initialization
startPromise.fail(cause);
});
| 1 | 若规范文件解析成功,你将获得一个可直接使用的 RouterBuilder 实例; |
| 2 | 若加载解析失败,则中止 Verticle 的部署生命周期。 |
编写路由处理器
现在,你可以通过 operation(operationId).handler() 将具体业务逻辑挂载到 OpenAPI 操作对应的路由处理器上。
针对 listPets 操作(查询宠物列表):
routerBuilder.operation("listPets").handler(routingContext ->
routingContext
.response() (1)
.setStatusCode(200)
.putHeader(HttpHeaders.CONTENT_TYPE, "application/json") (2)
.end(new JsonArray(getAllPets()).encode()) (3)
);
| 1 | 获取响应对象; |
| 2 | 写入 Content-Type: application/json 响应头; |
| 3 | 将所有宠物的 JSON 列表序列化并写入响应正文。 |
针对 createPets 操作(新增宠物):
routerBuilder.operation("createPets").handler(routingContext -> {
RequestParameters params = routingContext.get(ValidationHandler.REQUEST_CONTEXT_KEY); (1)
JsonObject pet = params.body().getJsonObject(); (2)
addPet(pet);
routingContext
.response()
.setStatusCode(200)
.end(); (3)
});
| 1 | 获取由契约校验器完成解析的参数容器; |
| 2 | 提取经校验合规的请求体(Body); |
| 3 | 返回 200 空响应,表示创建成功。 |
针对 showPetById 操作(按 ID 查询单个宠物):
routerBuilder.operation("showPetById").handler(routingContext -> {
RequestParameters params = routingContext.get("parsedParameters"); (1)
Integer id = params.pathParameter("petId").getInteger(); (2)
Optional<JsonObject> pet = getAllPets()
.stream()
.filter(p -> p.getInteger("id").equals(id))
.findFirst(); (3)
if (pet.isPresent())
routingContext
.response()
.setStatusCode(200)
.putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.end(pet.get().encode()); (4)
else
routingContext.fail(404, new Exception("Pet not found")); (5)
});
| 1 | 获取解析后的参数容器; |
| 2 | 提取类型安全的路径参数; |
| 3 | 在内存数据中匹配目标宠物; |
| 4 | 若宠物存在,将其以 JSON 形式写回客户端; |
| 5 | 若宠物不存在,将 RoutingContext 标记失败并触发 404 状态码。 |
生成路由器与全局异常处理
所有操作处理器声明完成后,即可生成 Router 并挂载全局 "404 Not Found" 与 "400 Bad Request" 错误拦截:
Router router = routerBuilder.createRouter(); (1)
router.errorHandler(404, routingContext -> { (2)
JsonObject errorObject = new JsonObject() (3)
.put("code", 404)
.put("message",
(routingContext.failure() != null) ?
routingContext.failure().getMessage() :
"Not Found"
);
routingContext
.response()
.setStatusCode(404)
.putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.end(errorObject.encode()); (4)
});
router.errorHandler(400, routingContext -> {
JsonObject errorObject = new JsonObject()
.put("code", 400)
.put("message",
(routingContext.failure() != null) ?
routingContext.failure().getMessage() :
"Validation Exception"
);
routingContext
.response()
.setStatusCode(400)
.putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.end(errorObject.encode());
});
server = vertx.createHttpServer(new HttpServerOptions().setPort(8080).setHost("localhost")); (5)
server.requestHandler(router).listen(); (6)
| 1 | 从 RouterBuilder 正式构建出 Vert.x Web Router 实例; |
| 2 | 挂载 404 找不到资源的全局错误处理器; |
| 3 | 组装包含异常详情的 JSON 错误对象; |
| 4 | 将结构化错误信息输出至客户端; |
| 5 | 实例化底层的 Vert.x HttpServer; |
| 6 | 将组装好的 Router 挂载到 HttpServer 上并开始监听网络请求。 |
完整源码
你可以在该指南的 GitHub 仓库中查阅 APIVerticle 的完整源代码。
运行与接口测试
APIVerticle 已自带 main 方法入口,可以直接启动:
-
创建
Vertx运行时上下文; -
部署
APIVerticle。
运行方式:
-
在你的 IDE 中直接右键运行
APIVerticle类; -
或者在终端使用 Maven 启动:
mvn compile exec:java。
服务启动后,你可以使用 curl 对各类契约场景进行验证:
# 1. 获取初始宠物列表
$ curl http://localhost:8080/pets
[{"id":1,"name":"Fufi","tag":"ABC"},{"id":2,"name":"Garfield","tag":"ABC"},{"id":3,"name":"Puffa","tag":"ABC"}]
# 2. 按 ID 成功查询宠物
$ curl http://localhost:8080/pets/3
{"id":3,"name":"Puffa","tag":"ABC"}
# 3. 查询不存在的 ID 返回 404
$ curl http://localhost:8080/pets/5
{"code":404,"message":"Pet not found"}
# 4. 创建新宠物
$ curl -X POST -H "Content-type: application/json" --data '{"id":4,"name":"Alan"}' http://localhost:8080/pets
# 5. 缺少必填字段触发 OpenAPI 契约校验失败(返回 400)
$ curl -X POST -H "Content-type: application/json" --data '{"id":4}' http://localhost:8080/pets
{"code":400,"message":"$.name: is missing but it is required"}
# 6. 验证新宠物已落库
$ curl http://localhost:8080/pets
[{"id":1,"name":"Fufi","tag":"ABC"},{"id":2,"name":"Garfield","tag":"ABC"},{"id":3,"name":"Puffa","tag":"ABC"},{"id":4,"name":"Alan"}]
本文总结
本指南向你阐明了:
-
如何从 OpenAPI 规范契约自动生成 Vert.x Web 路由器;
-
如何提取并信任已通过验证的请求参数;
-
如何格式化输出 JSON 响应;
-
如何配置全局统一的错误处理策略;
-
如何启动 HTTP 服务器并将契约路由投入运行。