WebClient (o mais completo da história)


Recomendação: A série de ambiente de desenvolvimento mais forte na superfície

Um trabalhador deve primeiro afiar suas ferramentas se quiser ter um bom desempenho
O ambiente de desenvolvimento mais forte na superfície: vagrant + java + springcloud + redis + download do espelho do zookeeper (e produção detalhada)
A implementação ativa mais forte na superfície: java SpringBoot SpringCloud implantação quente carregamento quente depuração
A ferramenta de solicitação mais forte na superfície (adeus, PostMan): IDEA HTTP Client (o mais completo da história)
O gadget PPT mais forte na superfície: Diao Zhentian, escreva o código PPT
Sem programação, sem criador, sem programação, sem criador, uma grande onda de mestres de programação estão se comunicando e aprendendo no círculo louco de criadores! Encontre uma organização, GO

Recomendação: série de microsserviços springCloud

Leitura recomendada
Nacos de combate real (o mais completo da história)
sentinela (o tutorial + introdutório mais completo da história)
combate de alta concorrência springcloud + webflux
Webflux (o mais completo da história)
Gateway SpringCloud (o mais completo da história)
Sem programação, sem criador, sem programação, sem criador, uma grande onda de mestres de programação estão se comunicando e aprendendo no círculo louco de criadores! Encontre uma organização, GO

1. O que é WebClient

Spring WebFlux inclui a resposta do WebClient às solicitações HTTP, sem bloqueio. O cliente e o servidor WebFlux contam com o mesmo codec sem bloqueio para codificar e decodificar o conteúdo da solicitação e resposta.

WebClient delega internamente para a biblioteca cliente HTTP. Por padrão, o WebClient usa Reactor Netty , com suporte integrado para HttpClient reativo Jetty , e outros podem ser conectados ClientHttpConnector.

Método 1: crie uma instância responsiva do WebClient por meio de um método de fábrica estático

A maneira mais fácil de criar um WebClient é por meio de um dos métodos de fábrica estáticos:

  • WebClient.create ()

  • WebClient.create (String baseUrl)

por exemplo: Um exemplo de solicitação Rest usando 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);
    
    .... 省略其他源码
    
    }
    
}

O método acima usa HttpClient com Reactor Netty com configurações padrão e espera que io.projectreactor.netty: reactor-netty esteja no caminho de classe.

Você também pode usar outras opções de WebClient.builder ():

  • uriBuilderFactory: um UriBuilderFactory personalizado é usado como URL base (BaseUrl).
  • defaultHeader: o cabeçalho de cada solicitação.
  • defaultCookie: Cookie para cada solicitação.
  • defaultRequest: o consumidor personaliza cada solicitação.
  • filtro: filtro de cliente para cada solicitação.
  • exchangeStrategies: Personalização do leitor / gravador de mensagens HTTP.
  • clientConnector: configurações da biblioteca de cliente HTTP.

Método 2: use o construtor (construtor) para criar uma instância responsiva do WebClient

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

enviar pedido

obter pedido

    /**
     * 测试用例
     */
    @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);
    }

Método 3: clonagem da instância do WebClient

Uma vez estabelecida, a instância do WebClient é imutável. No entanto, você pode cloná-lo e criar uma cópia modificada sem afetar a instância original, conforme mostrado no exemplo a seguir:

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

Extraia o público baseUrl

Se os URLs a serem acessados ​​forem todos do mesmo aplicativo, mas corresponderem a diferentes endereços de URL, você pode extrair a parte pública e defini-la como baseUrl neste momento, e então especificar apenas a parte da URL relativa a baseUrl ao fazer uma solicitação WebClient .
A vantagem disso é que você pode modificar apenas um local quando seu baseUrl precisar ser alterado.

O código a seguir define baseUrl como http: // localhost: 8081 ao criar WebClient e especifica a URL como / user / 1 ao iniciar uma solicitação Get, mas a URL realmente acessada é 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 Solicitação de envio

Enviar pedido de obtenção

   /**
     * 测试用例: 发送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);
    }

Enviar corpo Json

O tipo MIME do corpo da solicitação 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);

exemplo:

 /**
     * 测试用例: 发送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);
    }

enviar o formulário

O tipo MIME do corpo da solicitação application / x-www-form-urlencoded

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

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


ou

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

exemplo:

   /**
     * 提交表单  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的内容回显给客户端");
    }

fazer upload de arquivos

O tipo MIME do corpo da solicitação "multipart / form-data";

exemplo:

  @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 tratamento de erros

  • Você pode usar onStatus para realizar uma adaptação anormal de acordo com o código de status

  • Pode usar adaptação de exceção doOnError

  • Você pode usar onErrorReturn para retornar ao valor padrão

  /**
     * 测试用例: 错误处理
     */
    @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 decodificação de resposta

Existem duas maneiras de lidar com a resposta:

  • recuperar

    O método de recuperação consiste em obter diretamente o corpo da resposta.

  • intercâmbio

    No entanto, se você precisar de informações de cabeçalho de resposta, cookies, etc., você pode usar o método de troca, que pode acessar todo o ClientResponse.

Assíncrono para síncrono :

Como a resposta é assíncrona, você pode chamar o método de bloco para bloquear o programa atual e aguardar o resultado da resposta.

4.1 recuperar

O método retrieve () é a maneira mais fácil de obter o corpo da resposta e decodificá-lo. O exemplo a seguir mostra como fazer isso:

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

Por padrão, as respostas com códigos de status 4XX ou 5xx resultam em WebClientResponseException ou uma de suas subclasses específicas de status HTTP, como WebClientResponseException.BadRequest, WebClientResponseException.NotFound e outros. Você também pode usar o método onStatus para personalizar a exceção gerada

4.2 troca ()

O método exchange () fornece mais recuperação de controle do que este método. O exemplo a seguir é equivalente a retrieve (), mas também fornece acesso a ClientResponse:

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

Observe (ao contrário de retrieve ()) que para exchange (), não há sinal de erro automático para respostas 4xx e 5xx. Você deve verificar o código de status e decidir como proceder.
Comparado com retrieve (), ao usar exchange (), o aplicativo é responsável por usar qualquer conteúdo de resposta, independentemente da situação (sucesso, erro, dados inesperados, etc.), caso contrário, causará um vazamento de memória.

por exemplo: O exemplo a seguir usa troca para obter ClientResponse e julga o bit de status:


    /**
     * 测试用例: 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);
                }
            });
        }
    }

corpo de resposta Transforme o fluxo de resposta

Converter o corpo da resposta em objeto / coleção

  • bodyToMono

    Se o resultado retornado for um objeto, WebClient converterá a string JSON no objeto correspondente depois de receber a resposta e a enviará através do fluxo Mono.

  • bodyToFlux

    Se o resultado da resposta for uma coleção, você não pode continuar a usar bodyToMono (), você deve usar bodyToFlux () em vez disso e, em seguida, processar cada elemento por vez e colocá-lo no fluxo do Flux.

5 Solicitação e filtragem de resposta

WebClient também fornece Filter, que corresponde à interface org.springframework.web.reactive.function.client.ExchangeFilterFunction, e seu método de interface é definido como segue.

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

Ao interceptar, você pode interceptar a solicitação e a resposta.

Adicionar autenticação básica:

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

Use um filtro para filtrar a resposta:

 @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 filtros para registrar logs de solicitação:

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

referência:

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

Acho que você gosta

Origin blog.csdn.net/crazymakercircle/article/details/113550091
Recomendado
Clasificación