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:
<dependency><groupId>io.vertx</groupId><artifactId>vertx-web-client</artifactId><version>4.1.5</version></dependency>
- Gradle :
dependencies {compile 'io.vertx:vertx-web-client:4.1.5'}
创建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请求。以下是一般情况下的 HTTP GET, OPTIONS和HEAD 请求
WebClient client = WebClient.create(vertx);// 发送GET请求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()));// 发送HEAD请求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");// 添加 param3request.addQueryParam("param3", "param3_value");// 覆盖 param2request.setQueryParam("param2", "another_param2_value");
设置请求URI将会自动清除已有的查询参数
HttpRequest<Buffer> request = client.get(8080, "myserver.mycompany.com", "/some-uri");// 添加 param1request.addQueryParam("param1", "param1_value");// 覆盖 param1 并添加 param2request.uri("/some-uri?param1=param1_value¶m2=param2_value");
填充请求体
如需要发送请求体,可使用相同的API,并在最后加上 sendXXX 方法,发送相应的请求体。
使用 sendBuffer 发送一个buffer body
client.post(8080, "myserver.mycompany.com", "/some-uri").sendBuffer(buffer).onSuccess(res -> {// OK});
发送single buffer很有用,但是通常您不想完全将内容加载到内存中,因为它可能太大,或者您想同时处理多个请求,或者每个请求只想使用最小的(消耗)。 为此,Web Client可以使用 ReadStream<Buffer> 的(例如 AsyncFile 是一个ReadStream) sendStream 方法发送。
client.post(8080, "myserver.mycompany.com", "/some-uri").sendStream(stream).onSuccess(res -> {// OK});
Web Client负责为您设置泵传输(transfer pump)。如果流长度未知则使用分块传输(chunked transfer)编码。
当您知道流的大小,您应该在HTTP header中设置 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).onSuccess(res -> {// OK});}});
这个POST方法不会被分块传输。
JSON bodies
有时您需要发送JSON body请求,可使用 sendJsonObject 发送一个 JsonObject
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});
表单提交
您可以使用 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).onSuccess(res -> {// OK});
默认情况下,提交表单header中的 content-type 属性值为 application/x-www-form-urlencoded,您还可将其替换为 multipart/form-data:
MultiMap form = MultiMap.caseInsensitiveMultiMap();form.set("firstName", "Dale");form.set("lastName", "Cooper");// 提交multipart form表单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");// 提交multipart form表单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");
此处 Headers 是一个 MultiMap 实例,提供了添加、设置以及删除头属性操作的入口。HTTP headers允许某个特定的key有多个值。
您还可使用 putHeader 写入headers属性:
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().onSuccess(res -> {// OK});// 又一些请求get.send().onSuccess(res -> {// OK});
不过要当心 HttpRequest 实例是可变的(mutable). 因此,您应该在修改已被缓存了的实例之前,使用 copy 方法。
HttpRequest<Buffer> get = client.get(8080, "myserver.mycompany.com", "/some-uri");get.send().onSuccess(res -> {// OK});// "get" 请求实例保持未修改get.copy().putHeader("a-header", "with-some-value").send().onSuccess(res -> {// OK});
超时
您可通过 timeout 。方法设置超时时间。
client.get(8080, "myserver.mycompany.com", "/some-uri").timeout(5000).send().onSuccess(res -> {// OK}).onFailure(err -> {// 当是由java.util.concurrent.TimeoutException导致时,或许是一个超时});
若请求在设定时间内没有返回任何数据,则一个异常将会传递给响应处理器。
处理HTTP响应
Web Client请求发送之后,您总是在单个 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()));
响应解码
缺省状况下,Web Client提供一个response body作为 Buffer ,并且未运用任何解码器。
可以使用 BodyCodec 实现以下自定义response body解码:
- 文本字符串
- Json 对象
- Json 映射的 POJO
WriteStream
一个body解码器可以将任意二进制数据流解码为特定的对象实例,从而节省了您自己在响应处理器里解码的步骤。
使用 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()));
这个编码器将响应缓存泵入到 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可以提供一个推文回馈。处理这个情况,您可以使用 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 简单的丢弃response body。
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()));
若无法预知响应内容类型,您依旧可以在获取结果之后,用 bodyAsXXX() 方法将其转换成指定的类型
client.get(8080, "myserver.mycompany.com", "/some-uri").send().onSuccess(res -> {// 将结果解码为Json对象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请求才以错误结尾。
换言之, 您必须在收到响应后手动执行健全性检查:
client.get(8080, "myserver.mycompany.com", "/some-uri").send().onSuccess(res -> {if (res.statusCode() == 200 &&res.getHeader("content-type").equals("application/json")) {// 将结果解码为Json对象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 predicates 。
Response predicates 当响应不符合条件会使请求失败。
Web Client附带了一组开箱即用的谓词,可供使用:
client.get(8080, "myserver.mycompany.com", "/some-uri").expect(ResponsePredicate.SC_SUCCESS).expect(ResponsePredicate.JSON).send().onSuccess(res -> {// 安全地将body解码为json对象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()));
当现有谓词不满足您的需求时,您还可以创建自定义谓词:
Function<HttpResponse<Void>, ResponsePredicateResult> methodsPredicate =resp -> {String methods = resp.getHeader("Access-Control-Allow-Methods");if (methods != null) {if (methods.contains("POST")) {return ResponsePredicateResult.success();}}return ResponsePredicateResult.failure("Does not work");};// 发送预检CORS请求client.request(HttpMethod.OPTIONS,8080,"myserver.mycompany.com","/some-uri").putHeader("Origin", "Server-b.com").putHeader("Access-Control-Request-Method", "POST").expect(methodsPredicate).send().onSuccess(res -> {// 立即处理POST请求}).onFailure(err ->System.out.println("Something went wrong " + err.getMessage()));
预定义谓词
为了方便起见,Web Client附带了一些常见用例的谓词。
对于状态码,例如 ResponsePredicate.SC_SUCCESS ,验证响应具有 2xx 代码,您也可以自定义创建一个
client.get(8080, "myserver.mycompany.com", "/some-uri").expect(ResponsePredicate.status(200, 202)).send().onSuccess(res -> {// ....});
对于content types,例如 ResponsePredicate.JSON ,验证响应具有JSON数据,您也可以自定义创建一个
client.get(8080, "myserver.mycompany.com", "/some-uri").expect(ResponsePredicate.contentType("some/content-type")).send().onSuccess(res -> {// ....});
创建自定义失败
默认情况下,响应谓词(包括预定义的)使用默认的错误转换器,它将丢弃body并传递一条简单消息。您可以通过自定义异常类来替换错误转换器:
ResponsePredicate predicate = ResponsePredicate.create(ResponsePredicate.SC_SUCCESS,result -> new MyCustomException(result.message()));
为避免丢失此信息,在错误发生之前,可以在转换器被调用之前等待响应body被完全接收:
ErrorConverter converter = ErrorConverter.createFullBody(result -> {// 响应body被完全接收之后调用HttpResponse<Buffer> response = result.response();if (response.getHeader("content-type").equals("application/json")) {// 错误body是JSON数据JsonObject body = response.bodyAsJsonObject();return new MyCustomException(body.getString("code"),body.getString("message"));}// 返回自定义的消息return new MyCustomException(result.message());});ResponsePredicate predicate = ResponsePredicate.create(ResponsePredicate.SC_SUCCESS, converter);
处理 30x 重定向
默认情况下,客户端跟随着重定向,您可以在 WebClientOptions 配置默认行为:
WebClient client = WebClient.create(vertx, new WebClientOptions().setFollowRedirects(false));
客户端最多可以跟随 16 个请求重定向,可以在相同的配置中进行更改:
WebClient client = WebClient.create(vertx, new WebClientOptions().setMaxRedirects(5));
使用 HTTPS
Vert.x Web Client 可以用跟 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 。
创建一个 WebSession
您像下面一样创建一个 WebClientSession 实例
WebClient client = WebClient.create(vertx);WebClientSession session = WebClientSession.create(client);
发出请求
一旦创建, WebClientSession 可以代替 WebClient 去做HTTP(s) 请求并且自动管理你正在调用的,从服务器收到的所有cookie。
设置会话级别headers
您可以按以下步骤设置任何会话级别的headers到要添加的每个请求:
WebClientSession session = WebClientSession.create(client);session.addHeader("my-jwt-token", jwtToken);
然后headers将被添加到每个请求中; 注意 这些headers将发送给所有主机;如果你需要发送不同的headers到不同的主机, 您必须将它们手动添加到每个单个请求中,并且不添加到 WebClientSession 。
