<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>5.0.1</version>
</dependency>
Vert.x Web 客户端
Vert.x Web 客户端是一个异步 HTTP 和 HTTP/2 客户端。
Web 客户端使得与 Web 服务器进行 HTTP 请求/响应交互变得容易,并提供了以下高级功能:
-
Json 正文编码/解码
-
请求/响应传输
-
请求参数
-
统一错误处理
-
表单提交
Web 客户端并没有废弃 Vert.x Core 的 HttpClient
,事实上它就是基于这个客户端的,并继承了其配置和诸如连接池、HTTP/2 支持、流水线支持等强大功能。当需要对 HTTP 请求/响应进行精细控制时,应使用 HttpClient
。
Web 客户端不提供 WebSocket API,应使用 Vert.x Core 的 HttpClient
。它目前也不处理 cookie。
使用 Web 客户端
要使用 Vert.x Web 客户端,请将以下依赖项添加到您的构建描述符的 dependencies 部分中:
-
Maven(在您的
pom.xml
中)
-
Gradle(在您的
build.gradle
文件中)
dependencies {
compile 'io.vertx:vertx-web-client:5.0.1'
}
Vert.x 核心 HTTP 客户端回顾
Vert.x Web 客户端使用了 Vert.x 核心的 API,因此,如果您还不熟悉 Vert.x 核心中 HttpClient
的基本概念,那么熟悉一下这些概念将非常有价值。
创建 Web 客户端
您可以按如下方式创建具有默认选项的 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 客户端选项继承了 Http 客户端选项,因此您可以设置其中任何一个。
如果您的应用程序中已经有一个 HTTP 客户端,您也可以重复使用它:
WebClient client = WebClient.wrap(httpClient);
在大多数情况下,Web 客户端应在应用程序启动时创建一次,然后重复使用。否则,您将失去许多好处,例如连接池,并且如果实例未正确关闭,可能会导致资源泄漏。 |
发起请求
无请求体的简单请求
通常,您会希望发起不带请求体的 HTTP 请求。这通常适用于 HTTP GET、OPTIONS 和 HEAD 请求:
WebClient client = WebClient.create(vertx);
// Send a GET request
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()));
// Send a HEAD request
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");
// Add param3
request.addQueryParam("param3", "param3_value");
// Overwrite param2
request.setQueryParam("param2", "another_param2_value");
设置请求 URI 会丢弃现有查询参数:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri");
// Add param1
request.addQueryParam("param1", "param1_value");
// Overwrite param1 and add param2
request.uri("/some-uri?param1=param1_value¶m2=param2_value");
写入请求体
当您需要发起带请求体的请求时,您可以使用相同的 API,然后调用期望发送请求体的 sendXXX
方法。
使用 sendBuffer
发送缓冲区正文
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendBuffer(buffer)
.onSuccess(res -> {
// OK
});
发送单个缓冲区很有用,但通常您不希望将内容完全加载到内存中,因为它可能太大,或者您希望处理许多并发请求并希望每个请求仅使用最小的内存。为此,Web 客户端可以使用 sendStream
方法发送 ReadStream<Buffer>
(例如 AsyncFile
是 ReadStream<Buffer>
):
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendStream(stream)
.onSuccess(res -> {
// OK
});
Web 客户端会为您处理传输泵的设置。由于流的长度未知,请求将使用分块传输编码。
当您知道流的大小后,应在使用 content-length
标头之前指定:
fs.open("content.txt", new OpenOptions())
.onSuccess(fileStream -> {
String fileLen = "1024";
// Send the file to the server using POST
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.putHeader("content-length", fileLen)
.sendStream(fileStream)
.onSuccess(res -> {
// OK
});
});
POST 请求将不会分块。
Json 正文
通常您会希望发送 Json 正文请求,要发送 JsonObject
请使用 sendJsonObject
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 映射器将对象编码为 Json。 |
表单提交
您可以使用 sendForm
变体发送 http 表单提交正文。
MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");
// Submit the form as a form URL encoded body
client
.post(8080, "myserver.mycompany.com", "/some-uri")
.sendForm(form)
.onSuccess(res -> {
// OK
});
默认情况下,表单以 application/x-www-form-urlencoded
内容类型标头提交。您可以将 content-type
标头设置为 multipart/form-data
:
MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");
// Submit the form as a multipart form body
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");
// Submit the form as a multipart form body
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");
头是 MultiMap
的一个实例,它提供了添加、设置和删除条目的操作。Http 头允许一个特定键对应多个值。
您也可以使用 putHeader 写入头:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri");
request.putHeader("content-type", "application/json");
request.putHeader("other-header", "foo");
配置请求以添加身份验证。
身份验证可以通过手动设置正确的头来执行,或者使用我们预定义的方法(我们强烈建议启用 HTTPS,特别是对于需要身份验证的请求):
在基本 HTTP 身份验证中,请求包含一个 Authorization: Basic <credentials>
形式的头字段,其中 credentials 是由冒号连接的 id 和密码的 base64 编码。
您可以按如下方式配置请求以添加基本访问身份验证:
HttpRequest<Buffer> request = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.authentication(new UsernamePasswordCredentials("myid", "mypassword"));
在 OAuth 2.0 中,请求包含一个 Authorization: Bearer <bearerToken>
形式的头字段,其中 bearerToken 是由授权服务器颁发用于访问受保护资源的持有者令牌。
您可以按如下方式配置请求以添加持有者令牌身份验证:
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
});
// Same request again
get
.send()
.onSuccess(res -> {
// OK
});
但请注意,HttpRequest
实例是可变的。因此,在修改缓存的实例之前,您应该调用 copy
方法。
HttpRequest<Buffer> get = client
.get(8080, "myserver.mycompany.com", "/some-uri");
get
.send()
.onSuccess(res -> {
// OK
});
// The "get" request instance remains unmodified
get
.copy()
.putHeader("a-header", "with-some-value")
.send()
.onSuccess(res -> {
// OK
});
超时
您可以使用 connectTimeout
为特定的 http 请求设置连接超时。
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.connectTimeout(5000)
.send()
.onSuccess(res -> {
// OK
})
.onFailure(err -> {
// Might be a timeout when cause is java.util.concurrent.TimeoutException
});
如果客户端未能在超时期限内获取到与服务器的连接,则会将异常传递给响应处理程序。
您可以使用 idleTimeout
为特定的 http 请求设置空闲超时。
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.idleTimeout(5000)
.send()
.onSuccess(res -> {
// OK
})
.onFailure(err -> {
// Might be a timeout when cause is java.util.concurrent.TimeoutException
});
如果在超时期限内请求未返回任何数据,则会将异常传递给响应处理程序。
您可以使用 timeout
设置两种超时
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.timeout(5000)
.send()
.onSuccess(res -> {
// OK
})
.onFailure(err -> {
// Might be a timeout when cause is java.util.concurrent.TimeoutException
});
处理 HTTP 响应
当 Web 客户端发送请求时,您总是处理单个异步结果 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 客户端请求仅在网络层面发生错误时才以错误结束。换句话说, |
响应是完全缓冲的,使用 BodyCodec.pipe 将响应传输到写入流 |
解码响应
默认情况下,Web 客户端将 http 响应体作为 Buffer
提供,并且不应用任何解码。
可以使用 BodyCodec
实现自定义响应体解码
-
纯字符串
-
Json 对象
-
Json 映射的 POJO
正文编解码器可以将任意二进制数据流解码为特定的对象实例,从而省去了响应处理程序中的解码步骤。
使用 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 可以提供推文(tweets)的提要(feed)。要处理此用例,您可以使用 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()));
当您事先不知道 http 响应的内容类型时,您仍然可以使用 bodyAsXXX()
方法将响应解码为特定类型
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(res -> {
// Decode the body as a json object
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()));
这仅对解码为缓冲区的响应有效。 |
响应期望
默认情况下,Vert.x Web 客户端请求仅在网络层面发生错误时才以错误结束。
换句话说,您必须在收到响应后手动执行健全性检查
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.onSuccess(res -> {
if (
res.statusCode() == 200 &&
res.getHeader("content-type").equals("application/json")) {
// Decode the body as a json object
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 expectations
可以在响应不符合某个条件时使请求失败。
Web 客户端可以重用 Vert.x HTTP 客户端预定义的期望
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.expecting(HttpResponseExpectation.SC_SUCCESS.and(HttpResponseExpectation.JSON))
.onSuccess(res -> {
// Safely decode the body as a json object
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()));
当现有期望不符合您的需求时,您也可以创建自定义期望
Expectation<HttpResponseHead> methodsPredicate = new Expectation<HttpResponseHead>() {
@Override
public boolean test(HttpResponseHead resp) {
String methods = resp.getHeader("Access-Control-Allow-Methods");
return methods != null && methods.contains("POST");
}
};
// Send pre-flight CORS request
client
.request(
HttpMethod.OPTIONS,
8080,
"myserver.mycompany.com",
"/some-uri")
.putHeader("Origin", "Server-b.com")
.putHeader("Access-Control-Request-Method", "POST")
.send()
.expecting(methodsPredicate)
.onSuccess(res -> {
// Process the POST request now
})
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));
预定义期望
为方便起见,Vert.x HTTP 客户端附带了一些适用于常见用例的期望,这些期望也适用于 Web 客户端。
对于状态码,例如 HttpResponseExpectation.SC_SUCCESS
用于验证响应是否具有 2xx
代码,您也可以创建自定义的期望
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.expecting(HttpResponseExpectation.status(200, 202))
.onSuccess(res -> {
// ....
});
对于内容类型,例如 HttpResponseExpectation.JSON
用于验证响应体是否包含 JSON 数据,您也可以创建自定义的期望
client
.get(8080, "myserver.mycompany.com", "/some-uri")
.send()
.expecting(HttpResponseExpectation.contentType("some/content-type"))
.onSuccess(res -> {
// ....
});
请参阅 HttpResponseExpectation
文档以获取预定义谓词的完整列表。
创建自定义失败
默认情况下,响应期望(包括预定义的期望)使用一个默认的错误转换器,该转换器会丢弃正文并传递一个简单消息。您可以通过映射失败来自定义异常类
Expectation<HttpResponseHead> expectation = HttpResponseExpectation.SC_SUCCESS
.wrappingFailure((resp, err) -> new MyCustomException(err.getMessage()));
许多 Web API 在错误响应中提供详细信息。例如,[Marvel API](https://developer.marvel.com/docs) 使用此 JSON 对象格式
{
"code": "InvalidCredentials",
"message": "The passed API key is invalid."
}
为了避免丢失此信息,可以转换响应体
HttpResponseExpectation.SC_SUCCESS.wrappingFailure((resp, err) -> {
// Invoked after the response body is fully received
HttpResponse<?> response =(HttpResponse<?>) resp;
if (response
.getHeader("content-type")
.equals("application/json")) {
// Error body is JSON data
JsonObject body = response.bodyAsJsonObject();
return new MyCustomException(
body.getString("code"),
body.getString("message"));
}
// Fallback to defaut message
return new MyCustomException(err.getMessage());
});
在 Java 中创建异常在捕获堆栈跟踪时可能会产生性能开销,因此您可能希望创建不捕获堆栈跟踪的异常。默认情况下,异常是使用不捕获堆栈跟踪的异常报告的。 |
处理 30x 重定向
默认情况下,客户端会遵循重定向,您可以在 WebClientOptions
中配置默认行为
WebClient client = WebClient
.create(vertx, new WebClientOptions().setFollowRedirects(false));
客户端最多会遵循 16
个请求重定向,这可以在相同的选项中更改
WebClient client = WebClient
.create(vertx, new WebClientOptions().setMaxRedirects(5));
出于安全原因,客户端不会遵循除 GET 或 HEAD 之外的方法请求的重定向 |
客户端负载均衡
默认情况下,当客户端将主机名解析为多个 IP 地址列表时,客户端会使用返回的第一个 IP 地址。
客户端可以配置为执行客户端负载均衡
WebClient client = WebClient.wrap(vertx
.httpClientBuilder()
.withLoadBalancer(LoadBalancer.ROUND_ROBIN)
.build());
Vert.x 开箱即用地提供了几种您可以使用的负载均衡策略
大多数负载均衡策略都是不言自明的。
基于哈希的路由可以通过 LoadBalancer.CONSISTENT_HASHING
策略实现。
WebClient client = WebClient.wrap(vertx
.httpClientBuilder()
.withLoadBalancer(LoadBalancer.ROUND_ROBIN)
.build());
您可以在 Vert.x Core HTTP 客户端文档中阅读有关客户端负载均衡的更多详细信息。
HTTP 响应缓存
Vert.x Web 提供了一个 HTTP 响应缓存功能;要使用它,您需要创建一个 CachingWebClient
。
创建缓存 Web 客户端
WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);
配置缓存内容
默认情况下,缓存 Web 客户端将仅缓存 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);
除非客户端也是 WebClientSession
,否则 Cache-Control
头中包含 private
指令的响应将不会被缓存。请参阅处理私有响应。
提供外部存储
在存储响应时,默认的缓存客户端将使用本地 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
指令的响应)将缓存在创建客户端时所用的 CacheStore
中。私有响应(即 Cache-Control
头中带有 private
指令的响应)将与会话一起缓存,以确保缓存的响应不会泄露给其他用户(会话)。
要创建一个可以缓存私有响应的客户端,请将 CachingWebClient
传递给 WebClientSession
。
WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);
WebClient sessionClient = WebClientSession.create(cachingWebClient);
URI 模板
URI 模板提供了一种基于 [URI 模板 RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) 的 HTTP 请求字符串 URI 的替代方案。
您可以阅读 Vert.x URI 模板[文档](../../vertx-uri-template/java/)以了解更多信息。
您可以使用 UriTemplate
URI 而不是 Java 字符串 URI 来创建 HttpRequest
首先将模板字符串解析为 UriTemplate
UriTemplate REQUEST_URI = UriTemplate.of("/some-uri?{param}");
然后使用它来创建请求
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 会使用请求模板参数将模板扩展为字符串。
字符串扩展会为您处理参数编码,
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 客户端可以按照与 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。
设置会话级别请求头
您可以按如下方式设置要添加到每个请求的会话级别请求头:
WebClientSession session = WebClientSession.create(client);
session.addHeader("my-jwt-token", jwtToken);
这些请求头将被添加到每个请求中;请注意,这些请求头将发送到所有主机;如果您需要向不同主机发送不同的请求头,则必须手动将它们添加到每个请求中,而不是添加到 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。
避免令牌过期
您可以按如下方式为每个请求设置令牌过期宽限期:
OAuth2WebClient client = OAuth2WebClient.create(
baseClient,
oAuth2Auth,
new OAuth2WebClientOptions()
.setLeeway(5));
如果需要执行请求,会检查当前活动用户对象的过期状态并附加给定的宽限期。这将允许客户端在需要时执行令牌刷新,而不是因错误中止操作。
请求仍可能由于令牌过期而失败,因为过期计算仍将在服务器端执行。为了减少用户端的工作,客户端可以配置为对返回状态码 401(禁止)的请求执行**单次**重试。当选项标志:refreshTokenOnForbidden
设置为 true
时,客户端将执行新的令牌请求,并在将响应传递给用户处理程序/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
提供了原始 API 的 Rx 封装版本,rxSend
方法返回一个 Single<HttpResponse<Buffer>>
,该 Single
在订阅时发起 HTTP 请求,因此可以多次订阅。
Single<HttpResponse<Buffer>> single = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.rxSend();
// Send a request upon subscription of the Single
single.subscribe(response -> System.out.println("Received 1st response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));
// Send another request
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);
// Use the flatMap operator to make a request on the URL Single
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());
});
发送 Flowable<Buffer>
正文时,应优先使用 rxSendStream
。
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
并将其内容用于请求。
HTTP 响应期望
与 HTTP 后端交互通常涉及验证 HTTP 响应代码和/或内容类型。
为了简化验证过程,请使用 HttpResponseExpectation
方法
Single<HttpResponse<Buffer>> single = client
.get(8080, "myserver.mycompany.com", "/some-uri")
.rxSend()
// Transforms the single into a failed single if the HTTP response is not successful
.compose(HttpResponseExpectation.status(200))
// Transforms the single into a failed single if the HTTP response content is not JSON
.compose(HttpResponseExpectation.contentType("application/json"));
Unix 域套接字
Web 客户端支持 Unix 域套接字。例如,您可以与[本地 Docker 守护程序](https://docs.dockerd.com.cn/engine/reference/commandline/dockerd/)进行交互。
要实现这一点,您必须使用 JDK16+ 运行您的应用程序,或者使用[原生传输](../../vertx-core/java/#_native_transports)创建 Vertx
实例。
SocketAddress serverAddress = SocketAddress
.domainSocketAddress("/var/run/docker.sock");
// We still need to specify host and port so the request
// HTTP header will be localhost:8080
// otherwise it will be a malformed HTTP request
// the actual value does not matter much for this example
client
.request(
HttpMethod.GET,
serverAddress,
8080,
"localhost",
"/images/json")
.as(BodyCodec.jsonObject())
.send()
.expecting(HttpResponseExpectation.SC_ACCEPTED)
.onSuccess(res ->
System.out.println("Current Docker images" + res.body()))
.onFailure(err ->
System.out.println("Something went wrong " + err.getMessage()));