Vert.x Web Client

Vert.x Web Client 是一个异步的 HTTP 和 HTTP/2 客户端。

Web Client使得发送 HTTP 请求以及从 Web 服务器接收 HTTP 响应变得更加便捷,同时提供了额外的高级功能,例如:

  • Json body的编码和解码
  • 请求和响应泵
  • 请求参数的处理
  • 统一的错误处理
  • 提交表单

使用Web Client

如需使用Vert.x Web Client,请先加入以下依赖,到您的build描述 dependencies 部分 :

  • Maven:
  1. <dependency>
  2. <groupId>io.vertx</groupId>
  3. <artifactId>vertx-web-client</artifactId>
  4. <version>4.1.5</version>
  5. </dependency>
  • Gradle :
  1. dependencies {
  2. compile 'io.vertx:vertx-web-client:4.1.5'
  3. }

创建Web Client

可以创建一个缺省 WebClient 实例:

  1. WebClient client = WebClient.create(vertx);

还可以使用配置项来创建客户端:

  1. WebClientOptions options = new WebClientOptions()
  2. .setUserAgent("My-App/1.2.3");
  3. options.setKeepAlive(false);
  4. WebClient client = WebClient.create(vertx, options);

Web Client配置项继承自 HttpClient 配置项,您可以设置其中任何一个项。

如果已在程序中创建 HttpClient,可用以下方式复用:

  1. WebClient client = WebClient.wrap(httpClient);

发送请求

无请求体的简单请求

通常,您想发送一个无请求体的HTTP请求。以下是一般情况下的 HTTP GET, OPTIONS和HEAD 请求

  1. WebClient client = WebClient.create(vertx);
  2. // 发送GET请求
  3. client
  4. .get(8080, "myserver.mycompany.com", "/some-uri")
  5. .send()
  6. .onSuccess(response ->
  7. System.out.println("Received response with status code" + response.statusCode()))
  8. .onFailure(err ->
  9. System.out.println("Something went wrong " + err.getMessage()));
  10. // 发送HEAD请求
  11. client
  12. .head(8080, "myserver.mycompany.com", "/some-uri")
  13. .send()
  14. .onSuccess(response ->
  15. System.out.println("Received response with status code" + response.statusCode()))
  16. .onFailure(err ->
  17. System.out.println("Something went wrong " + err.getMessage()));

您可用以下链式方式向请求URI添加查询参数

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .addQueryParam("param", "param_value")
  4. .send()
  5. .onSuccess(response -> System.out
  6. .println("Received response with status code" + response.statusCode()))
  7. .onFailure(err ->
  8. System.out.println("Something went wrong " + err.getMessage()));

在请求URI中的参数将会被预填充

  1. HttpRequest<Buffer> request = client
  2. .get(
  3. 8080,
  4. "myserver.mycompany.com",
  5. "/some-uri?param1=param1_value&param2=param2_value");
  6. // 添加 param3
  7. request.addQueryParam("param3", "param3_value");
  8. // 覆盖 param2
  9. request.setQueryParam("param2", "another_param2_value");

设置请求URI将会自动清除已有的查询参数

  1. HttpRequest<Buffer> request = client
  2. .get(8080, "myserver.mycompany.com", "/some-uri");
  3. // 添加 param1
  4. request.addQueryParam("param1", "param1_value");
  5. // 覆盖 param1 并添加 param2
  6. request.uri("/some-uri?param1=param1_value&param2=param2_value");

填充请求体

如需要发送请求体,可使用相同的API,并在最后加上 sendXXX 方法,发送相应的请求体。

使用 sendBuffer 发送一个buffer body

  1. client
  2. .post(8080, "myserver.mycompany.com", "/some-uri")
  3. .sendBuffer(buffer)
  4. .onSuccess(res -> {
  5. // OK
  6. });

