是一个异步的非阻塞的客户端, client和server都依赖同一套codec进行编码和解码.
4.1 配置
可以通过以下静态方法创建:
WebClient.create();
WebClient.create(String baseUrl);可以通过WebClient.builder来创建, 可以配置这些属性:
uriBuilderFactory: 通过这个来配置BaseUrl;
- dafaultHeader;
- defaultCookie
- defaultRequest;
- filter;
- exchangeStategies: http消息reader/ writer自定义
- clientConnector: HTTP client library settings
通过exchangeStrategis可以配置http codec
一旦webclient被创建, 它就是不可变的, 不过可以通过创建另外一个实例来重新指定属性:
WebClient client1 = WebClient.builder()
.filter(filterA).filter(filterB).build();
WebClient client2 = client1.mutate()
.filter(filterC).filter(filterD).build();Reactor Netty
如果要自定义这个的属性, 比如说配置ssl等, 只需要提供一个配置好的httpClient给webClient实例:
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();1 配置Resource
这里的Resource比如说有event loop线程, 连接池等. 默认的配置是在应用里全局共享.
配置为非全局共享的例子:
@Bean
public ReactorResourceFactory resourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setGlobalResources(false);
return factory;
}
@Bean
public WebClient webClient() {
Function<HttpClient, HttpClient> mapper = client -> {
// Further customizations...
};
ClientHttpConnector connector =
new ReactorClientHttpConnector(resourceFactory(), mapper);
return WebClient.builder().clientConnector(connector).build();
}2 配置Timeouts
方式1:
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000));方式2, 分别配置读timeout和写timeout:
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10))));已jetty作为httpclient的底层时配置jetty:
HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);
ClientHttpConnector connector = new JettyClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().clientConnector(connector).build();4.2 retrieve方法
用来获取请求的响应:
WebClient client = WebClient.create("https://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);获取响应流:
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);默认4xx或者5xx状态将导致一个异常, 对于其他状态码, 可以用onStatus方法来指定它们的处理:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);当onStatus指定的处理器被触发时, 即使处理器不处理这个响应, 这个响应也会自动被消费以保证资源的释放.
4.3 exchange()方法
相对于retrieve方法, 可以进行更加多的控制
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(Person.class));exchange方法, 不会自动处理4xx或者5xx状态码, 需要应用自己来决定怎么处理这些异常;
当使用exchange方法时, 必须调用CLientResponse的bodyxx或者toEntity方法来保证连接等资源可以被释放. 假如不期望有响应, 可以使用bodyToMono(Void.class).
4.4 请求体的构造
以json做为请求体, 发送一个实体的例子:
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);发送一个对象流的例子:
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);假如持有一个实际值(而不是Mono或者FLux这种当前可能不存在, 在未来某个时间可以通过这个东西来获取的这种抽象), 可以这样发送:
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(person)
.retrieve()
.bodyToMono(Void.class);form data的发送:
使用MultiValueMap<String, String> 作为body, 这种body会被FormHttpMessgeWriter自动转才能够appliction/ x-www-form-urlencoded格式.
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(formData)
.retrieve()
.bodyToMono(Void.class);可以用流畅风格来配置formbody
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);Multipart Data
通过 MultipartBodyBuilder来创建请求体:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
MultiValueMap<String, HttpEntity<?>> parts = builder.build();不需要指定Multipart中每一个部分的格式, 一般会自动检测, 比如说文件会根据扩张名来检测.
当然也可以通过传递一个MediaType显式指定.
通过sysnBody方法来发送一个MultipartBody:
MultipartBodyBuilder builder = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(builder.build())
.retrieve()
.bodyToMono(Void.class);流畅风格:
MultipartBodyBuilder builder = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.syncBody(builder.build())
.retrieve()
.bodyToMono(Void.class);4.5 clientFilter
示例如下:
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();一般用作一些横切逻辑, base authentication的例子:
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "password"))
.build();filter将作用在所有请求上, 假如不想这样, 官方推荐的方法是set这请求的属性, 根据这个属性, filter执行不同的策略:
WebClient client = WebClient.builder()
.filter((request, next) -> {
Optional<Object> usr = request.attribute("myAttribute");
// ...
})
.build();
client.get().uri("https://example.org/")
.attribute("myAttribute", "...")
.retrieve()
.bodyToMono(Void.class);
}重新配置filter:
// static import of ExchangeFilterFunctions.basicAuthentication
WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();web同步的使用方法
如下
Person person = client.get().uri("/person/{id}", i).retrieve()
.bodyToMono(Person.class)
.block();
List<Person> persons = client.get().uri("/persons").retrieve()
.bodyToFlux(Person.class)
.collectList()
.block();如果有多个调用, 这种方式是低效的. 多个调用的推荐方式:
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
.retrieve().bodyToMono(Person.class);
Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
.retrieve().bodyToFlux(Hobby.class).collectList();
Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
Map<String, String> map = new LinkedHashMap<>();
map.put("person", personName);
map.put("hobbies", hobbies);
return map;
})
.block();未完待续...
后续会分享sample项目。