Пролет 5 WebClient

1. Общ преглед

В този урок ще разгледаме WebClient , който е реактивен уеб клиент, представен през пролетта 5.

Ние също така ще изглежда по WebTestClient, а WebClient предназначен да се използва в изследвания.

2. Какво представлява WebClient ?

Просто казано, WebClient е интерфейс, представляващ основната входна точка за изпълнение на уеб заявки.

Той е създаден като част от модула Spring Web Reactive и ще замени класическия RestTemplate в тези сценарии. Освен това новият клиент е реактивно, неблокиращо решение, което работи по протокола HTTP / 1.1.

И накрая, интерфейсът има една реализация, класът DefaultWebClient , с който ще работим.

3. Зависимости

Тъй като използваме приложението Spring Boot, имаме нужда от зависимостта spring-boot-starter-webflux , както и от проекта Reactor.

3.1. Сграда с Maven

Нека добавим следните зависимости към файла pom.xml :

 org.springframework.boot spring-boot-starter-webflux   org.projectreactor reactor-spring 1.0.1.RELEASE 

3.2. Сграда с Gradle

С Gradle трябва да добавим следните записи към файла build.gradle :

dependencies { compile 'org.springframework.boot:spring-boot-starter-webflux' compile 'org.projectreactor:reactor-spring:1.0.1.RELEASE' }

4. Работа с WebClient

За да работим правилно с клиента, трябва да знаем как да:

  • създайте екземпляр
  • обръщам се с молба
  • се справят с отговора

4.1. Създаване на WebClient съд

Има три възможности за избор. Първият е създаването на обект WebClient с настройки по подразбиране:

WebClient client1 = WebClient.create(); 

Вторият вариант е да се инициира екземпляр на WebClient с даден основен URI:

WebClient client2 = WebClient.create("//localhost:8080"); 

Третата опция (и най-напредналата) е изграждането на клиент с помощта на класа DefaultWebClientBuilder , който позволява пълна персонализация:

WebClient client3 = WebClient .builder() .baseUrl("//localhost:8080") .defaultCookie("cookieKey", "cookieValue") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultUriVariables(Collections.singletonMap("url", "//localhost:8080")) .build();

4.2. Създаване на екземпляр на WebClient с изчаквания

Често HTTP изчакванията по подразбиране от 30 секунди са твърде бавни за нашите нужди, така че нека да видим как да ги конфигурираме за нашия екземпляр WebClient .

Основният клас, който използваме, е TcpClient.

Там можем да зададем времето за изчакване на връзката чрез стойността на ChannelOption.CONNECT_TIMEOUT_MILLIS . Също така можем да зададем времето за изчакване за четене и запис, като използваме ReadTimeoutHandler и WriteTimeoutHandler , съответно:

TcpClient tcpClient = TcpClient .create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .doOnConnected(connection -> { connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)); connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)); }); WebClient client = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient))) .build();

Имайте предвид, че макар да можем да извикаме и изчакване при заявка на клиента, това е изчакване на сигнала, а не HTTP връзка или изчакване за четене / запис; това е изчакване за издателя Mono / Flux.

4.3. Подготовка на заявка

Първо трябва да посочим HTTP метод на заявка чрез извикване на метод (метод HttpMethod) или извикване на методите му за бърз достъп, като get , post и delete :

WebClient.UriSpec request1 = client3.method(HttpMethod.POST); WebClient.UriSpec request2 = client3.post();

Следващата стъпка е да предоставите URL адрес. Можем да го предадем на API на uri като String или екземпляр на java.net.URL :

WebClient.RequestBodySpec uri1 = client3 .method(HttpMethod.POST) .uri("/resource"); WebClient.RequestBodySpec uri2 = client3 .post() .uri(URI.create("/resource"));

След това можем да зададем тяло на заявката, тип съдържание, дължина, бисквитки или заглавки, ако трябва.

Например, ако искаме да зададем тяло на заявка, има два налични начина: да го запълним с BodyInserter или да делегираме това произведение на издател :

WebClient.RequestHeadersSpec requestSpec1 = WebClient .create() .method(HttpMethod.POST) .uri("/resource") .body(BodyInserters.fromPublisher(Mono.just("data")), String.class); WebClient.RequestHeadersSpec requestSpec2 = WebClient .create("//localhost:8080") .post() .uri(URI.create("/resource")) .body(BodyInserters.fromObject("data"));

В BodyInserter е интерфейс, който отговаря за попълващ ReactiveHttpOutputMessage тяло с дадена продукция съобщение и контекст, използван по време на вмъкване. А Издател е реактивен компонент, който е отговорен за осигуряване на потенциално неограничена брой подреждат елементи.