发送single buffer很有用,但是通常您不想完全将内容加载到内存中,因为它可能太大,或者您想同时处理多个请求,或者每个请求只想使用最小的(消耗)。 为此,Web Client可以使用 ReadStream<Buffer> 的(例如 AsyncFile 是一个ReadStream) sendStream 方法发送。

  1. client
  2. .post(8080, "myserver.mycompany.com", "/some-uri")
  3. .sendStream(stream)
  4. .onSuccess(res -> {
  5. // OK
  6. });

Web Client负责为您设置泵传输(transfer pump)。如果流长度未知则使用分块传输(chunked transfer)编码。

当您知道流的大小,您应该在HTTP header中设置 content-length

  1. fs.open("content.txt", new OpenOptions(), fileRes -> {
  2. if (fileRes.succeeded()) {
  3. ReadStream<Buffer> fileStream = fileRes.result();
  4. String fileLen = "1024";
  5. // 用POST方法发送文件
  6. client
  7. .post(8080, "myserver.mycompany.com", "/some-uri")
  8. .putHeader("content-length", fileLen)
  9. .sendStream(fileStream)
  10. .onSuccess(res -> {
  11. // OK
  12. })
  13. ;
  14. }
  15. });

这个POST方法不会被分块传输。

JSON bodies

有时您需要发送JSON body请求,可使用 sendJsonObject 发送一个 JsonObject

  1. client
  2. .post(8080, "myserver.mycompany.com", "/some-uri")
  3. .sendJsonObject(
  4. new JsonObject()
  5. .put("firstName", "Dale")
  6. .put("lastName", "Cooper"))
  7. .onSuccess(res -> {
  8. // OK
  9. });

在Java,Groovy以及Kotlin中,您可以使用 sendJson 方法,它使用 Json.encode 方法映射一个 POJO(Plain Old Java Object) 到一个 Json 对象

  1. client
  2. .post(8080, "myserver.mycompany.com", "/some-uri")
  3. .sendJson(new User("Dale", "Cooper"))
  4. .onSuccess(res -> {
  5. // OK
  6. });

表单提交

您可以使用 sendForm 的变体发送http表单提交。

  1. MultiMap form = MultiMap.caseInsensitiveMultiMap();
  2. form.set("firstName", "Dale");
  3. form.set("lastName", "Cooper");
  4. // 用URL编码方式提交表单
  5. client
  6. .post(8080, "myserver.mycompany.com", "/some-uri")
  7. .sendForm(form)
  8. .onSuccess(res -> {
  9. // OK
  10. });

默认情况下,提交表单header中的 content-type 属性值为 application/x-www-form-urlencoded,您还可将其替换为 multipart/form-data

  1. MultiMap form = MultiMap.caseInsensitiveMultiMap();
  2. form.set("firstName", "Dale");
  3. form.set("lastName", "Cooper");
  4. // 提交multipart form表单
  5. client
  6. .post(8080, "myserver.mycompany.com", "/some-uri")
  7. .putHeader("content-type", "multipart/form-data")
  8. .sendForm(form)
  9. .onSuccess(res -> {
  10. // OK
  11. });

如果你想上传文件的同时发送属性,您可以创建一个 MultipartForm ,然后使用 sendMultipartForm

  1. MultipartForm form = MultipartForm.create()
  2. .attribute("imageDescription", "a very nice image")
  3. .binaryFileUpload(
  4. "imageFile",
  5. "image.jpg",
  6. "/path/to/image",
  7. "image/jpeg");
  8. // 提交multipart form表单
  9. client
  10. .post(8080, "myserver.mycompany.com", "/some-uri")
  11. .sendMultipartForm(form)
  12. .onSuccess(res -> {
  13. // OK
  14. });

填充请求头

您可使用headers的multi-map 填充请求头:

  1. HttpRequest<Buffer> request = client
  2. .get(8080, "myserver.mycompany.com", "/some-uri");
  3. MultiMap headers = request.headers();
  4. headers.set("content-type", "application/json");
  5. headers.set("other-header", "foo");

此处 Headers 是一个 MultiMap 实例,提供了添加、设置以及删除头属性操作的入口。HTTP headers允许某个特定的key有多个值。

