WebClient (the most complete in history)


Recommendation: The strongest development environment series on the surface

A worker must first sharpen his tools if he wants to do well
The strongest development environment on the surface: vagrant+java+springcloud+redis+zookeeper mirror download (& detailed production)
The strongest hot deployment on the surface: java SpringBoot SpringCloud hot deployment hot loading hot debugging
The strongest requesting tool on the surface (goodbye, PostMan): IDEA HTTP Client (the most complete in history)
The strongest PPT gadget on the surface: Diao Zhentian, write PPT like code
No programming, no maker, no programming, no maker, a large wave of programming masters are communicating and learning in the crazy maker circle! Find an organization, GO

Recommendation: springCloud microservice series

Recommended reading
nacos actual combat (the most complete in history)
sentinel (the most complete + introductory tutorial in history)
springcloud + webflux high concurrency combat
Webflux (the most complete in history)
SpringCloud gateway (the most complete in history)
No programming, no maker, no programming, no maker, a large wave of programming masters are communicating and learning in the crazy maker circle! Find an organization, GO

1. What is WebClient

Spring WebFlux includes WebClient's response to HTTP requests, non-blocking. The WebFlux client and server rely on the same non-blocking codec to encode and decode the request and response content.

WebClient internally delegates to the HTTP client library. By default, WebClient uses Reactor Netty , with built-in support for Jetty reactive HttpClient, and others can be plugged in ClientHttpConnector.

Method 1: Create a responsive WebClient instance through a static factory method

The easiest way to create a WebClient is through one of the static factory methods:

  • WebClient.create()

  • WebClient.create (String baseUrl)

eg: An example of Rest request using Webclient (Responsive HttpClient)

package com.crazymaker.springcloud.reactive.rpc.mock;

import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import java.io.IOException;

public class WebClientDemo
{

    /**
     * 测试用例
     */
    @Test
    public void testCreate() throws IOException
    {

        //响应式客户端
        WebClient client = null;

        WebClient.RequestBodySpec request = null;

        String baseUrl = "http://crazydemo.com:7700/demo-provider/";
        client = WebClient.create(baseUrl);

        /**
         * 是通过 WebClient 组件构建请求
         */
        String restUrl = baseUrl + "api/demo/hello/v1";
        request = client
                // 请求方法
                .method(HttpMethod.GET)
                // 请求url 和 参数
//                .uri(restUrl, params)
                .uri(restUrl)
                // 媒体的类型
                .accept(MediaType.APPLICATION_JSON);
    
    .... 省略其他源码
    
    }
    
}

The above method uses HttpClient with Reactor Netty with default settings and expects io.projectreactor.netty:reactor-netty to be on the classpath.

You can also use other options of WebClient.builder():

  • uriBuilderFactory: A custom UriBuilderFactory is used as the base URL (BaseUrl).
  • defaultHeader: The header of each request.
  • defaultCookie: Cookie for each request.
  • defaultRequest: Consumer customizes each request.
  • filter: Client filter for each request.
  • exchangeStrategies: HTTP message reader/writer customization.
  • clientConnector: HTTP client library settings.

Method 2: Use builder (constructor) to create a responsive WebClient instance

        //方式二:使用builder(构造者)创建响应式WebClient实例
        client = WebClient.builder()
                .baseUrl("https://api.github.com")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
                .build();

send request

get request

    /**
     * 测试用例
     */
    @Test
    public void testGet() throws IOException
    {
        String restUrl = baseUrl + "api/demo/hello/v1";

        Mono<String> resp = WebClient.create()
                .method(HttpMethod.GET)
                .uri(restUrl)
                .cookie("token", "jwt_token")
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .retrieve().bodyToMono(String.class);

        // 订阅结果
        resp.subscribe(responseData ->
        {
            log.info(responseData.toString());
        }, e ->
        {
            log.info("error:" + e.getMessage());
        });
        //主线程等待, 一切都是为了查看到异步结果
        ThreadUtil.sleepSeconds(1000);
    }

Method 3: WebClient instance cloning

Once established, the WebClient instance is immutable. However, you can clone it and build a modified copy without affecting the original instance, as shown in the following example:

WebClient client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build();

WebClient client2 = client1.mutate()
        .filter(filterC).filter(filterD).build();

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

Extract the public baseUrl

If the URLs to be accessed are all from the same application, but correspond to different URL addresses, you can extract the public part and define it as baseUrl at this time, and then only specify the URL part relative to baseUrl when making a WebClient request.
The advantage of this is that you can modify only one place when your baseUrl needs to be changed.

The following code defines baseUrl as http://localhost:8081 when creating WebClient, and specifies the URL as /user/1 when initiating a Get request, but the URL actually accessed is http://localhost:8081/user/ 1.

