-
Libro clásico de Crazy Maker Circle: "Netty Zookeeper Redis Combate real de alta concurrencia" Entrevista Esencial + Entrevista Esencial + Entrevista Esencial [ Entrada general al jardín del blog ]
-
Libros clásicos de Crazy Maker Circle: "SpringCloud, Nginx High Concurrency Core Programming" Fundamentos para los principales fabricantes + Fundamentos para los principales fabricantes + Fundamentos para los principales fabricantes [ Entrada general de Blog Park ]
-
Una comunidad de alta concurrencia necesaria para ingresar a una gran fábrica + aumentar los salarios: [ Blog Park General Entrance ]
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
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