您还可使用 putHeader 写入headers属性:

  1. HttpRequest<Buffer> request = client
  2. .get(8080, "myserver.mycompany.com", "/some-uri");
  3. request.putHeader("content-type", "application/json");
  4. request.putHeader("other-header", "foo");

重用请求

send 方法可被安全的重复多次调用,这使得它可以很容易的配置以及重用 HttpRequest 对象

  1. HttpRequest<Buffer> get = client
  2. .get(8080, "myserver.mycompany.com", "/some-uri");
  3. get
  4. .send()
  5. .onSuccess(res -> {
  6. // OK
  7. });
  8. // 又一些请求
  9. get
  10. .send()
  11. .onSuccess(res -> {
  12. // OK
  13. });

不过要当心 HttpRequest 实例是可变的(mutable). 因此,您应该在修改已被缓存了的实例之前,使用 copy 方法。

  1. HttpRequest<Buffer> get = client
  2. .get(8080, "myserver.mycompany.com", "/some-uri");
  3. get
  4. .send()
  5. .onSuccess(res -> {
  6. // OK
  7. });
  8. // "get" 请求实例保持未修改
  9. get
  10. .copy()
  11. .putHeader("a-header", "with-some-value")
  12. .send()
  13. .onSuccess(res -> {
  14. // OK
  15. });

超时

您可通过 timeout 。方法设置超时时间。

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .timeout(5000)
  4. .send()
  5. .onSuccess(res -> {
  6. // OK
  7. })
  8. .onFailure(err -> {
  9. // 当是由java.util.concurrent.TimeoutException导致时,或许是一个超时
  10. });

若请求在设定时间内没有返回任何数据,则一个异常将会传递给响应处理器。

处理HTTP响应

Web Client请求发送之后,您总是在单个 HttpResponse 中处理单个异步结果 。

当响应被成功接收到之后,相应的回调函数将会被调用。

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .send()
  4. .onSuccess(res ->
  5. System.out.println("Received response with status code" + res.statusCode()))
  6. .onFailure(err ->
  7. System.out.println("Something went wrong " + err.getMessage()));

响应解码

缺省状况下,Web Client提供一个response body作为 Buffer ,并且未运用任何解码器。

可以使用 BodyCodec 实现以下自定义response body解码:

  • 文本字符串
  • Json 对象
  • Json 映射的 POJO
  • WriteStream

一个body解码器可以将任意二进制数据流解码为特定的对象实例,从而节省了您自己在响应处理器里解码的步骤。

使用 BodyCodec.jsonObject 解码一个 Json 对象:

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .as(BodyCodec.jsonObject())
  4. .send()
  5. .onSuccess(res -> {
  6. JsonObject body = res.body();
  7. System.out.println(
  8. "Received response with status code" +
  9. res.statusCode() +
  10. " with body " +
  11. body);
  12. })
  13. .onFailure(err ->
  14. System.out.println("Something went wrong " + err.getMessage()));

在Java,Groovy以及Kotlin中,可以自定义Json映射POJO解码:

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .as(BodyCodec.json(User.class))
  4. .send()
  5. .onSuccess(res -> {
  6. User user = res.body();
  7. System.out.println(
  8. "Received response with status code" +
  9. res.statusCode() +
  10. " with body " +
  11. user.getFirstName() +
  12. " " +
  13. user.getLastName());
  14. })
  15. .onFailure(err ->
  16. System.out.println("Something went wrong " + err.getMessage()));

这个编码器将响应缓存泵入到 WriteStream 中,并且在异步结果响应中,发出操作成功或失败的信号。

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .as(BodyCodec.pipe(writeStream))
  4. .send()
  5. .onSuccess(res ->
  6. System.out.println("Received response with status code" + res.statusCode()))
  7. .onFailure(err ->
  8. System.out.println("Something went wrong " + err.getMessage()));

