Vert.x Web Client(Web客戶端)是一個異步的 HTTP 和 HTTP/2 客戶端。html
Web Client使得發送 HTTP 請求以及從 Web 服務器接收 HTTP 響應變得更加便捷,同時提供了額外的高級功能,例如:java
JSON體的編碼和解碼git
請求和響應泵github
請求參數的處理web
統一的錯誤處理json
提交表單api
製做Web Client的目的並不是爲了替換Vert.x Core中的 HttpClient
,而是基於該客戶端,擴展並保留其便利的設置和特性,例如請求鏈接池(Pooling),HTTP/2的支持,流水線/管線的支持等。當您須要對 HTTP 請求和響應作細微粒度控制時,您應當使用 HttpClient
。緩存
另外Web Client並未提供 WebSocket API,此時您應當使用 HttpClient
。服務器
如需使用Vert.x Web Client,請先加入如下依賴:app
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 Web Client使用Vert.x Core的API,如您對此還不熟悉,請先熟悉 HttpClient
的一些基本概念。
您可以使用缺省設置建立一個 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¶m2=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¶m2=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格式,可以使用 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 { // 此處可填入超時處理部分代碼 } });
若請求在設定時間內沒返回任何數據,則一個超時異常將會傳遞給響應處理代碼。
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
將響應定製成如下類型:
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狀態碼自動重定向,您可以使用 WebClientOptions
予以配置:
WebClient client = WebClient.create(vertx, new WebClientOptions().setFollowRedirects(false));
客戶端將會執行最多達16
次重定向,該參數亦可在 WebClientOptions
配置:
WebClient client = WebClient.create(vertx, new WebClientOptions().setMaxRedirects(5));
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的 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
將會被訂閱,其內容將會被用於請求中。