-
Livro clássico do Crazy Maker Circle: "Netty Zookeeper Redis High Concurrency Actual Combat" Entrevista essencial + Entrevista essencial + Entrevista essencial [ Entrada geral do jardim do blog ]
-
Crazy Maker Circle Classic Books: "SpringCloud, Nginx High Concurrency Core Programming" Essentials para os principais fabricantes + Essentials para os principais fabricantes + Essentials para os principais fabricantes [ Blog Park General Entrance ]
-
Uma comunidade de alta concorrência necessária para entrar em uma grande fábrica + aumento de salários: [ Entrada Geral do Blog Park ]
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
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