Spring 5 WebClient

Spring 5 WebClient

1. обзор

В этой статье мы собираемся показатьWebClient - реактивный веб-клиент, представленный в Spring 5.

Мы также собираемся взглянуть наWebTestClient - этоWebClient, предназначенное для использования в тестах.

Дальнейшее чтение:

Spring WebClient Filters

Узнайте о фильтрах WebClient в Spring WebFlux

Read more

Spring WebClient Запросы с параметрами

Узнайте, как реактивно использовать конечные точки REST API с помощью WebClient из Spring Webflux.

Read more

2. Что такоеWebClient?

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

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

Наконец, интерфейс имеет единственную реализацию - классDefaultWebClient, с которым мы и будем работать.

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

Поскольку мы используем приложение Spring Boot, нам нужна зависимостьspring-boot-starter-webflux, а такжеthe Reactor project.

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("http://localhost:8080");

Последний способ (и самый продвинутый) - это создание клиента с использованием классаDefaultWebClientBuilder, который допускает полную настройку:

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

4.2. Подготовка запроса

Во-первых, нам нужно указать HTTP-метод запроса, вызвавmethod(HttpMethod method) или вызывая его методы быстрого доступа, такие какget,post,delete:

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

Следующим шагом будет предоставление URL. Мы можем передать его в APIuri - как экземплярString илиjava.net.URL:

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

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

Двигаясь дальше, мы можем установить тело запроса, тип контента, длину, куки или заголовки - если нам нужно.

Например, если мы хотим установить тело запроса - есть два доступных способа - заполнить егоBodyInserter или делегировать эту работуPublisher:

WebClient.RequestHeadersSpec requestSpec1 = WebClient
  .create()
  .method(HttpMethod.POST)
  .uri("/resource")
  .body(BodyInserters.fromPublisher(Mono.just("data")), String.class);

WebClient.RequestHeadersSpec requestSpec2 = WebClient
  .create("http://localhost:8080")
  .post()
  .uri(URI.create("/resource"))
  .body(BodyInserters.fromObject("data"));

The BodyInserter is an interface responsible for populating a ReactiveHttpOutputMessage body with a given output message and a context used during the insertion. APublisher - это реактивный компонент, который отвечает за предоставление потенциально неограниченного количества упорядоченных элементов.

Второй способ - это методbody, который является сокращением для исходного методаbody(BodyInserter inserter).

Чтобы облегчить этот процесс заполненияBodyInserter,, существует классBodyInserters с рядом полезных служебных методов:

BodyInserter, ReactiveHttpOutputMessage> 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());

После того, как мы установили тело, мы можем установить заголовки, куки, приемлемые типы носителей. Values will be added to those have been set when instantiating the client.с

Кроме того, имеется дополнительная поддержка наиболее часто используемых заголовков, например“If-None-Match”, “If-Modified-Since”, “Accept”, “Accept-Charset”..

Вот пример использования этих значений:

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.3. Получение ответа

Завершающим этапом является отправка запроса и получение ответа. Это можно сделать с помощью методовexchange илиretrieve.

Они различаются типами возврата; методexchange предоставляетClientResponse вместе с его статусом и заголовками, тогда как методretrieve - это кратчайший путь к непосредственному извлечению тела:

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

Обратите внимание на методbodyToMono, который выдастWebClientException, если код состояния равен4xx (ошибка клиента) или5xx (ошибка сервера). Мы использовали методblock дляMonos, чтобы подписаться и получить фактические данные, которые были отправлены с ответом.

5. Работа сWebTestClient

WebTestClient - это основная точка входа для тестирования конечных точек сервера WebFlux. У него очень похожий API наWebClient, и он делегирует большую часть работы внутреннему экземпляруWebClient, уделяя основное внимание обеспечению тестового контекста. КлассDefaultWebTestClient - это реализация единого интерфейса.

Клиент для тестирования может быть привязан к реальному серверу или работать с конкретными контроллерами или функциями. Чтобы выполнить тесты сквозной интеграции с фактическими запросами к работающему серверу, мы можем использовать методbindToServer:

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

Мы можем протестировать конкретныйRouterFunction, передав его методуbindToRouterFunction:

RouterFunction function = RouterFunctions.route(
  RequestPredicates.GET("/resource"),
  request -> ServerResponse.ok().build()
);

WebTestClient
  .bindToRouterFunction(function)
  .build().get().uri("/resource")
  .exchange()
  .expectStatus().isOk()
  .expectBody().isEmpty();

Такого же поведения можно добиться с помощью методаbindToWebHandler, который принимает экземплярWebHandler:

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

Более интересная ситуация возникает, когда мы используем методbindToApplicationContext. Он принимаетApplicationContext, анализирует контекст для компонентов контроллера и конфигураций@EnableWebFlux.

Если мы введем экземплярApplicationContext, простой фрагмент кода может выглядеть так:

@Autowired
private ApplicationContext context;

WebTestClient testClient = WebTestClient.bindToApplicationContext(context)
  .build();

Более короткий подход заключался бы в предоставлении массива контроллеров, которые мы хотим протестировать методомbindToController. Предполагая, что у нас есть классController и мы внедрили его в необходимый класс, мы можем написать:

@Autowired
private Controller controller;

WebTestClient testClient = WebTestClient.bindToController(controller).build();

После создания объектаWebTestClient все последующие операции в цепочке будут похожи на методWebClient вплоть до методаexchange (один из способов получить ответ), который предоставляетWebTestClient.ResponseSpec интерфейс для работы с полезными методами, такими какexpectStatus,expectBody,expectHeader:

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

6. Заключение

В этом руководстве мы рассмотрели новый улучшенный механизм Spring для выполнения запросов на стороне клиента - классWebClient.

Кроме того, мы рассмотрели преимущества, которые он дает, пройдя весь процесс обработки запросов.

Все фрагменты кода, упомянутые в статье, можно найти вour GitHub repository.