随着 Vert.x 进化到 3.5.0,本系列也迎来了新篇章。
对于 CORS,搞 Web 开发(不论你是前端,还是后端)的同志应该不陌生,尤其是如今微服务盛行的时代,CORS 更是最常用的配置项之一。假若你对此还有一点点的疑问,不用问,你已经落伍了!
Vert.x 很早就支持了 CORS,但到了 3.5.0,出于安全性上的考虑,它增加了一个限制:
当 allowedOriginPattern 为“*”时,allowCredentials 不允许为“true”。
这个限制其实起源自协议的限制:
The string “_” cannot be used for a resource that supports credentials.The string “
_
” cannot be used for a resource that supports credentials.
即:Access-Control-Allow-Origin 为“*”时,Access-Control-Allow-Credentials 会被忽略,Credentials 信息(一般是 Cookie)也不会被 user-agent 发送。大家可以参考这篇文章(英文原版)了解如何安全地设置各个 header。
在 3.5.0 时,当 allowedOriginPattern 为“*”和 allowCredentials 为“true”时,Vert.x 会产生一个异常,若不处理会导致 Verticle 不能正常启动。在之前的版本中,不会出现这种情况。
断路器是个好东东,强烈建议在框架设计上就考虑它,以避免粗心的开发者写的代码扰乱其他好代码。在我写dgate和dfx时,对所有对外暴露的服务也都强加了这个限制。
既然 CB 这么好,是不是动过将其应用到普通 Handler 上的念头?不过由于Vert.x CB 的文档示例中给出的都是清一色的 HTTP 请求的例子,要想将 CB 推而广之应用到普通的 Handler 上还得费点气力。
public static void withCircuitBreaker(Vertx vertx, CircuitBreaker circuitBreaker
, Accessible accessible, Map params, Handler<Map> successHandler, Handler<Throwable> failureHandler) {
circuitBreaker.execute(future ->
vertx.executeBlocking(f -> {
try {
f.complete(accessible.invoke(params));
} catch (Throwable throwable) {
f.fail(throwable);
}
}
, result -> {
if (result.succeeded()) {
future.complete(result.result());
} else {
future.fail(result.cause());
}
})
).setHandler(result -> {
if (result.succeeded()) {
successHandler.handle((Map) result.result());
} else {
logger.error("CB[{}] execution failed, cause: ", circuitBreaker.name(), result.cause());
failureHandler.handle(result.cause());
}
});
}
它的使用很简单:
Utils.withCircuitBreaker(vertx, circuitBreaker, accessible, body.getMap()
, result -> Utils.fireJsonResponse(response, 200, result)
, throwable -> Utils.fireSingleMessageResponse(response, 500, throwable.getMessage()));
各位可依葫芦画瓢将其改为自己的形式,其中的重点在于以“vertx.executeBlocking”方式来执行 Handler 的代码。
在稍微正规的队伍中,自动化测试应该已经是一个标准实践了,并且 Vert.x 对于测试也提供了支持。
不过,我不喜欢。原因有几个:
为了证明我所言非虚,大家可以去看看dgate和dfx的测试代码。这里给出两个例子:
都是基于 Spock 写的,各位可以体验其酸爽度。
在本系列第一篇里,我就提出了一个钟意的工程结构组成,但里面没有提到“运行时外部配置文件”这一常见的实践。
经过若干项目的锤炼之后,目前对于这种运行时的外部配置文件,我基本形成了一个固定套路:Groovy DSL + Groovy ConfigSlurper。它俩简直是完成这一任务的绝配,比起 Vert.x 的提供的 json 配置文件要爽太多。
看看 Gradle 的 build 文件,你就可以知道这种 DSL 的灵活度可以到什么程度,更何况 Groovy 的语法对于 Java 开发者极其友好。
或许有人会觉得 Groovy 不酷,甚至有点鄙夷,言必称 Scala、Clojure、Kotlin、Go 或者 Rust。对此,哥只想说:作为开发者,最让人鄙夷的是交不出活,客户才不关心你用什么语言呢!
dgate是我写的一个基于 vertx 的轻量级网关,所有的配置全部通过配置文件来定义,无需数据库。关于它的详细介绍,可以参见其文档。为了说明上一节采用 Groovy DSL 的灵活度,这里展示几个 dgate 的配置例子。
利用 dgate 的 mock 功能,分离开的前后端开发人员可以并行工作,只需将 mock 响应配置到 dgate 的配置文件中就好。
"/summary" {
expected {
statusCode = 200
payload {
eqLocations = []
opRateInLast30Days = []
myOrgs = [
["name": "org1", "admin": false]
]
}
}
}
动态 Mock 为那些返回动态结果(如某些情况下成功,某些情况下失败)的 URL 模拟提供了便利。
"/login" {
required = ["sub", "password"]
methods = [HttpMethod.GET, HttpMethod.POST]
expected {
statusCode = 200
payload = {
JWTAuth jwtAuth = Utils.createAuthProvider(Vertx.vertx())
JWTTokenGenerator tokenGenerator = new JWTTokenGenerator(jwtAuth)
[token: tokenGenerator.token(["sub": "……", "name": "……", "role": "normal"], 200)]
}
}
}
上例将模拟一个实际的动态 JWT,这样的好处在于,方便前端/移动端开发直接去完成刷新 token 或重新登录的过程,而不需要再针对此场景做其他特殊处理。
请注意,为了产生动态结果,此处的 payload 使用的是闭包,而不像前一例用的是 Map。此时,闭包的返回值为 Mock 响应的结果。
以上的例子已经充分展现了将 Groovy DSL 作为运行时外部配置的能力,可以说是完胜 Vert.x 自带的 json 配置方式。
好啦,本期内容就此结束,请保持关注,期待下期继续!
觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :
付费文章