WebClient (el más completo de la historia)


Recomendación: la serie de entornos de desarrollo más sólida en la superficie

Un trabajador debe primero afilar sus herramientas si quiere hacerlo bien
El entorno de desarrollo más sólido de la superficie: vagrant + java + springcloud + redis + descarga del espejo del guardián del zoológico (y producción detallada)
La implementación en caliente más fuerte en la superficie: java SpringBoot SpringCloud implementación en caliente carga en caliente depuración en caliente
La herramienta de solicitud más sólida en la superficie (adiós, PostMan): IDEA HTTP Client (el más completo de la historia)
El dispositivo PPT más potente de la superficie: Diao Zhentian, escribe un código tipo PPT
¡Sin programación, sin creador, sin programación, sin creador, una gran ola de maestros de programación se están comunicando y aprendiendo en el círculo de los creadores locos!

Recomendación: serie de microservicios springCloud

Lectura recomendada
combate real nacos (el mas completo de la historia)
centinela (el tutorial introductorio más completo de la historia)
springcloud + webflux combate de alta concurrencia
Webflux (el más completo de la historia)
Gateway SpringCloud (el más completo de la historia)
¡Sin programación, sin creador, sin programación, sin creador, una gran ola de maestros de programación se están comunicando y aprendiendo en el círculo de los creadores locos!

1. ¿Qué es WebClient?

Spring WebFlux incluye la respuesta de WebClient a las solicitudes HTTP, sin bloqueo. El cliente y el servidor de WebFlux se basan en el mismo códec sin bloqueo para codificar y decodificar el contenido de la solicitud y la respuesta.

WebClient delega internamente a la biblioteca de cliente HTTP. De forma predeterminada, WebClient usa Reactor Netty , con soporte incorporado para Jetty reactivo HttpClient, y se pueden conectar otros ClientHttpConnector.

Método 1: crear una instancia de WebClient receptiva a través de un método de fábrica estático

La forma más sencilla de crear un WebClient es a través de uno de los métodos de fábrica estática:

  • WebClient.create ()

  • WebClient.create (String baseUrl)

por ejemplo: un ejemplo de solicitud de descanso utilizando 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);
    
    .... 省略其他源码
    
    }
    
}

El método anterior usa HttpClient con Reactor Netty con la configuración predeterminada y espera que io.projectreactor.netty: reactor-netty esté en la ruta de clases.

También puede utilizar otras opciones de WebClient.builder ():

  • uriBuilderFactory: se utiliza una UriBuilderFactory personalizada como URL base (BaseUrl).
  • defaultHeader: el encabezado de cada solicitud.
  • defaultCookie: Cookie para cada solicitud.
  • defaultRequest: el consumidor personaliza cada solicitud.
  • filter: filtro de cliente para cada solicitud.
  • exchangeStrategies: personalización del lector / escritor de mensajes HTTP.
  • clientConnector: configuración de la biblioteca de cliente HTTP.

Método 2: use el generador (constructor) para crear una instancia de WebClient receptiva

        //方式二:使用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 petición

obtener solicitud

    /**
     * 测试用例
     */
    @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: clonación de instancias de WebClient

Una vez establecida, la instancia de WebClient es inmutable. Sin embargo, puede clonarlo y crear una copia modificada sin afectar la instancia original, como se muestra en el siguiente ejemplo:

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

Extraiga la baseUrl pública

Si las URL a las que se va a acceder son todas de la misma aplicación, pero corresponden a diferentes direcciones URL, puede extraer la parte pública y definirla como baseUrl en este momento, y luego solo especificar la parte URL relativa a baseUrl al realizar una solicitud de WebClient .
La ventaja de esto es que puede modificar solo un lugar cuando es necesario cambiar su baseUrl.

El siguiente código define baseUrl como http: // localhost: 8081 al crear WebClient, y especifica la URL como / user / 1 al iniciar una solicitud Get, pero la URL a la que se accede realmente es 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 Solicitud de envío

Enviar solicitud de obtención

   /**
     * 测试用例: 发送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 cuerpo Json

El tipo de mime de la aplicación del cuerpo de la solicitud / 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);

ejemplo:

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

El tipo de mime de la aplicación del cuerpo de la solicitud / x-www-form-urlencoded

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

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


o

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

ejemplo:

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

subir archivos

El tipo mime del cuerpo de la solicitud "multipart / form-data";

ejemplo:

  @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 manejo de errores

  • Puede utilizar onStatus para realizar una adaptación anormal de acuerdo con el código de estado

  • Puede usar la adaptación de excepción doOnError

  • Puede usar onErrorReturn para volver al valor predeterminado

  /**
     * 测试用例: 错误处理
     */
    @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 decodificación de respuesta

Hay dos formas de manejar la respuesta:

  • recuperar

    El método de recuperación consiste en obtener directamente el cuerpo de la respuesta.

  • intercambio

    Sin embargo, si necesita información de encabezado de respuesta, cookies, etc., puede utilizar el método de intercambio, que puede acceder a toda la ClientResponse.

Asíncrono a síncrono :

Dado que la respuesta es asincrónica, puede llamar al método de bloqueo para bloquear el programa actual y esperar el resultado de la respuesta.

4.1 recuperar

El método retrieve () es la forma más sencilla de obtener el cuerpo de la respuesta y decodificarlo. El siguiente ejemplo muestra cómo hacer esto:

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

De forma predeterminada, las respuestas con códigos de estado 4XX o 5xx dan como resultado WebClientResponseException o una de sus subclases específicas de estado HTTP, como WebClientResponseException.BadRequest, WebClientResponseException.NotFound y otros. También puede utilizar el método onStatus para personalizar la excepción generada

4.2 intercambio ()

El método exchange () proporciona más control de recuperación que este método. El siguiente ejemplo es equivalente a retrieve () pero también proporciona acceso a ClientResponse:

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

Tenga en cuenta (a diferencia de retrieve ()) que para exchange (), no hay una señal de error automática para las respuestas 4xx y 5xx. Debe verificar el código de estado y decidir cómo proceder.
En comparación con retrieve (), cuando se usa exchange (), la aplicación es responsable de usar cualquier contenido de respuesta, independientemente de la situación (éxito, error, datos inesperados, etc.); de lo contrario, provocará una pérdida de memoria.

Por ejemplo: el siguiente ejemplo usa el intercambio para obtener ClientResponse y juzga el bit de estado:


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

cuerpo de respuesta Transformar el flujo de respuesta

Convertir el cuerpo de la respuesta en un objeto / colección

  • bodyToMono

    Si el resultado devuelto es un Objeto, WebClient convertirá la cadena JSON en el objeto correspondiente después de recibir la respuesta y lo mostrará a través de la secuencia Mono.

  • bodyToFlux

    Si el resultado de la respuesta es una colección, no puede continuar usando bodyToMono (), debe usar bodyToFlux () en su lugar, y luego procesar cada elemento a su vez y sacarlo a través del flujo Flux.

5 Filtrado de solicitudes y respuestas

WebClient también proporciona Filter, que corresponde a la interfaz org.springframework.web.reactive.function.client.ExchangeFilterFunction, y su método de interfaz se define de la siguiente manera.

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

Al interceptar, puede interceptar la solicitud y la respuesta.

Agregar autenticación 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();

Utilice un filtro para filtrar la respuesta:

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

Utilice filtros para registrar registros de solicitudes:

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

referencia:

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

Supongo que te gusta

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