Vert.x Web Client
Vert.x Web Client 是一个异步的 HTTP 和 HTTP/2 客户端。
Web Client使得发送 HTTP 请求以及从 Web 服务器接收 HTTP 响应变得更加便捷,同时提供了额外的高级功能, 例如:
-
Json body的编码和解码
-
请求和响应泵
-
请求参数的处理
-
统一的错误处理
-
提交表单
制作Web Client的目的并非为了替换Vert.x Core中的 HttpClient
,而是基于该客户端,
继承其配置和强大的功能,例如请求连接池(Pooling),HTTP/2的支持,流水线/管线的支持等。
当您需要对 HTTP 请求和响应做细微粒度控制时,
您应当使用 HttpClient
。
另外Web Client并未提供 WebSocket API,此时您应当使用 Vert.x Core的 HttpClient
。
目前还无法处理cookies。
使用Web Client
如需使用Vert.x Web Client,请先加入以下依赖,到您的build描述 dependencies 部分 :
-
Maven (在您的
pom.xml
文件内):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>4.3.8</version>
</dependency>
-
Gradle (在您的
build.gradle
文件内):
dependencies {
compile 'io.vertx:vertx-web-client:4.3.8'
}
回顾 Vert.x Core的 HTTP Client
Vert.x Web Client使用Vert.x core的API,如果您对此还不熟悉,熟悉基于 Vert.x core的
HttpClient
基本概念是很有价值的。
创建Web Client
您可以创建一个缺省 WebClient
实例:
WebClient client = WebClient.create(vertx);
您还可以使用配置项来创建客户端:
WebClientOptions options = new WebClientOptions()
.setUserAgent("My-App/1.2.3");
options.setKeepAlive(false);
WebClient client = WebClient.create(vertx, options);
Web Client配置项继承自 HttpClient
配置项,您可以设置其中任何一个项。
如果已在程序中创建 HttpClient
,可用以下方式复用:
WebClient client = WebClient.wrap(httpClient);
重要
|
在大多数情况下,一个Web Client 应该在应用程序启动时创建,并重用它。 否则,您将失去很多好处,例如连接池。如果实例未正确关闭,则可能会资源泄漏。 |
发送请求
无请求体的简单请求
通常,您想发送一个无请求体的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("Received response with status code" + response.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
// 发送HEAD请求
client
.head(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(response -> System.out
.println("Received response with status code" + response.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
您可用以下链式方式向请求URI添加查询参数
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.addQueryParam("param", "param_value")
.send()
.onSuccess(response -> System.out
.println("Received response with status code" + response.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + 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将会自动清除已有的查询参数
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri");
// 添加 param1
request.addQueryParam("param1", "param1_value");
// 覆盖 param1 并添加 param2
request.uri("/some-uri?param1=param1_value¶m2=param2_value");
填充请求体
如需要发送请求体,可使用相同的API,并在最后加上 sendXXX
方法,
发送相应的请求体。
使用 sendBuffer
发送一个buffer body
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendBuffer(buffer)
.onSuccess(res -> {
// OK
});
发送single buffer很有用,但是通常您不想完全将内容加载到内存中,
因为它可能太大,或者您想同时处理多个请求,或者每个请求只想使用最小的(消耗)。
为此,Web Client可以使用 ReadStream<Buffer>
的(例如
AsyncFile
是一个ReadStream<Buffer>) sendStream
方法发送。
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendStream(stream)
.onSuccess(res -> {
// OK
});
Web Client负责为您设置泵传输(transfer pump)。 如果流长度未知则使用分块传输(chunked transfer)编码。
当您知道流的大小,您应该在HTTP header中设置 content-length
fs.open("content.txt", new OpenOptions(), fileRes -> {
if (fileRes.succeeded()) {
ReadStream<Buffer> fileStream = fileRes.result();
String fileLen = "1024";
// 用POST方法发送文件
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.putHeader("content-length", fileLen)
.sendStream(fileStream)
.onSuccess(res -> {
// OK
})
;
}
});
这个POST方法不会被分块传输。
JSON bodies
有时您需要发送JSON body请求, 可使用 sendJsonObject
发送一个 JsonObject
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendJsonObject(
new JsonObject()
.put("firstName", "Dale")
.put("lastName", "Cooper"))
.onSuccess(res -> {
// OK
});
在Java,Groovy以及Kotlin中,您可以使用 sendJson
方法,
它使用 Json.encode
方法映射一个 POJO(Plain Old Java Object)
到一个 Json 对象
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendJson(new User("Dale", "Cooper"))
.onSuccess(res -> {
// OK
});
注意
|
Json.encode 方法使用Jackson mapper将 POJO 编码成
JSON。
|
表单提交
您可以使用 sendForm
的变体发送http表单提交。
MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");
// 用URL编码方式提交表单
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendForm(form)
.onSuccess(res -> {
// OK
});
默认情况下,提交表单header中的 content-type
属性值为 application/x-www-form-urlencoded
,
您还可将其替换为 multipart/form-data
:
MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");
// 提交multipart form表单
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.putHeader("content-type", "multipart/form-data")
.sendForm(form)
.onSuccess(res -> {
// OK
});
如果您想上传文件的同时发送属性,您可以创建一个 MultipartForm
,然后使用
sendMultipartForm
。
MultipartForm form = MultipartForm.create()
.attribute("imageDescription", "a very nice image")
.binaryFileUpload(
"imageFile",
"image.jpg",
"/path/to/image",
"image/jpeg");
// 提交multipart form表单
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendMultipartForm(form)
.onSuccess(res -> {
// OK
});
填充请求头
您可使用headers的multi-map 填充请求头:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri");
MultiMap headers = request.headers();
headers.set("content-type", "application/json");
headers.set("other-header", "foo");
此处 Headers 是一个 MultiMap
实例,提供了添加、
设置以及删除头属性操作的入口。HTTP headers允许某个特定的key有多个值。
您还可使用 putHeader 写入headers属性:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri");
request.putHeader("content-type", "application/json");
request.putHeader("other-header", "foo");
配置请求以添加身份验证
可以通过设置正确的 headers 来手动执行身份验证,或我们的预定义方法 (我们强烈建议启用HTTPS,尤其是对于经过身份验证的请求):
在基本的HTTP身份验证中,请求包含以下形式的表单header字段 Authorization: Basic <credentials>
,
credentials是base64编码的,由冒号连接的id和密码。
您可以像下面这样配置请求以添加基本访问验证:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.authentication(new UsernamePasswordCredentials("myid", "mypassword"));
在OAuth 2.0种,请求包含以下形式的表单header字段 Authorization: Bearer <bearerToken>
,bearerToken是授权服务器发布的,
用于访问受保护资源的不记名令牌。
您可以像下面这样配置请求,以添加bearer token访问验证:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.authentication(new TokenCredentials("myBearerToken"));
重用请求
send
方法可被安全的重复多次调用,
这使得它可以很容易的配置以及重用 HttpRequest
对象
HttpRequest<Buffer> get = client
.get(8080, "myserver.mycompany.com", "/some-uri");
get
.send()
.onSuccess(res -> {
// OK
});
// 又一些请求
get
.send()
.onSuccess(res -> {
// OK
});
不过要当心 HttpRequest
实例是可变的(mutable).
因此,您应该在修改已被缓存了的实例之前,使用 copy
方法。
HttpRequest<Buffer> get = client
.get(8080, "myserver.mycompany.com", "/some-uri");
get
.send()
.onSuccess(res -> {
// OK
});
// "get" 请求实例保持未修改
get
.copy()
.putHeader("a-header", "with-some-value")
.send()
.onSuccess(res -> {
// OK
});
超时
您可通过 timeout
。方法设置超时时间。
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.timeout(5000)
.send()
.onSuccess(res -> {
// OK
})
.onFailure(err -> {
// 当是由java.util.concurrent.TimeoutException导致时,或许是一个超时
});
若请求在设定时间内没有返回任何数据, 则一个异常将会传递给响应处理器。
处理HTTP响应
Web Client 请求发送之后,您总是在单个 HttpResponse
中处理单个异步结果 。
当响应被成功接收到之后,相应的回调函数将会被调用。
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
小心
|
默认状况下,仅当在网络级别发生错误时,Vert.x Web Client 请求才以错误结尾。
换言之, |
警告
|
响应会被完全缓冲,请使用 BodyCodec.pipe
将响应接入写入流。
|
响应解码
缺省状况下,Web Client提供一个response body作为 Buffer
,
并且未运用任何解码器。
可以使用 BodyCodec
实现以下自定义response body解码:
-
文本字符串
-
Json 对象
-
Json 映射的 POJO
一个body解码器可以将任意二进制数据流解码为特定的对象实例, 从而节省了您自己在响应处理器里解码的步骤。
使用 BodyCodec.jsonObject
解码一个 Json 对象:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.jsonObject())
.send()
.onSuccess(res -> {
JsonObject body = res.body();
System.out.println(
"Received response with status code" +
res.statusCode() +
" with body " +
body);
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
在Java,Groovy以及Kotlin中,可以自定义Json映射POJO解码:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.json(User.class))
.send()
.onSuccess(res -> {
User user = res.body();
System.out.println(
"Received response with status code" +
res.statusCode() +
" with body " +
user.getFirstName() +
" " +
user.getLastName());
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
如果返回数据非常大,则应该使用 BodyCodec.pipe
方法。
这个编码器将响应缓存泵入到 WriteStream
中,
并且在异步结果响应中,发出操作成功或失败的信号。
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.pipe(writeStream))
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
经常会看到API返回一个JSON对象流。例如,Twitter API可以提供一个推文回馈。
处理这个情况,您可以使用 BodyCodec.jsonStream
。
传递一个JSON解析器,该解析器从HTTP响应中开始读取JSON流。
JsonParser parser = JsonParser.newParser().objectValueMode();
parser.handler(event -> {
JsonObject object = event.objectValue();
System.out.println("Got " + object.encode());
});
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.jsonStream(parser))
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
最后,如您对响应结果不感兴趣,可用 BodyCodec.none
简单的丢弃响应体。
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.as(BodyCodec.none())
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
若无法预知响应内容类型,您依旧可以在获取结果之后,用 bodyAsXXX()
方法将其转换成指定的类型
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(res -> {
// 将结果解码为Json对象
JsonObject body = res.bodyAsJsonObject();
System.out.println(
"Received response with status code" +
res.statusCode() +
" with body " +
body);
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
警告
|
这种方式仅对响应结果为buffer有效。 |
响应谓词
默认的,仅当在网络级别发生错误时,Vert.x Web Client请求才以错误结尾。
换言之, 您必须在收到响应后手动执行健全性检查:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(res -> {
if (
res.statusCode() == 200 &&
res.getHeader("content-type").equals("application/json")) {
// 将结果解码为Json对象
JsonObject body = res.bodyAsJsonObject();
System.out.println(
"Received response with status code" +
res.statusCode() +
" with body " +
body);
} else {
System.out.println("Something went wrong " + res.statusCode());
}
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
您可以灵活的替换成清晰简明的 response predicates 。
Response predicates
当响应不符合条件会使请求失败。
Web Client附带了一组开箱即用的谓词,可供使用:
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.expect(ResponsePredicate.SC_SUCCESS)
.expect(ResponsePredicate.JSON)
.send()
.onSuccess(res -> {
// 安全地将body解码为json对象
JsonObject body = res.bodyAsJsonObject();
System.out.println(
"Received response with status code" +
res.statusCode() +
" with body " +
body);
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
当现有谓词不满足您的需求时,您还可以创建自定义谓词:
Function<HttpResponse<Void>, ResponsePredicateResult> methodsPredicate =
resp -> {
String methods = resp.getHeader("Access-Control-Allow-Methods");
if (methods != null) {
if (methods.contains("POST")) {
return ResponsePredicateResult.success();
}
}
return ResponsePredicateResult.failure("Does not work");
};
// 发送预检CORS请求
client
.request(
HttpMethod.OPTIONS,
8080,
"myserver.mycompany.com",
"/some-uri")
.putHeader("Origin", "Server-b.com")
.putHeader("Access-Control-Request-Method", "POST")
.expect(methodsPredicate)
.send()
.onSuccess(res -> {
// 立即处理POST请求
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
提示
|
响应谓词是在收到响应体 之前 对其进行评估。 因此, 您无法在谓词测试函数中检查response body。 |
预定义谓词
为了方便起见,Web Client附带了一些常见用例的谓词。
对于状态码,例如 ResponsePredicate.SC_SUCCESS
,
验证响应具有 2xx
代码,您也可以自定义创建一个
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.expect(ResponsePredicate.status(200, 202))
.send()
.onSuccess(res -> {
// ....
});
对于content types,例如 ResponsePredicate.JSON
,
验证响应具有JSON数据,您也可以自定义创建一个
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.expect(ResponsePredicate.contentType("some/content-type"))
.send()
.onSuccess(res -> {
// ....
});
请参考 ResponsePredicate
文档获取预定义谓词的完整列表。
创建自定义失败
默认情况下,响应谓词(包括预定义的)使用默认的错误转换器, 它将丢弃body并传递一条简单消息。您可以通过自定义异常类来替换错误转换器:
ResponsePredicate predicate = ResponsePredicate.create(
ResponsePredicate.SC_SUCCESS,
result -> new MyCustomException(result.message()));
许多Web API在错误响应中提供了详细信息。 例如, Marvel API 使用此JSON对象格式:
{
"code": "InvalidCredentials",
"message": "The passed API key is invalid."
}
为避免丢失此信息,在错误发生之前, 可以在转换器被调用之前等待响应body被完全接收:
ErrorConverter converter = ErrorConverter.createFullBody(result -> {
// 响应body被完全接收之后调用
HttpResponse<Buffer> response = result.response();
if (response
.getHeader("content-type")
.equals("application/json")) {
// 错误body是JSON数据
JsonObject body = response.bodyAsJsonObject();
return new MyCustomException(
body.getString("code"),
body.getString("message"));
}
// 返回自定义的消息
return new MyCustomException(result.message());
});
ResponsePredicate predicate = ResponsePredicate
.create(ResponsePredicate.SC_SUCCESS, converter);
警告
|
在Java中,当捕获了stack trace, 创建异常可能会带来性能开销,所以您可能想要创建不捕获stack trace的异常。默认情况下, 报告异常使用不捕获stack trace的异常。 |
处理 30x 重定向
默认情况下,客户端跟随着重定向,您可以在 WebClientOptions
配置默认行为:
WebClient client = WebClient
.create(vertx, new WebClientOptions().setFollowRedirects(false));
客户端最多可以跟随 16
个请求重定向,可以在相同的配置中进行更改:
WebClient client = WebClient
.create(vertx, new WebClientOptions().setMaxRedirects(5));
注意
|
出于安全原因,客户端不会使用除了GET或HEAD的方法来跟随着重定向请求 |
HTTP 响应缓存
Vert.x web 提供了一个可以缓存 HTTP 响应的实现, 您可以创建一个 CachingWebClient
实例。
创建一个带缓存的 web client
WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);
配置缓存内容
默认情况下,带缓存的 web client 只会缓存通过 GET
方法返回的状态值为 200
, 301
, 或 404
的响应内容。
带有 Vary
头部的响应默认情况下也不会被缓存。
您可以在创建客户端时,通过传入一个 CachingWebClientOptions
实例来配置缓存策略。
CachingWebClientOptions options = new CachingWebClientOptions()
.addCachedMethod(HttpMethod.HEAD)
.removeCachedStatusCode(301)
.setEnableVaryCaching(true);
WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client, options);
如果响应的 Cache-Control
头部内容为 private
策略,那么该响应不会被缓存,除非
该 web client 是一个 WebClientSession
的实例。 请参阅 处理私有响应 。
使用别的储存实现
默认情况下, 带缓存的客户端使用一个本地的 Map
来保存响应。 您也可以使用您自己的储存实现类来保存响应内容。
您需要自己实现 CacheStore
接口,并且在创建客户端时指定使用您的实现类。
WebClient client = WebClient.create(vertx);
CacheStore store = new NoOpCacheStore(); // or any store you like
WebClient cachingWebClient = CachingWebClient.create(client, store);
处理私有响应
为了缓存私有响应, CachingWebClient
可以与
WebClientSession
结合。执行以下代码,那些在 Cache-Control
头部中指定为 public
缓存策略的
响应,会被缓存到和 web client 一起创建的 CacheStore
中。 私有响应,也就是 Cache-Control
头部中指定为 private
缓存策略的响应,会被缓存到
会话中,以确保缓存的响应不会被泄露给其他用户(会话)。
要创建一个可以缓存私有响应的客户端,需要传入一个 CachingWebClient
给 WebClientSession
。
WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);
WebClient sessionClient = WebClientSession.create(cachingWebClient);
URI模板
URI模板基于https://datatracker.ietf.org/doc/html/rfc6570[URI Template RFC 6570]提供Http请求字符串URI替代方案。
您可通过阅读 Vert.x URI 文档 以获取更多信息。
您可以使用`UriTemplate`URI替代java字符串URI创建`HttpRequest`,
首先将模板字符串解析为 UriTemplate
UriTemplate REQUEST_URI = UriTemplate.of("/some-uri?{param}");
然后使用它来创建一个request
HttpRequest<Buffer> request = client.get(8080, "myserver.mycompany.com", REQUEST_URI);
设置模板参数
request.setTemplateParam("param", "param_value");
最后像通常一样发送请求
request.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
或连起来
client.get(8080, "myserver.mycompany.com", REQUEST_URI)
.setTemplateParam("param", "param_value")
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
URI模板扩展
在发送请求之前,Vert.x WebClient把模板扩展为带有请求模板参数字符串。
字符串模板负责把您的参数encoding,
String euroSymbol = "\u20AC";
UriTemplate template = UriTemplate.of("/convert?{amount}&{currency}");
// Request uri: /convert?amount=1234¤cy=%E2%82%AC
Future<HttpResponse<Buffer>> fut = client.get(template)
.setTemplateParam("amount", amount)
.setTemplateParam("currency", euroSymbol)
.send();
默认扩展语法称为_简单字符串扩展_, 还有其他可用的扩展语法
-
路径参数扩展 (
{/varname}
) -
表单式查询扩展 (
{?varname}
) -
等等…
您可以参考 Vert.x URI 模板文档(如果可用,请添加链接)以获取各种扩展样式的完整概述。
根据 RFC 的要求,模板扩展将用空字符串替换缺少的模板参数。您可以修改插入失败这个情况,缺少参数后将抛出异常:
WebClient webClient = WebClient.create(vertx, new WebClientOptions()
.setTemplateExpandOptions(new ExpandOptions()
.setAllowVariableMiss(false))
);
模板参数值
目标参数接受 String
, List<String>
和 Map<String, String>
。
每种扩展取决于扩展样式(用`?前缀表示),这里以 _query_参数例子开展(由 `*
后缀表示) 和使用表单查询形式扩展:
Map<String, String> query = new HashMap<>();
query.put("color", "red");
query.put("width", "30");
query.put("height", "50");
UriTemplate template = UriTemplate.of("/{?query*}");
// Request uri: /?color=red&width=30&height=50
Future<HttpResponse<Buffer>> fut = client.getAbs(template)
.setTemplateParam("query", query)
.send();
表单查询方式扩展将根据变量`{?query*}`扩展为`?color=red&width=30&height=50`
使用 HTTPS
Vert.x Web Client 可以用跟 Vert.x HttpClient
完全一样的方式配置使用HTTPS。
您可以指定每个请求的行为
client
.get(443, "myserver.mycompany.com", "/some-uri")
.ssl(true)
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
或使用带有绝对URI参数的创建方法
client
.getAbs("https://myserver.mycompany.com:4043/some-uri")
.send()
.onSuccess(res ->
System.out.println("Received response with status code" + res.statusCode()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
会话管理
Vert.x Web提供了Web会话管理设施;使用它,您需要对于每个用户(会话)创建一个
WebClientSession
,并使用它来代替
WebClient
。
创建一个 WebClientSession
您像下面一样创建一个 WebClientSession
实例
WebClient client = WebClient.create(vertx);
WebClientSession session = WebClientSession.create(client);
发出请求
一旦创建, WebClientSession
就可以代替
WebClient
去做 HTTP(s) 请求并且自动管理从您正在调用的
服务器收到的所有cookie。
设置会话级别headers
您可以按以下步骤设置任何会话级别的headers到要添加的每个请求:
WebClientSession session = WebClientSession.create(client);
session.addHeader("my-jwt-token", jwtToken);
然后 headers 将被添加到每个请求中; 注意 这些 headers 将发送给所有主机;
如果您需要发送不同的 headers 到不同的主机, 您必须将它们手动添加到每个单个请求中,并且不能添加到
WebClientSession
。
OAuth2 安全
Vert.x web 提供了 web 会话管理的功能; 您可以给每个用户(会话)创建一个
OAuth2WebClient
,并且用其来替代
WebClient
。
创建一个 Oauth2 客户端
您可以用以下代码创建一个 OAuth2WebClient
的实例:
WebClient client = WebClient.create(vertx);
OAuth2WebClient oauth2 = OAuth2WebClient.create(
client,
OAuth2Auth.create(vertx, new OAuth2Options(/* enter IdP config */)))
// configure the initial credentials (needed to fetch if needed
// the access_token
.withCredentials(new TokenCredentials("some.jwt.token"));
客户端也可以使用 OpenId 服务发现来进行配置,例如通过以下操作来 连接一个 keycloak 服务器:
KeycloakAuth.discover(
vertx,
new OAuth2Options().setSite("https://keycloakserver.com"))
.onSuccess(oauth -> {
OAuth2WebClient client = OAuth2WebClient.create(
WebClient.create(vertx),
oauth)
// if your keycloak is configured for password_credentials_flow
.withCredentials(
new UsernamePasswordCredentials("bob", "s3cret"));
});
发送请求
客户端创建完毕后, 就可以使用 OAuth2WebClient
来代替
WebClient
发送 HTTP(s) 请求,并且自动管理从正在请求的服务器接收
到的所有 cookie。
避免过期的 token
您可以使用以下代码来给每个请求设置 token 的过期时间:
OAuth2WebClient client = OAuth2WebClient.create(
baseClient,
oAuth2Auth,
new OAuth2WebClientOptions()
.setLeeway(5));
如果要发送一个请求,那么当前正在使用的用户对象会被检查是否到达了设置的过期时间。 如果需要的话,客户端会刷新 token,否则中止请求并抛出异常。
请求仍然可能因为 token 过期而失败,这是因为服务端也会验证一遍 token 是否
过期。 为了减少用户端的工作, 客户端可以配置为当返回的状态码为 401 (禁止访问)时
只进行 单次 重试。 当 refreshTokenOnForbidden
被设置为 true
时, 客户端会
用一个新的 token 重试一次,再给用户返回
handler/promise 。
OAuth2WebClient client = OAuth2WebClient.create(
baseClient,
oAuth2Auth,
new OAuth2WebClientOptions()
// the client will attempt a single token request, if the request
// if the status code of the response is 401
// there will be only 1 attempt, so the second consecutive 401
// will be passed down to your handler/promise
.setRenewTokenOnForbidden(true));
RxJava 3 API
RxJava HttpRequest
提供了RX化的原始版本API,
rxSend
方法返回一个能够订阅到HTTP请求的 Single<HttpResponse<Buffer>>
,因此,Single
能被订阅多次。
Single<HttpResponse<Buffer>> single = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.rxSend();
// Single订阅后,发送请求
single.subscribe(response -> System.out.println("Received 1st response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));
// 发送另一个请求
single.subscribe(response -> System.out.println("Received 2nd response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));
获得的 Single
可以与RxJava API自然地组合和链式调用
Single<String> url = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.rxSend()
.map(HttpResponse::bodyAsString);
// 使用flatMap操作,向Single的URL上发出请求
url
.flatMap(u -> client.getAbs(u).rxSend())
.subscribe(response -> System.out.println("Received response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));
同样的API们是可用的
Single<HttpResponse<JsonObject>> single = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.putHeader("some-header", "header-value")
.addQueryParam("some-param", "param value")
.as(BodyCodec.jsonObject())
.rxSend();
single.subscribe(resp -> {
System.out.println(resp.statusCode());
System.out.println(resp.body());
});
rxSendStream
应首选发送 Flowable<Buffer>
body。
Flowable<Buffer> body = getPayload();
Single<HttpResponse<Buffer>> single = client
.post(8080, "myserver.mycompany.com", "/some-uri")
.rxSendStream(body);
single.subscribe(resp -> {
System.out.println(resp.statusCode());
System.out.println(resp.body());
});
订阅后, body
将被订阅,其内容被用于请求。
域套接字
自3.7.1后, Web Client 支持domain sockets, 例如,您可以跟 local Docker daemon 交流。
为了达成这个目的, Vertx
实例必须使用native transport创建,
您可以阅读 Vert.x 核心文档,它清楚地说明了这一点。
SocketAddress serverAddress = SocketAddress
.domainSocketAddress("/var/run/docker.sock");
// 我们仍然需要指定主机和端口,
// 因此请求的HTTP header 是 localhost:8080
// 否则将是错误格式的HTTP请求
// 在此示例中,实际值并不重要
client
.request(
HttpMethod.GET,
serverAddress,
8080,
"localhost",
"/images/json")
.expect(ResponsePredicate.SC_ACCEPTED)
.as(BodyCodec.jsonObject())
.send()
.onSuccess(res ->
System.out.println("Current Docker images" + res.body()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));