Vert.x Web Client

    • Pump:泵(平滑流式数据读入内存的机制,防止一次性将大量数据读入内存导致内存溢出)
    • Response Codec:响应编解码器(编码及解码工具)
    • Body Codec:响应体编解码器

    组件介绍

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

    • JSON体的编码和解码

    • 请求和响应泵

    • 请求参数的处理

    • 统一的错误处理

    • 提交表单

    制作Web Client的目的并非为了替换Vert.x Core中的 ,而是基于该客户端,扩展并保留其便利的设置和特性,例如请求连接池(Pooling),HTTP/2的支持,流水线/管线的支持等。当您需要对 HTTP 请求和响应做细微粒度控制时,您应当使用 。

    另外Web Client并未提供 WebSocket API,此时您应当使用 HttpClient

    使用Web Client

    如需使用Vert.x Web Client,请先加入以下依赖:

    • Maven(在 pom.xml文件中):

    • Gradle(在build.gradle文件中):

      1. dependencies {
      2. compile 'io.vertx3.4.2'
      3. }

    Vert.x Web Client使用Vert.x Core的API,如您对此还不熟悉,请先熟悉 HttpClient 的一些基本概念。

    创建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 GET,OPTIONS以及HEAD请求没有请求体,可用以下方式发送无请求体的HTTP Requests(HTTP请求):

    1. WebClient client = WebClient.create(vertx);
    2. // 发送GET请求
    3. client
    4. .get(8080, "myserver.mycompany.com", "/some-uri")
    5. .send(ar -> {
    6. if (ar.succeeded()) {
    7. // 获取响应
    8. HttpResponse<Buffer> response = ar.result();
    9. System.out.println("Received response with status code" + response.statusCode());
    10. } else {
    11. System.out.println("Something went wrong " + ar.cause().getMessage());
    12. }
    13. });
    14. //发送HEAD请求
    15. client
    16. .head(8080, "myserver.mycompany.com", "/some-uri")
    17. .send(ar -> {
    18. if (ar.succeeded()) {
    19. // 获取响应
    20. HttpResponse<Buffer> response = ar.result();
    21. System.out.println("Received response with status code" + response.statusCode());
    22. } else {
    23. System.out.println("Something went wrong " + ar.cause().getMessage());
    24. }
    25. });

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

    1. client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .addQueryParam("param", "param_value")
    4. .send(ar -> {});

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

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

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

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

    填充请求体

    例如用 方法发送一个缓冲体:

    1. client
    2. .post(8080, "myserver.mycompany.com", "/some-uri")
    3. .sendBuffer(buffer, ar -> {
    4. if (ar.succeeded()) {
    5. // Ok
    6. }
    7. });

    有时候我们并不希望将所有数据一次性全部读入内存,因为文件太大或希望同时处理多个请求,希望每个请求仅使用最小的内存。出于此目的,Web Client可用 sendStream 方法发送流式数据 ReadStream<Buffer>(例如 便是一个 ReadStream<Buffer>):

    1. client
    2. .post(8080, "myserver.mycompany.com", "/some-uri")
    3. .sendStream(stream, resp -> {});

    Web Client会为您设置好传输泵以平滑传输流。如果流长度未知则使用分块传输(chunked transfer)。

    如已知流的大小,可在HTTP协议头中设置 content-length 属性

    此时POST方法不会使用分块传输。

    JSON体

    有时您需要在请求体中使用JSON格式,可使用 方法发送 JsonObject

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

    在Java,Groovy以及Kotlin语言中,您亦可使用 方法发送POJO(Plain Old Java Object),该方法会自动调用 Json.encode 方法将 POJO 映射为 JSON:

    1. .post(8080, "myserver.mycompany.com", "/some-uri")
    2. .sendJson(new User("Dale", "Cooper"), ar -> {
    3. if (ar.succeeded()) {
    4. // Ok
    5. }
    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, ar -> {
    8. // Ok
    9. }
    10. });

    缺省情况下,提交表单的请求头中的 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. // 用分块方式编码提交表单
    5. client
    6. .post(8080, "myserver.mycompany.com", "/some-uri")
    7. .putHeader("content-type", "multipart/form-data")
    8. .sendForm(form, ar -> {
    9. if (ar.succeeded()) {
    10. // Ok
    11. }
    12. });

    您可使用以下方式填充请求头:

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

    此处 Headers 是一个 对象,提供了增加、设置以及删除头属性操作的入口。HTTP头的某些特定属性允许设置多个值。

    您亦可通过 putHeader 方法写入头属性:

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

    重用请求

    方法可被重复多次调用,这使得配置以及重用 HttpRequest 对象变得更加便捷:

    1. HttpRequest<Buffer> get = client.get(8080, "myserver.mycompany.com", "/some-uri");
    2. get.send(ar -> {
    3. if (ar.succeeded()) {
    4. // Ok
    5. }
    6. });
    7. // 再次发送同样的请求
    8. get.send(ar -> {
    9. if (ar.succeeded()) {
    10. // Ok
    11. }
    12. });

    请注意, 对象是可变的。
    所以在修改缓存中的对象之前,您应当使用 copy 方法先复制一份拷贝:

    1. HttpRequest<Buffer> get = client.get(8080, "myserver.mycompany.com", "/some-uri");
    2. get.send(ar -> {
    3. if (ar.succeeded()) {
    4. // Ok
    5. }
    6. });
    7. // 获取同样的请求
    8. get.copy()
    9. .putHeader("an-header", "with-some-value")
    10. .send(ar -> {
    11. if (ar.succeeded()) {
    12. // Ok
    13. }

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

    1. client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .timeout(5000)
    4. .send(ar -> {
    5. if (ar.succeeded()) {
    6. // Ok
    7. } else {
    8. // 此处可填入超时处理部分代码
    9. }
    10. });

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

    Web Client请求发送之后,返回的结果将会被包装在异步结果 HttpResponse 中。

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

    1. client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .send(ar -> {
    4. if (ar.succeeded()) {
    5. HttpResponse<Buffer> response = ar.result();
    6. System.out.println("Received response with status code" + response.statusCode());
    7. } else {
    8. System.out.println("Something went wrong " + ar.cause().getMessage());
    9. }
    10. });

    响应编解码器

    缺省状况下,响应以缓冲形式提供,并不提供任何形式的解码。

    可用 BodyCodec 将响应定制成以下类型:

    • 普通字符串
    • JSON对象
    • 将JSON映射成POJO

    响应体编解码器对二进制数据流解码,以节省您在响应处理中的代码。

    使用 BodyCodec.jsonObject 将结果解码为JSON对象:

    在Java,Groovy以及Kotlin语言中,JSON对象可被解码映射成POJO:

    1. client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .as(BodyCodec.json(User.class))
    4. .send(ar -> {
    5. if (ar.succeeded()) {
    6. HttpResponse<User> response = ar.result();
    7. User user = response.body();
    8. System.out.println("Received response with status code" + response.statusCode() + " with body " +
    9. user.getFirstName() + " " + user.getLastName());
    10. } else {
    11. System.out.println("Something went wrong " + ar.cause().getMessage());
    12. }
    13. });

    当响应结果较大时,请使用 方法。响应体编解码器将响应结果压入 WriteStream 并在最后发出成功或失败的信号。

    1. .get(8080, "myserver.mycompany.com", "/some-uri")
    2. .as(BodyCodec.pipe(writeStream))
    3. .send(ar -> {
    4. if (ar.succeeded()) {
    5. HttpResponse<Void> response = ar.result();
    6. System.out.println("Received response with status code" + response.statusCode());
    7. } else {
    8. System.out.println("Something went wrong " + ar.cause().getMessage());
    9. }
    10. });

    最后,如您对响应结果不感兴趣,可用 BodyCodec.none 废弃响应体。

    1. client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .as(BodyCodec.none())
    4. .send(ar -> {
    5. if (ar.succeeded()) {
    6. HttpResponse<Void> response = ar.result();
    7. System.out.println("Received response with status code" + response.statusCode());
    8. } else {
    9. System.out.println("Something went wrong " + ar.cause().getMessage());
    10. }
    11. });

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

    1. client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .send(ar -> {
    4. if (ar.succeeded()) {
    5. HttpResponse<Buffer> response = ar.result();
    6. // 将结果解码为Json对象
    7. JsonObject body = response.bodyAsJsonObject();
    8. System.out.println("Received response with status code" + response.statusCode() + " with body " + body);
    9. } else {
    10. System.out.println("Something went wrong " + ar.cause().getMessage());
    11. }
    12. });

    缺省状况下,客户端将会依照30x状态码自动重定向,您可使用 予以配置:

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

    客户端将会执行最多达16次重定向,该参数亦可在 WebClientOptions 配置:

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

    使用HTTPS

    Vert.x Web Client可用与 HttpClient 相同方式配置HTTPS协议。

    您可对每个请求单独设置:

    1. client
    2. .get(443, "myserver.mycompany.com", "/some-uri")
    3. .ssl(true)
    4. .send(ar -> {
    5. if (ar.succeeded()) {
    6. // 获取响应
    7. HttpResponse<Buffer> response = ar.result();
    8. System.out.println("Received response with status code" + response.statusCode());
    9. } else {
    10. System.out.println("Something went wrong " + ar.cause().getMessage());
    11. }
    12. });

    或使用绝对路径:

    1. client
    2. .getAbs("https://myserver.mycompany.com:4043/some-uri")
    3. .send(ar -> {
    4. if (ar.succeeded()) {
    5. // 获取响应
    6. HttpResponse<Buffer> response = ar.result();
    7. System.out.println("Received response with status code" + response.statusCode());
    8. } else {
    9. System.out.println("Something went wrong " + ar.cause().getMessage());
    10. }
    11. });

    RxJava API

    RxJava的 HttpRequest 提供了原版API的响应式版本, 方法返回一个可被订阅的 Single<HttpResponse<Buffer>>,故单个 Single 可被多次订阅。

    1. Single<HttpResponse<Buffer>> single = client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .rxSend();
    4. // 发送一次请求,并处理其响应,rx通常通过订阅触发各种响应
    5. single.subscribe(response -> {
    6. System.out.println("Received 1st response with status code" + response.statusCode());
    7. }, error -> {
    8. System.out.println("Something went wrong " + error.getMessage());
    9. });
    10. // 再次发送请求
    11. single.subscribe(response -> {
    12. System.out.println("Received 2nd response with status code" + response.statusCode());
    13. }, error -> {
    14. System.out.println("Something went wrong " + error.getMessage());
    15. });

    获取到的 Single 可与其它RxJava API自然组合成链式处理

    1. Single<String> url = client
    2. .get(8080, "myserver.mycompany.com", "/some-uri")
    3. .rxSend()
    4. .map(HttpResponse::bodyAsString);
    5. // 用flatMap将返回值内的链接作为参数传入lambda,在lambda中将其设置成发送请求,并返回Single,在下一步订阅中予以触发
    6. url
    7. .flatMap(u -> client.getAbs(u).rxSend())
    8. .subscribe(response -> {
    9. System.out.println("Received response with status code" + response.statusCode());
    10. }, error -> {
    11. System.out.println("Something went wrong " + error.getMessage());
    12. });

    之前的例子可写成

    当发送请求体为 Observable<Buffer> 时,应使用 sendStream

    1. Observable<Buffer> body = getPayload();
    2. Single<HttpResponse<Buffer>> single = client
    3. .post(8080, "myserver.mycompany.com", "/some-uri")
    4. .rxSendStream(body);
    5. single.subscribe(resp -> {
    6. System.out.println(resp.statusCode());
    7. System.out.println(resp.body());

    当订阅时,body 将会被订阅,其内容将会被用于请求中。