1. 程式人生 > >Vert.x學習之 Web Client

Vert.x學習之 Web Client

失敗 put esp 相同 轉換 之前 any 版本 targe

Vert.x Web Client

  • 原文檔
  • 組件源碼
  • 組件示例

中英對照表

  • Pump:泵(平滑流式數據讀入內存的機制,防止一次性將大量數據讀入內存導致內存溢出)
  • Response Codec:響應編解碼器(編碼及解碼工具)
  • Body Codec:響應體編解碼器

組件介紹

Vert.x Web Client(Web客戶端)是一個異步的 HTTP 和 HTTP/2 客戶端。

Web Client使得發送 HTTP 請求以及從 Web 服務器接收 HTTP 響應變得更加便捷,同時提供了額外的高級功能,例如:

  • JSON體的編碼和解碼

  • 請求和響應泵

  • 請求參數的處理

  • 統一的錯誤處理

  • 提交表單

制作Web Client的目的並非為了替換Vert.x Core中的 HttpClient,而是基於該客戶端,擴展並保留其便利的設置和特性,例如請求連接池(Pooling),HTTP/2的支持,流水線/管線的支持等。當您需要對 HTTP 請求和響應做細微粒度控制時,您應當使用 HttpClient

另外Web Client並未提供 WebSocket API,此時您應當使用 HttpClient

使用Web Client

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

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

    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-web-client</artifactId>
      <version>3.4.2</version>
    </dependency>
  • Gradle(在build.gradle文件中):

    dependencies {
      compile ‘io.vertx:vertx-web-client:3.4.2‘
    }

對Vert.x Core HTTP Client的回顧

Vert.x Web Client使用Vert.x Core的API,如您對此還不熟悉,請先熟悉 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);

發送請求

無請求體的簡單請求

一般情況下,HTTP GET,OPTIONS以及HEAD請求沒有請求體,可用以下方式發送無請求體的HTTP Requests(HTTP請求):

WebClient client = WebClient.create(vertx);