经常会看到API返回一个JSON对象流。例如,Twitter API可以提供一个推文回馈。处理这个情况,您可以使用 BodyCodec.jsonStream。传递一个JSON解析器,该解析器从HTTP响应中开始读取JSON流。

  1. JsonParser parser = JsonParser.newParser().objectValueMode();
  2. parser.handler(event -> {
  3. JsonObject object = event.objectValue();
  4. System.out.println("Got " + object.encode());
  5. });
  6. client
  7. .get(8080, "myserver.mycompany.com", "/some-uri")
  8. .as(BodyCodec.jsonStream(parser))
  9. .send()
  10. .onSuccess(res ->
  11. System.out.println("Received response with status code" + res.statusCode()))
  12. .onFailure(err ->
  13. System.out.println("Something went wrong " + err.getMessage()));

最后,如您对响应结果不感兴趣,可用 BodyCodec.none 简单的丢弃response body。

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .as(BodyCodec.none())
  4. .send()
  5. .onSuccess(res ->
  6. System.out.println("Received response with status code" + res.statusCode()))
  7. .onFailure(err ->
  8. System.out.println("Something went wrong " + err.getMessage()));

若无法预知响应内容类型,您依旧可以在获取结果之后,用 bodyAsXXX() 方法将其转换成指定的类型

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .send()
  4. .onSuccess(res -> {
  5. // 将结果解码为Json对象
  6. JsonObject body = res.bodyAsJsonObject();
  7. System.out.println(
  8. "Received response with status code" +
  9. res.statusCode() +
  10. " with body " +
  11. body);
  12. })
  13. .onFailure(err ->
  14. System.out.println("Something went wrong " + err.getMessage()));

响应谓词

默认的,仅当在网络级别发生错误时,Vert.x Web Client请求才以错误结尾。

换言之, 您必须在收到响应后手动执行健全性检查:

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .send()
  4. .onSuccess(res -> {
  5. if (
  6. res.statusCode() == 200 &&
  7. res.getHeader("content-type").equals("application/json")) {
  8. // 将结果解码为Json对象
  9. JsonObject body = res.bodyAsJsonObject();
  10. System.out.println(
  11. "Received response with status code" +
  12. res.statusCode() +
  13. " with body " +
  14. body);
  15. } else {
  16. System.out.println("Something went wrong " + res.statusCode());
  17. }
  18. })
  19. .onFailure(err ->
  20. System.out.println("Something went wrong " + err.getMessage()));

您可以灵活的替换成清晰简明的 response predicates

Response predicates 当响应不符合条件会使请求失败。

Web Client附带了一组开箱即用的谓词,可供使用:

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .expect(ResponsePredicate.SC_SUCCESS)
  4. .expect(ResponsePredicate.JSON)
  5. .send()
  6. .onSuccess(res -> {
  7. // 安全地将body解码为json对象
  8. JsonObject body = res.bodyAsJsonObject();
  9. System.out.println(
  10. "Received response with status code" +
  11. res.statusCode() +
  12. " with body " +
  13. body);
  14. })
  15. .onFailure(err ->
  16. System.out.println("Something went wrong " + err.getMessage()));

当现有谓词不满足您的需求时,您还可以创建自定义谓词:

  1. Function<HttpResponse<Void>, ResponsePredicateResult> methodsPredicate =
  2. resp -> {
  3. String methods = resp.getHeader("Access-Control-Allow-Methods");
  4. if (methods != null) {
  5. if (methods.contains("POST")) {
  6. return ResponsePredicateResult.success();
  7. }
  8. }
  9. return ResponsePredicateResult.failure("Does not work");
  10. };
  11. // 发送预检CORS请求
  12. client
  13. .request(
  14. HttpMethod.OPTIONS,
  15. 8080,
  16. "myserver.mycompany.com",
  17. "/some-uri")
  18. .putHeader("Origin", "Server-b.com")
  19. .putHeader("Access-Control-Request-Method", "POST")
  20. .expect(methodsPredicate)
  21. .send()
  22. .onSuccess(res -> {
  23. // 立即处理POST请求
  24. })
  25. .onFailure(err ->
  26. System.out.println("Something went wrong " + err.getMessage()));

预定义谓词

为了方便起见,Web Client附带了一些常见用例的谓词。

