Рубрики
Без рубрики

Веб-клиент Spring 5

Откройте для себя веб – клиент Spring 5-новую альтернативу реактивной RestTemplate.

Автор оригинала: baeldung.

1. Обзор

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

Мы также рассмотрим клиент Web Test, a Web Client , предназначенный для использования в тестах.

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

Фильтры веб-клиента Spring

Запросы веб-клиента Spring с параметрами

2. Что такое Веб-клиент?

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

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

Важно отметить, что, хотя это, по сути, неблокирующий клиент и он принадлежит к библиотеке spring-webflux , решение предлагает поддержку как синхронных, так и асинхронных операций, что делает его подходящим также для приложений, работающих в стеке сервлетов.

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

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

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

Поскольку мы используем приложение Spring Boot, все, что нам нужно,-это зависимость spring-boot-starter-web flux для получения реактивной веб-поддержки Spring Framework.

3.1. Построение с помощью Maven

Давайте добавим следующие зависимости в pom.xml файл:


    org.springframework.boot
    spring-boot-starter-webflux

3.2. Строительство с помощью Gradle

С помощью Gradle нам нужно добавить следующие записи в файл build.gradle :

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-webflux'
}

4. Работа с веб-клиентом

Чтобы правильно работать с клиентом, мы должны знать, как:

  • создание экземпляра
  • сделайте запрос
  • обработайте ответ

4.1. Создание экземпляра WebClient

Есть три варианта на выбор. Первый-это создание объекта WebClient с настройками по умолчанию:

WebClient client = WebClient.create();

Второй вариант-инициировать экземпляр WebClient с заданным базовым URI:

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

Третий вариант (и самый продвинутый) – это создание клиента с помощью класса Default Web Client Builder , который позволяет полностью настраивать:

WebClient client = 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. Создание экземпляра WebClient с тайм-аутами

Часто таймауты HTTP по умолчанию в 30 секунд слишком медленны для наших нужд, чтобы настроить это поведение, мы можем создать экземпляр HttpClient и настроить наш WebClient для его использования.

Мы можем:

  • установите тайм – аут соединения с помощью установите тайм – аут соединения с помощью вариант
  • установите таймауты чтения и записи с помощью ReadTimeoutHandler и WriteTimeoutHandler , соответственно
  • настройте тайм-аут ответа с помощью тайм-аут ответа директива

Как мы уже говорили, все они должны быть указаны в экземпляре HttpClient , который мы настроим:

HttpClient httpClient = HttpClient.create()
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
  .responseTimeout(Duration.ofMillis(5000))
  .doOnConnected(conn -> 
    conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
      .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));

WebClient client = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(httpClient))
  .build();

Обратите внимание, что хотя мы также можем вызвать timeout по запросу нашего клиента, это тайм-аут сигнала, а не HTTP-соединение, чтение/запись или тайм-аут ответа; это тайм-аут для издателя Mono/Flux.

4.3. Подготовка запроса – Определение метода

Во-первых, нам нужно указать HTTP-метод запроса, вызвав метод (метод HttpMethod):

UriSpec uriSpec = client.method(HttpMethod.POST);

Или вызов его методов быстрого доступа, таких как get , post и delete :

UriSpec uriSpec = client.post();

Примечание: хотя может показаться, что мы повторно используем переменные спецификации запроса ( WebClient.Uri Spec , WebClient.Спецификация тела запроса , WebClient.RequestHeadersSpec , WebClient. Спецификация ответа ), это просто для простоты, чтобы представить различные подходы. Эти директивы не должны повторно использоваться для различных запросов, они извлекают ссылки, и поэтому последние операции изменят определения, которые мы сделали на предыдущих шагах.

4.4. Подготовка запроса – Определение URL-адреса

Следующий шаг-предоставить URL-адрес. Опять же, у нас есть разные способы сделать это.

Мы можем передать его в uri API в виде строки :

RequestBodySpec bodySpec = uriSpec.uri("/resource");

Использование функции UriBuilder :

RequestBodySpec bodySpec = uriSpec.uri(
  uriBuilder -> uriBuilder.pathSegment("/resource").build());

Или как java.net.URL экземпляр:

RequestBodySpec bodySpec = uriSpec.uri(URI.create("/resource"));

Имейте в виду , что если бы мы определили базовый URL-адрес по умолчанию для WebClient , этот последний метод переопределил бы это значение.