String baseUrl = "http://localhost:8081";

WebClient webClient = WebClient.create(baseUrl);

Mono<User> mono = webClient.get().uri("user/{id}", 1).retrieve().bodyToMono(User.class);

2 Request to submit

Send get request

   /**
     * 测试用例: 发送get请求
     */
    @Test
    public void testGet() throws IOException
    {
        String restUrl = baseUrl + "api/demo/hello/v1";

        Mono<String> resp = WebClient.create()
                .method(HttpMethod.GET)
                .uri(restUrl)
                .cookie("token", "jwt_token")
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .retrieve().bodyToMono(String.class);

        // 订阅结果
        resp.subscribe(responseData ->
        {
            log.info(responseData.toString());
        }, e ->
        {
            log.info("error:" + e.getMessage());
        });
        //主线程等待, 一切都是为了查看到异步结果
        ThreadUtil.sleepSeconds(1000);
    }

Submit Json Body

The mime type of the request body application/x-www-form-urlencoded

Mono<Person> personMono = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(personMono, Person.class)
        .retrieve()
        .bodyToMono(Void.class);

example:

 /**
     * 测试用例: 发送post 请求 mime为 application/json
     */
    @Test
    public void testJSONParam(){
        String restUrl = baseUrl + "api/demo/post/demo/v2";
        LoginInfoDTO dto=new LoginInfoDTO("lisi","123456");
        Mono<LoginInfoDTO> personMono =Mono.just(dto);

        Mono<String> resp = WebClient.create().post()
                .uri(restUrl)
                .contentType(MediaType.APPLICATION_JSON)
//                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(personMono,LoginInfoDTO.class)
                .retrieve().bodyToMono(String.class);

        // 订阅结果
        resp.subscribe(responseData ->
        {
            log.info(responseData.toString());
        }, e ->
        {
            log.info("error:" + e.getMessage());
        });
        //主线程等待, 一切都是为了查看到异步结果
        ThreadUtil.sleepSeconds(1000);
    }

submit Form

The mime type of the request body application/x-www-form-urlencoded

MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .bodyToMono(Void.class);


or

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

example:

   /**
     * 提交表单  mime类型 application/x-www-form-urlencoded
     *
     * @return RestOut
     */
//    @PostMapping("/post/demo/v1")
    @RequestMapping(value = "/post/demo/v1", method = RequestMethod.POST)
    @ApiOperation(value = "post请求演示")
    public RestOut<LoginInfoDTO> postDemo(@RequestParam String username, @RequestParam String password)
    {
        /**
         * 直接返回
         */
        LoginInfoDTO dto = new LoginInfoDTO();
        dto.setUsername(username);
        dto.setPassword(password);
        return RestOut.success(dto).setRespMsg("body的内容回显给客户端");
    }

upload files

The mime type of the request body "multipart/form-data";

example:

  @Test
    public void testUploadFile()
    {
        String restUrl = baseUrl + "/api/file/upload/v1";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.IMAGE_PNG);
        HttpEntity<ClassPathResource> entity = 
                new HttpEntity<>(new ClassPathResource("logback-spring.xml"), headers);
        MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
        parts.add("file", entity);
        Mono<String> resp = WebClient.create().post()
                .uri(restUrl)
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(BodyInserters.fromMultipartData(parts))
                .retrieve().bodyToMono(String.class);
        log.info("result:{}", resp.block());
    }

3 error handling

  • You can use onStatus to perform abnormal adaptation according to the status code

  • Can use doOnError exception adaptation

  • You can use onErrorReturn to return to the default value

  /**
     * 测试用例: 错误处理
     */
    @Test
    public void testFormParam4xx()
    {
        WebClient webClient = WebClient.builder()
                .baseUrl("https://api.github.com")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
                .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
                .build();
        WebClient.ResponseSpec responseSpec = webClient.method(HttpMethod.GET)
                .uri("/user/repos?sort={sortField}&direction={sortDirection}",
                        "updated", "desc")
                .retrieve();
        Mono<String> mono = responseSpec
                .onStatus(e -> e.is4xxClientError(), resp ->
                {
                    log.error("error:{},msg:{}", resp.statusCode().value(), resp.statusCode().getReasonPhrase());
                    return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase()));
                })
                .bodyToMono(String.class)
                .doOnError(WebClientResponseException.class, err ->
                {
                    log.info("ERROR status:{},msg:{}", err.getRawStatusCode(), err.getResponseBodyAsString());
                    throw new RuntimeException(err.getMessage());
                })
                .onErrorReturn("fallback");
        String result = mono.block();
        System.out.print(result);
    }