Вторият начин е методът body , който е пряк път за оригиналния метод body (BodyInserter inserter) .

За да се облекчи процесът на попълване на BodyInserter, има клас BodyInserters с редица полезни полезни методи:

BodyInserter
    
      inserter1 = BodyInserters .fromPublisher(Subscriber::onComplete, String.class); 
    

Възможно е и с MultiValueMap :

LinkedMultiValueMap map = new LinkedMultiValueMap(); map.add("key1", "value1"); map.add("key2", "value2"); BodyInserter inserter2 = BodyInserters.fromMultipartData(map); 

Или чрез използване на един обект:

BodyInserter inserter3 = BodyInserters.fromObject(new Object()); 

След като зададем тялото, можем да зададем заглавки, бисквитки и приемливи типове носители. Стойностите ще бъдат добавени към тези, които вече са зададени при създаването на клиент.

Освен това има допълнителна поддръжка за най-често използваните заглавки като „If-None-Match“, „If-Modified-Since“, „Accept“ и „Accept-Charset“.

Here's an example of how these values can be used:

WebClient.ResponseSpec response1 = uri1 .body(inserter3) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) .acceptCharset(Charset.forName("UTF-8")) .ifNoneMatch("*") .ifModifiedSince(ZonedDateTime.now()) .retrieve();

4.4. Getting a Response

The final stage is sending the request and receiving a response. This can be done with either the exchange or the retrieve method.

These methods differ in return types; the exchange method provides a ClientResponse along with its status and headers, while the retrieve method is the shortest path to fetching a body directly:

String response2 = request1.exchange() .block() .bodyToMono(String.class) .block(); String response3 = request2 .retrieve() .bodyToMono(String.class) .block();

It's important to pay attention to the bodyToMono method, which will throw a WebClientException if the status code is 4xx (client error) or 5xx (server error). We use the block method on Monos to subscribe and retrieve actual data that was sent with the response.

5. Working with the WebTestClient

The WebTestClient is the main entry point for testing WebFlux server endpoints. It has a very similar API to the WebClient, and it delegates most of the work to an internal WebClient instance focusing mainly on providing a test context. The DefaultWebTestClient class is a single interface implementation.

The client for testing can be bound to a real server or work with specific controllers or functions.

5.1. Binding to a Server

To complete end-to-end integration tests with actual requests to a running server, we can use the bindToServer method:

WebTestClient testClient = WebTestClient .bindToServer() .baseUrl("//localhost:8080") .build(); 

5.2. Binding to a Router

We can test a particular RouterFunction by passing it to the bindToRouterFunction method:

RouterFunction function = RouterFunctions.route( RequestPredicates.GET("/resource"), request -> ServerResponse.ok().build() ); WebTestClient .bindToRouterFunction(function) .build().get().uri("/resource") .exchange() .expectStatus().isOk() .expectBody().isEmpty(); 

5.3. Binding to a Web Handler

The same behavior can be achieved with the bindToWebHandler method, which takes a WebHandler instance:

WebHandler handler = exchange -> Mono.empty(); WebTestClient.bindToWebHandler(handler).build();

5.4. Binding to an Application Context

A more interesting situation occurs when we're using the bindToApplicationContext method. It takes an ApplicationContext and analyses the context for controller beans and @EnableWebFlux configurations.

If we inject an instance of the ApplicationContext, a simple code snippet may look like this:

@Autowired private ApplicationContext context; WebTestClient testClient = WebTestClient.bindToApplicationContext(context) .build(); 

5.5. Binding to a Controller

A shorter approach would be providing an array of controllers we want to test by the bindToController method. Assuming we've got a Controller class and we injected it into a needed class, we can write:

@Autowired private Controller controller; WebTestClient testClient = WebTestClient.bindToController(controller).build(); 

5.6. Making a Request

After building a WebTestClient object, all following operations in the chain are going to be similar to the WebClient until the exchange method (one way to get a response), which provides the WebTestClient.ResponseSpec interface to work with useful methods like the expectStatus, expectBody, and expectHeader:

WebTestClient .bindToServer() .baseUrl("//localhost:8080") .build() .post() .uri("/resource") .exchange() .expectStatus().isCreated() .expectHeader().valueEquals("Content-Type", "application/json") .expectBody().isEmpty(); 

6. Conclusion

В тази статия разгледахме WebClient, нов подобрен пролетен механизъм за отправяне на заявки от страна на клиента.

Също така разгледахме предимствата, които предоставя, като преминахме през конфигуриране на клиента, подготовка на заявката и обработка на отговора.

Всички кодови фрагменти, споменати в статията, могат да бъдат намерени в нашето хранилище GitHub.