4.5. Подготовка запроса – Определение органа

Затем мы можем задать тело запроса, тип контента, длину, файлы cookie или заголовки, если это необходимо.

Например, если мы хотим задать тело запроса, есть несколько доступных способов. Вероятно, наиболее распространенным и простым вариантом является использование метода body Value :

RequestHeadersSpec headersSpec = bodySpec.bodyValue("data");

Или путем представления Publisher (и типа элементов, которые будут опубликованы) методу body :

RequestHeadersSpec headersSpec = bodySpec.body(
  Mono.just(new Foo("name")), Foo.class);

В качестве альтернативы мы можем использовать класс Body Inserters utility. Например, давайте посмотрим, как мы можем заполнить тело запроса, используя простой объект, как мы сделали с значением тела методом :

RequestHeadersSpec headersSpec = bodySpec.body(
  BodyInserters.fromValue("data"));

Аналогично, мы можем использовать метод Body Inserters#from Publisher , если мы используем экземпляр реактора:

RequestHeadersSpec headersSpec = bodySpec.body(
  BodyInserters.fromPublisher(Mono.just("data")),
  String.class);

Этот класс также предлагает другие интуитивно понятные функции для более сложных сценариев. Например, в случае, если нам придется отправлять составные запросы:

LinkedMultiValueMap map = new LinkedMultiValueMap();
map.add("key1", "value1");
map.add("key2", "value2");
RequestHeadersSpec headersSpec = bodySpec.body(
  BodyInserters.fromMultipartData(map));

Все эти методы создают экземпляр Body Inserter , который затем мы можем представить как body запроса.

То Корпус Вставлен является ли интерфейс ответственным за заполнение Реактивный HttpOutputMessage тело с заданным выходным сообщением и контекстом, используемым при вставке.

A Publisher – это реактивный компонент, отвечающий за предоставление потенциально неограниченного количества упорядоченных элементов. Это интерфейс, и наиболее популярными реализациями являются Mono и Flux.

4.6. Подготовка запроса – Определение заголовков

После установки тела мы можем установить заголовки, файлы cookie и приемлемые типы носителей. Значения будут добавлены к тем, которые уже были заданы при создании экземпляра клиента.

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

Вот пример того, как можно использовать эти значения:

ResponseSpec responseSpec = headersSpec.header(
    HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
  .acceptCharset(StandardCharsets.UTF_8)
  .ifNoneMatch("*")
  .ifModifiedSince(ZonedDateTime.now())
  .retrieve();

4.7. Получение ответа

Заключительный этап-отправка запроса и получение ответа. Мы можем достичь этого, используя либо метод exchange To Mono/exchange To Flux , либо метод retrieve .

Методы exchange To Mono и exchange To Flux позволяют получить доступ к ответу клиента вместе с его статусом и заголовками:

Mono response = headersSpec.exchangeToMono(response -> {
  if (response.statusCode()
    .equals(HttpStatus.OK)) {
      return response.bodyToMono(String.class);
  } else if (response.statusCode()
    .is4xxClientError()) {
      return Mono.just("Error response");
  } else {
      return response.createException()
        .flatMap(Mono::error);
  }
});

В то время как метод retrieve является кратчайшим путем к прямой выборке тела:

Mono response = headersSpec.retrieve()
  .bodyToMono(String.class);

Важно обратить внимание на ResponseSpec. тело В метод Mono , который вызовет исключение веб-клиента , если код состояния 4xx (ошибка клиента) или 5xx (ошибка сервера).

5. Работа с клиентом веб-тестирования

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

Клиент для тестирования может быть привязан к реальному серверу или работать с определенными контроллерами или функциями.

5.1. Привязка к Серверу

Для выполнения сквозных интеграционных тестов с фактическими запросами к работающему серверу мы можем использовать метод привязка к серверу :

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

5.2. Привязка к маршрутизатору

Мы можем протестировать конкретную функцию Маршрутизатора , передав ее в метод привязка к функции маршрутизатора :

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. Привязка к веб-обработчику

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

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

5.4. Привязка к контексту приложения

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

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

@Autowired
private ApplicationContext context;

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

5.5. Привязка к контроллеру

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

@Autowired
private Controller controller;

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

5.6. Подача запроса

После создания объекта 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().jsonPath("field").isEqualTo("value");

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

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

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

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