4 Response decoding

There are two ways to handle the response:

  • retrieve

    The retrieve method is to directly obtain the response body.

  • exchange

    However, if you need response header information, cookies, etc., you can use the exchange method, which can access the entire ClientResponse.

Asynchronous to synchronous :

Since the response is asynchronous, you can call the block method to block the current program and wait for the response result.

4.1 retrieve

The retrieve() method is the easiest way to get the response body and decode it. The following example shows how to do this:

Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, response -> ...)
        .onStatus(HttpStatus::is5xxServerError, response -> ...)
        .bodyToMono(Person.class);

By default, responses with 4XX or 5xx status codes result in WebClientResponseException or one of its specific subclasses of HTTP status, such as WebClientResponseException.BadRequest, WebClientResponseException.NotFound and others. You can also use the onStatus method to customize the exception generated

4.2 exchange()

The exchange() method provides more control retrieve than this method. The following example is equivalent to retrieve() but also provides access to ClientResponse:

ono<ResponseEntity<Person>> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .exchange()
        .flatMap(response -> response.toEntity(Person.class));

Please note (unlike retrieve()) that for exchange(), there is no automatic error signal for 4xx and 5xx responses. You must check the status code and decide how to proceed.
Compared with retrieve(), when using exchange(), the application is responsible for using any response content, regardless of the situation (success, error, unexpected data, etc.), otherwise it will cause a memory leak.

eg: The following example uses exchange to obtain ClientResponse and judges the status bit:


    /**
     * 测试用例: Exchange
     */
    @Test
    public void testExchange()
    {
        String baseUrl = "http://localhost:8081";
        WebClient webClient = WebClient.create(baseUrl);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("username", "u123");
        map.add("password", "p123");

        Mono<ClientResponse> loginMono = webClient.post().uri("login").syncBody(map).exchange();
        ClientResponse response = loginMono.block();
        if (response.statusCode() == HttpStatus.OK) {
            Mono<RestOut> resultMono = response.bodyToMono(RestOut.class);
            resultMono.subscribe(result -> {
                if (result.isSuccess()) {
                    ResponseCookie sidCookie = response.cookies().getFirst("sid");
                    Mono<LoginInfoDTO> dtoMono = webClient.get().uri("users").cookie(sidCookie.getName(), sidCookie.getValue()).retrieve().bodyToMono(LoginInfoDTO.class);
                    dtoMono.subscribe(System.out::println);
                }
            });
        }
    }

response body Transform the response stream

Convert response body to object/collection

  • bodyToMono

    If the returned result is an Object, WebClient will convert the JSON string into the corresponding object after receiving the response, and pop it out through the Mono stream.

  • bodyToFlux

    If the result of the response is a collection, you cannot continue to use bodyToMono(), you should use bodyToFlux() instead, and then process each element in turn and pop it out through the Flux stream.

5 Request and response filtering

WebClient also provides Filter, which corresponds to the org.springframework.web.reactive.function.client.ExchangeFilterFunction interface, and its interface method is defined as follows.

Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next)

When intercepting, you can intercept the request and the response.

Add basic authentication:

WebClient webClient = WebClient.builder()
    .baseUrl(GITHUB_API_BASE_URL)
    .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
    .filter(ExchangeFilterFunctions
            .basicAuthentication(username, token))
    .build();

Use a filter to filter the response:

 @Test
    void filter() {
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("p1", "var1");
        uriVariables.put("p2", 1);
        WebClient webClient = WebClient.builder().baseUrl("http://www.ifeng.com")
                .filter(logResposneStatus())
                .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
                .build();
        Mono<String> resp1 = webClient
                .method(HttpMethod.GET)
                .uri("/")
                .cookie("token","xxxx")
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .retrieve().bodyToMono(String.class);
        String re=  resp1.block();
        System.out.print("result:" +re);
 
    }
 
    private ExchangeFilterFunction logResposneStatus() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            log.info("Response Status {}", clientResponse.statusCode());
            return Mono.just(clientResponse);
        });
    }

Use filters to record request logs:

WebClient webClient = WebClient.builder()
    .baseUrl(GITHUB_API_BASE_URL)
    .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
    .filter(ExchangeFilterFunctions
            .basicAuthentication(username, token))
    .filter(logRequest())
    .build();

private ExchangeFilterFunction logRequest() {
    return (clientRequest, next) -> {
        logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers()
                .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
        return next.exchange(clientRequest);
    };
}

reference:

https://www.jb51.net/article/133384.htm

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client

https://www.jianshu.com/p/15d0a2bed6da

Guess you like

Origin blog.csdn.net/crazymakercircle/article/details/113550091