<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>5.1.10</version>
</dependency>
Vert.x Web Client 响应式 Web 客户端
Vert.x Web Client 是一个专为 Vert.x 打造的异步非阻塞 HTTP 与 HTTP/2 客户端。
Web Client 极大地简化了与外部 HTTP 服务器之间的请求/响应交互,并提供了大量高级实用特性:
-
自动 JSON 请求体编码与响应体反序列化
-
流式请求/响应管道传输(Pumping)
-
链式流畅的请求参数追加
-
统一的强类型异常与断言拦截(Expectations)
-
各种形式的表单与大文件流式提交(Multipart)
Web Client 并没有废弃 Vert.x Core 的原生 HttpClient,事实上它正是基于 HttpClient 构建的高级封装,继承了其完备的底层能力,如高性能连接池管理、HTTP/2 多路复用、HTTP Pipelining 等。
当需要对 HTTP 报文进行极其微观、低阶的控制时,可以使用底层的 HttpClient。
Web Client 本身不提供 WebSocket API,若需使用 WebSocket,请直接使用 Vert.x Core 的 HttpClient。
使用 Web Client
在项目构建配置中添加以下依赖:
-
Maven(在
pom.xml中):
-
Gradle(在
build.gradle文件中):
dependencies {
compile 'io.vertx:vertx-web-client:5.1.10'
}
回顾 Vert.x Core HTTP 客户端
Vert.x Web Client 深度基于 Vert.x Core 的底层能力构建。如果你此前未接触过 HttpClient,建议先行查阅 Core 的 HTTP 文档。
创建 Web Client 实例
使用默认配置创建 WebClient 实例:
WebClient client = WebClient.create(vertx);
如需定制高级选项(如连接超时、User-Agent 等):
WebClientOptions options = new WebClientOptions()
.setUserAgent("My-App/1.2.3");
options.setKeepAlive(false);
WebClient client = WebClient.create(vertx, options);
WebClientOptions 继承自 HttpClientOptions,因此可以自由配置底层的各种连接池大小、SSL 凭证等属性。
若应用中已经存在现成的 HttpClient 实例,也可以直接将其包装为 WebClient:
WebClient client = WebClient.wrap(httpClient);
| 在绝大多数生产场景中,WebClient 应当在应用程序启动时全局创建一次并持久复用。否则你将失去底层连接池复用带来的巨大性能红利,并且若未妥善关闭还会导致套接字资源泄漏。 |
发起 HTTP 请求
无请求体的简单请求
在实际调用中,通常会发起不带请求体的简单请求(如 HTTP GET、OPTIONS 或 HEAD):
WebClient client = WebClient.create(vertx);
// 发起 GET 请求
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(response -> System.out
.println("收到响应,状态码: " + response.statusCode()))
.onFailure(err ->
System.out.println("请求发生异常: " + err.getMessage()));
// 发起 HEAD 请求
client
.head(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(response -> System.out
.println("收到响应,状态码: " + response.statusCode()))
.onFailure(err ->
System.out.println("请求发生异常: " + err.getMessage()));
你可以以链式调用的方式向请求 URI 中动态追加查询参数:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.addQueryParam("param", "param_value")
.send()
.onSuccess(response -> System.out
.println("收到响应,状态码: " + response.statusCode()))
.onFailure(err ->
System.out.println("请求发生异常: " + err.getMessage()));
URI 路径中自带的查询参数会被自动预解析:
HttpRequest<Buffer> request = client
.get(
8080,
"myserver.mycompany.com",
"/some-uri?param1=param1_value¶m2=param2_value");
// 追加 param3
request.addQueryParam("param3", "param3_value");
// 覆盖已有 param2 的取值
request.setQueryParam("param2", "another_param2_value");
如果显式重新设置 uri(…),此前已存在的查询参数会被全部重置。
写入请求体
当需要发送带有负载的请求时,调用对应的 sendXXX 方法即可:
使用 sendBuffer 发送二进制 Buffer:
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendBuffer(buffer)
.onSuccess(res -> {
// 成功发送
});
针对大文件或高并发场景,为避免一次性将整个文件读入内存造成内存溢出,Web Client 允许通过 sendStream 直接以反应式流(如 AsyncFile)流式发送:
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendStream(stream)
.onSuccess(res -> {
// 成功发送
});
Web Client 会自动建立流传输管道。若流的总长度未知,默认会采用 HTTP 分块传输编码(Chunked Transfer Encoding)。如果预先已知体积,可显式设置 content-length 标头以避免 chunked。
发送 JSON 请求体
发送 JsonObject 时使用 sendJsonObject:
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendJsonObject(
new JsonObject()
.put("firstName", "Dale")
.put("lastName", "Cooper"))
.onSuccess(res -> {
// 发送成功
});
也可以通过 sendJson 直接传入任意普通 Java POJO 对象,底层会自动调用 Jackson 进行序列化:
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendJson(new User("Dale", "Cooper"))
.onSuccess(res -> {
// 发送成功
});
表单提交与文件上传
通过 sendForm 提交常规表单数据:
MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");
// 默认以 application/x-www-form-urlencoded 格式提交
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendForm(form)
.onSuccess(res -> {
// 提交成功
});
如需上传文件与复杂属性,使用 MultipartForm 配合 sendMultipartForm:
MultipartForm form = MultipartForm.create()
.attribute("imageDescription", "一张高清美图")
.binaryFileUpload(
"imageFile",
"image.jpg",
"/path/to/image",
"image/jpeg");
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendMultipartForm(form)
.onSuccess(res -> {
// 成功上传
});
设置 HTTP 请求头
可以通过 MultiMap 批量管理请求头,或使用 putHeader:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri");
request.putHeader("content-type", "application/json");
request.putHeader("other-header", "foo");
请求认证配置 (Authentication)
支持快捷注入 HTTP 认证凭据:
对于 HTTP Basic 认证:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.authentication(new UsernamePasswordCredentials("myid", "mypassword"));
对于 OAuth 2.0 / JWT Bearer 令牌认证:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.authentication(new TokenCredentials("myBearerToken"));
请求模板复用
send 方法可以安全地被多次触发调用。但请注意 HttpRequest 实例内部是可变的,若需在已配置的请求模板上派生新请求,请调用 copy() 进行克隆:
HttpRequest<Buffer> get = client
.get(8080, "myserver.mycompany.com", "/some-uri");
get.send().onSuccess(res -> { ... });
// 基于克隆副本作差异化修改并发送,原始 get 对象不受污染
get.copy()
.putHeader("a-header", "with-some-value")
.send();
超时控制 (Timeouts)
支持配置建立连接超时(Connect Timeout)、请求空闲超时(Idle Timeout)以及全局综合超时(Timeout):
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.timeout(5000) // 5 秒全局超时
.send()
.onSuccess(res -> {
// 成功收到响应
})
.onFailure(err -> {
// 若因超时触发,cause 类型为 java.util.concurrent.TimeoutException
});
处理 HTTP 响应
发送请求后,返回的结果由 HttpResponse 承载。
|
默认情况下,只有在发生底层网络或套接字异常时,Vert.x Web Client 的 Future 才会进入 |
响应体解码 (Decoding responses)
默认返回原始二进制 Buffer。通过 BodyCodec 可自动将其解码为目标业务类型:
-
解码为纯文本 String
-
解码为
JsonObject -
解码为 Java POJO 强类型实体类
-
流式管道推送至
WriteStream(如直接落盘为文件)
反序列化为 POJO 示例:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.json(User.class))
.send()
.onSuccess(res -> {
User user = res.body();
System.out.println("用户姓名: " + user.getFirstName() + " " + user.getLastName());
});
直接流式落盘保存到文件(避免内存堆积):
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.pipe(fileWriteStream))
.send();
SSE 服务端事件流 (Server-Sent Events)
通过 BodyCodec.sseStream 可以将长连接的 SSE 事件流直接解码为持续接收事件的响应流:
client.get("/events").as(BodyCodec.sseStream(stream -> {
stream.handler(event -> System.out.println("收到 SSE 事件: " + event));
stream.endHandler(v -> System.out.println("SSE 流结束"));
})).send().expecting(HttpResponseExpectation.SC_OK);
响应断言机制 (Response expectations)
通过设置断言(Expectations),可以让 Web Client 在响应状态码或返回的 Content-Type 不符合预期时自动触发失败:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
// 要求状态码必须为 2xx,且 Content-Type 必须为 application/json
.expecting(HttpResponseExpectation.SC_SUCCESS.and(HttpResponseExpectation.JSON))
.onSuccess(res -> {
// 此时已安全保证为 200 JSON 响应
JsonObject body = res.bodyAsJsonObject();
})
.onFailure(err -> {
// 当服务端返回 404/500 或非 JSON 时自动进入此处
});
客户端负载均衡 (Client side load balancing)
当域名解析出多个目标 IP 时,Web Client 默认使用首个 IP。 你可以配置开启客户端负载均衡策略:
WebClient client = WebClient.wrap(vertx
.httpClientBuilder()
.withLoadBalancer(LoadBalancer.ROUND_ROBIN)
.build());
内置策略包括:轮询(Round-robin)、最少活跃请求(Least requests)、两选择幂(Power of two choices)以及一致性哈希(Consistent hashing)。
HTTP 响应客户端缓存 (CachingWebClient)
通过使用 CachingWebClient,可以为客户端赋予符合 HTTP 规范的响应缓存能力:
WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);
默认对 200、301、404 的 GET 响应进行缓存,支持自定义缓存条目上限与第三方存储介质(CacheStore SPI)。
URI 模板化支持 (RFC 6570)
支持 RFC 6570 标准的 URI 模板语法,自动进行安全的变量转义与格式化展开:
UriTemplate template = UriTemplate.of("/convert?{amount}&{currency}");
client.get(template)
.setTemplateParam("amount", "100")
.setTemplateParam("currency", "EUR")
.send();
WebClientSession 会话与 Cookie 自动管理
在需要维持类似浏览器 Cookie 会话的场景下,使用 WebClientSession 代替普通 WebClient:
WebClient client = WebClient.create(vertx);
WebClientSession session = WebClientSession.create(client);
// 发送登录请求后,服务端下发的 Cookie 会被 session 自动保存
// 后续请求会自动带上 Cookie 保持会话
session.get("/user/profile").send();
OAuth2 自动令牌管理 (OAuth2WebClient)
使用 OAuth2WebClient 能够全自动管理 OAuth2 Access Token 的下发、鉴权头自动注入,以及在令牌过期(或遇到 401 时)自动触发刷新重试:
OAuth2WebClient oauth2Client = OAuth2WebClient.create(
baseClient,
oAuth2Auth,
new OAuth2WebClientOptions()
.setRenewTokenOnForbidden(true)); // 遇到 401 自动刷新 Token 并静默重试
Unix Domain Sockets 本地套接字支持
Web Client 原生支持通过 Unix Domain Socket 进行高性能跨进程通信(如直接与本机的 /var/run/docker.sock Docker 守护进程通信):
SocketAddress serverAddress = SocketAddress
.domainSocketAddress("/var/run/docker.sock");
client
.request(
HttpMethod.GET,
serverAddress,
8080,
"localhost",
"/images/json")
.as(BodyCodec.jsonObject())
.send()
.expecting(HttpResponseExpectation.SC_ACCEPTED)
.onSuccess(res -> System.out.println("本地 Docker 镜像列表: " + res.body()));