对于状态码,例如 ResponsePredicate.SC_SUCCESS ,验证响应具有 2xx 代码,您也可以自定义创建一个

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .expect(ResponsePredicate.status(200, 202))
  4. .send()
  5. .onSuccess(res -> {
  6. // ....
  7. });

对于content types,例如 ResponsePredicate.JSON ,验证响应具有JSON数据,您也可以自定义创建一个

  1. client
  2. .get(8080, "myserver.mycompany.com", "/some-uri")
  3. .expect(ResponsePredicate.contentType("some/content-type"))
  4. .send()
  5. .onSuccess(res -> {
  6. // ....
  7. });

创建自定义失败

默认情况下,响应谓词(包括预定义的)使用默认的错误转换器,它将丢弃body并传递一条简单消息。您可以通过自定义异常类来替换错误转换器:

  1. ResponsePredicate predicate = ResponsePredicate.create(
  2. ResponsePredicate.SC_SUCCESS,
  3. result -> new MyCustomException(result.message()));

为避免丢失此信息,在错误发生之前,可以在转换器被调用之前等待响应body被完全接收:

  1. ErrorConverter converter = ErrorConverter.createFullBody(result -> {
  2. // 响应body被完全接收之后调用
  3. HttpResponse<Buffer> response = result.response();
  4. if (response
  5. .getHeader("content-type")
  6. .equals("application/json")) {
  7. // 错误body是JSON数据
  8. JsonObject body = response.bodyAsJsonObject();
  9. return new MyCustomException(
  10. body.getString("code"),
  11. body.getString("message"));
  12. }
  13. // 返回自定义的消息
  14. return new MyCustomException(result.message());
  15. });
  16. ResponsePredicate predicate = ResponsePredicate
  17. .create(ResponsePredicate.SC_SUCCESS, converter);

处理 30x 重定向

默认情况下,客户端跟随着重定向,您可以在 WebClientOptions 配置默认行为:

  1. WebClient client = WebClient
  2. .create(vertx, new WebClientOptions().setFollowRedirects(false));

客户端最多可以跟随 16 个请求重定向,可以在相同的配置中进行更改:

  1. WebClient client = WebClient
  2. .create(vertx, new WebClientOptions().setMaxRedirects(5));

使用 HTTPS

Vert.x Web Client 可以用跟 Vert.x HttpClient 完全一样的方式配置使用HTTPS。

您可以指定每个请求的行为

  1. client
  2. .get(443, "myserver.mycompany.com", "/some-uri")
  3. .ssl(true)
  4. .send()
  5. .onSuccess(res ->
  6. System.out.println("Received response with status code" + res.statusCode()))
  7. .onFailure(err ->
  8. System.out.println("Something went wrong " + err.getMessage()));

或使用带有绝对URI参数的创建方法

  1. client
  2. .getAbs("https://myserver.mycompany.com:4043/some-uri")
  3. .send()
  4. .onSuccess(res ->
  5. System.out.println("Received response with status code" + res.statusCode()))
  6. .onFailure(err ->
  7. System.out.println("Something went wrong " + err.getMessage()));

会话管理

Vert.x Web提供了Web会话管理设施;使用它,您需要对于每个用户(会话)创建一个 WebClientSession ,并使用它来代替 WebClient

创建一个 WebSession

您像下面一样创建一个 WebClientSession 实例

  1. WebClient client = WebClient.create(vertx);
  2. WebClientSession session = WebClientSession.create(client);

发出请求

一旦创建, WebClientSession 可以代替 WebClient 去做HTTP(s) 请求并且自动管理你正在调用的,从服务器收到的所有cookie。

设置会话级别headers

您可以按以下步骤设置任何会话级别的headers到要添加的每个请求:

  1. WebClientSession session = WebClientSession.create(client);
  2. session.addHeader("my-jwt-token", jwtToken);

然后headers将被添加到每个请求中; 注意 这些headers将发送给所有主机;如果你需要发送不同的headers到不同的主机, 您必须将它们手动添加到每个单个请求中,并且不添加到 WebClientSession