// 發送GET請求
client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send(ar -> {
    if (ar.succeeded()) {
      // 獲取響應
      HttpResponse<Buffer> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

//發送HEAD請求
client
  .head(8080, "myserver.mycompany.com", "/some-uri")
  .send(ar -> {
    if (ar.succeeded()) {
      // 獲取響應
      HttpResponse<Buffer> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

您可用以下鏈式方式向請求URI添加查詢參數

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

在請求URI中的參數將會被預填充

HttpRequest<Buffer> request = client.get(8080, "myserver.mycompany.com", "/some-uri?param1=param1_value&param2=param2_value");

// 添加param3(參數3)
request.addQueryParam("param3", "param3_value");

// 覆蓋param2(參數2)
request.setQueryParam("param2", "another_param2_value");

設置請求URI將會自動清除已有的查詢參數

HttpRequest<Buffer> request = client.get(8080, "myserver.mycompany.com", "/some-uri");

// 添加param1(參數1)
request.addQueryParam("param1", "param1_value");

// 覆蓋param1(參數1)同時新增param2(參數2)
request.uri("/some-uri?param1=param1_value&param2=param2_value");

填充請求體

如需要發送請求體,可使用相同的API並在最後加上 sendXXX 方法發送相應的請求體。

例如用 sendBuffer 方法發送一個緩沖體:

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendBuffer(buffer, ar -> {
    if (ar.succeeded()) {
      // Ok
    }
  });
client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendBuffer(buffer, ar -> {
    if (ar.succeeded()) {
      // Ok
    }
  });

有時候我們並不希望將所有數據一次性全部讀入內存,因為文件太大或希望同時處理多個請求,希望每個請求僅使用最小的內存。出於此目的,Web Client可用 sendStream 方法發送流式數據 ReadStream<Buffer>(例如 AsyncFile 便是一個 ReadStream<Buffer>):

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

Web Client會為您設置好傳輸泵以平滑傳輸流。如果流長度未知則使用分塊傳輸(chunked transfer)。

如已知流的大小,可在HTTP協議頭中設置 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, ar -> {
        if (ar.succeeded()) {
          // Ok
        }
      });
  }
});

此時POST方法不會使用分塊傳輸。

JSON體

有時您需要在請求體中使用JSON格式,可使用 sendJsonObject 方法發送 JsonObject

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendJsonObject(new JsonObject()
    .put("firstName", "Dale")
    .put("lastName", "Cooper"), ar -> {
    if (ar.succeeded()) {
      // Ok
    }
  });

在Java,Groovy以及Kotlin語言中,您亦可使用 sendJson 方法發送POJO(Plain Old Java Object),該方法會自動調用 Json.encode 方法將 POJO 映射為 JSON:

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendJson(new User("Dale", "Cooper"), ar -> {
    if (ar.succeeded()) {
      // 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, ar -> {
    if (ar.succeeded()) {
      // Ok
    }
  });

缺省情況下,提交表單的請求頭中的 content-type 屬性值為 application/x-www-form-urlencoded,您亦可將其設置為 multipart/form-data

MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");

// 用分塊方式編碼提交表單
client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .putHeader("content-type", "multipart/form-data")
  .sendForm(form, ar -> {
    if (ar.succeeded()) {
      // Ok
    }
  });

請註意:當前版本並不支持分塊文件編碼(multipart files,即文件上傳),該功能可能在將來版本中予以支持。

填充請求頭

您可使用以下方式填充請求頭:

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頭的某些特定屬性允許設置多個值。

您亦可通過 putHeader 方法寫入頭屬性:

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

重用請求

send 方法可被重復多次調用,這使得配置以及重用 HttpRequest 對象變得更加便捷:

HttpRequest<Buffer> get = client.get(8080, "myserver.mycompany.com", "/some-uri");
get.send(ar -> {
  if (ar.succeeded()) {
    // Ok
  }
});

// 再次發送同樣的請求
get.send(ar -> {
  if (ar.succeeded()) {
    // Ok
  }
});

請註意,HttpRequest 對象是可變的。 所以在修改緩存中的對象之前,您應當使用 copy 方法先復制一份拷貝:

HttpRequest<Buffer> get = client.get(8080, "myserver.mycompany.com", "/some-uri");
get.send(ar -> {
  if (ar.succeeded()) {
    // Ok
  }
});

// 獲取同樣的請求
get.copy()
  .putHeader("an-header", "with-some-value")
  .send(ar -> {
  if (ar.succeeded()) {
    // Ok
  }

超時

您可通過 timeout 方法設置超時時間。

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .timeout(5000)
  .send(ar -> {
    if (ar.succeeded()) {
      // Ok
    } else {
      // 此處可填入超時處理部分代碼
    }
  });

若請求在設定時間內沒返回任何數據,則一個超時異常將會傳遞給響應處理代碼。

處理HTTP響應

Web Client請求發送之後,返回的結果將會被包裝在異步結果 HttpResponse 中。

當響應被成功接收到之後,相應的回調函數將會被觸發。

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send(ar -> {
    if (ar.succeeded()) {

      HttpResponse<Buffer> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

警告:缺省狀況下,響應會被完全緩沖讀入內存,請用 BodyCodec.pipe 方法將響應寫入流。

響應編解碼器

缺省狀況下,響應以緩沖形式提供,並不提供任何形式的解碼。

可用 BodyCodec 將響應定制成以下類型:

  • 普通字符串
  • JSON對象
  • 將JSON映射成POJO
  • WriteStream

響應體編解碼器對二進制數據流解碼,以節省您在響應處理中的代碼。

使用 BodyCodec.jsonObject 將結果解碼為JSON對象:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.jsonObject())
  .send(ar -> {
    if (ar.succeeded()) {
      HttpResponse<JsonObject> response = ar.result();

      JsonObject body = response.body();

      System.out.println("Received response with status code" + response.statusCode() + " with body " + body);
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

在Java,Groovy以及Kotlin語言中,JSON對象可被解碼映射成POJO:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.json(User.class))
  .send(ar -> {
    if (ar.succeeded()) {
      HttpResponse<User> response = ar.result();

      User user = response.body();

      System.out.println("Received response with status code" + response.statusCode() + " with body " +
        user.getFirstName() + " " + user.getLastName());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

當響應結果較大時,請使用 BodyCodec.pipe 方法。響應體編解碼器將響應結果壓入 WriteStream 並在最後發出成功或失敗的信號。

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.pipe(writeStream))
  .send(ar -> {
    if (ar.succeeded()) {

      HttpResponse<Void> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

最後,如您對響應結果不感興趣,可用 BodyCodec.none 廢棄響應體。

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.none())
  .send(ar -> {
    if (ar.succeeded()) {

      HttpResponse<Void> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

若無法預知響應內容類型,您依舊可以在獲取結果之後,用 bodyAsXXX() 方法將其轉換成特定的類型

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send(ar -> {
    if (ar.succeeded()) {

      HttpResponse<Buffer> response = ar.result();

      // 將結果解碼為Json對象
      JsonObject body = response.bodyAsJsonObject();

      System.out.println("Received response with status code" + response.statusCode() + " with body " + body);
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

警告:這種方式僅對響應結果為緩沖體有效。

處理30x重定向

缺省狀況下,客戶端將會依照30x狀態碼自動重定向,您可使用 WebClientOptions 予以配置:

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

客戶端將會執行最多達16次重定向,該參數亦可在 WebClientOptions 配置:

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

使用HTTPS

Vert.x Web Client可用與 HttpClient 相同方式配置HTTPS協議。

您可對每個請求單獨設置:

client
  .get(443, "myserver.mycompany.com", "/some-uri")
  .ssl(true)
  .send(ar -> {
    if (ar.succeeded()) {
      // 獲取響應
      HttpResponse<Buffer> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

或使用絕對路徑:

client
  .getAbs("https://myserver.mycompany.com:4043/some-uri")
  .send(ar -> {
    if (ar.succeeded()) {
      // 獲取響應
      HttpResponse<Buffer> response = ar.result();

      System.out.println("Received response with status code" + response.statusCode());
    } else {
      System.out.println("Something went wrong " + ar.cause().getMessage());
    }
  });

RxJava API

RxJava的 HttpRequest 提供了原版API的響應式版本,rxSend 方法返回一個可被訂閱的 Single<HttpResponse<Buffer>>,故單個 Single 可被多次訂閱。

Single<HttpResponse<Buffer>> single = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .rxSend();

// 發送一次請求,並處理其響應,rx通常通過訂閱觸發各種響應
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將返回值內的鏈接作為參數傳入lambda,在lambda中將其設置成發送請求,並返回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());
  });

之前的例子可寫成

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());
});

當發送請求體為 Observable<Buffer> 時,應使用 sendStream

Observable<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 將會被訂閱,其內容將會被用於請求中。

Vert.x學習之 Web Client