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

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

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

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

1. Обзор

Многие фреймворки и проекты внедряют реактивное программирование и асинхронную обработку запросов|/. Следовательно, Spring 5 представила реализацию реактивного веб – клиента как часть Веб-потока фреймворка.

В этом руководстве мы увидим, как реактивно использовать конечные точки API REST с помощью WebClient .

2. Конечные точки API REST

Для начала давайте определим пример REST API с следующими точками доступа :

  • /товары – получить все товары
  • /товары/{id} – получить товар по идентификатору
  • /продукты/{идентификатор}/атрибуты/{Идентификатор атрибута} – получить атрибут продукта по идентификатору
  • /товары/?имя={имя}&Дата доставки={Дата доставки}&цвет={цвет} – поиск товаров
  • /товары/?тег[]={tag1}&тег[]={tag2} – получение товаров по тегам
  • /товары/?категория={категория1}&категория={категория2} – получить товары по категориям

Итак, мы только что определили несколько различных URI. Через мгновение мы выясним, как создавать и отправлять URL-адреса каждого типа с помощью WebClient .

Пожалуйста, обратите внимание, что URI для получения товаров по тегам и категориям содержат массивы в качестве параметров запроса. Однако синтаксис отличается. Поскольку нет строгого определения того, как массивы должны быть представлены в URI . В первую очередь это зависит от реализации на стороне сервера. Соответственно, мы рассмотрим оба случая.

3. Настройка веб-клиента

Сначала нам нужно создать экземпляр WebClient . Для этой статьи мы будем использовать издевательский объект , поскольку нам нужно просто убедиться, что запрошен действительный URI.

Давайте определим клиента и связанные с ним макетные объекты:

this.exchangeFunction = mock(ExchangeFunction.class);
ClientResponse mockResponse = mock(ClientResponse.class);
when(this.exchangeFunction.exchange(this.argumentCaptor.capture())).thenReturn(Mono.just(mockResponse));
this.webClient = WebClient
  .builder()
  .baseUrl("https://example.com/api")
  .exchangeFunction(exchangeFunction)
  .build();

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

Наконец, чтобы убедиться, что конкретный URL-адрес был передан базовому экземпляру Exchange , давайте воспользуемся следующим вспомогательным методом:

private void verifyCalledUrl(String relativeUrl) {
    ClientRequest request = this.argumentCaptor.getValue();
    Assert.assertEquals(String.format("%s%s", BASE_URL, relativeUrl), request.url().toString());
    Mockito.verify(this.exchangeFunction).exchange(request);
    verifyNoMoreInteractions(this.exchangeFunction);
}

Класс Конструктор веб-клиентов имеет метод uri () , который предоставляет экземпляр UriBuilder в качестве аргумента. Как правило, вызов API обычно выполняется следующим образом:

this.webClient.get()
  .uri(uriBuilder -> uriBuilder
    //... building a URI
    .build())
  .retrieve();

Мы будем широко использовать UriBuilder в этом руководстве для построения URI. Стоит отметить, что мы можем создать URI любым другим способом, а затем просто передать сгенерированный URL-адрес в виде строки.

4. Компонент пути URI

Компонент пути состоит из последовательности сегментов пути, разделенных косой чертой (/) . Во-первых, давайте начнем с простого случая, когда URI не имеет никаких переменных сегментов /продукты :

this.webClient.get()
  .uri("/products")
  .retrieve();
verifyCalledUrl("/products");

В этом случае мы можем просто передать Строку в качестве аргумента.

Далее давайте возьмем конечную точку /products/{id} и построим соответствующий URI:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}")
    .build(2))
  .retrieve();
verifyCalledUrl("/products/2");

Из приведенного выше кода мы видим, что фактические значения сегментов передаются в метод build () .
Теперь аналогичным образом мы можем создать URI с несколькими сегментами пути для /продуктов/{id}/атрибутов/{идентификатор атрибута}
конечной точки:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}/attributes/{attributeId}")
    .build(2, 13))
  .retrieve();
verifyCalledUrl("/products/2/attributes/13");

URI может содержать столько сегментов пути, сколько требуется. Конечно, если конечная длина URI не превышает ограничений. Наконец, не забудьте сохранить правильный порядок фактических значений сегмента, передаваемых методу build () .

5. Параметры запроса URI

Обычно параметр запроса представляет собой простую пару ключ-значение, например title=Baeldung . Давайте посмотрим, как построить такие URI.

5.1. Параметры с Одним Значением

Давайте начнем с параметров с одним значением и возьмем /продукты/?имя={имя}&Дата доставки={Дата доставки}&цвет={цвет} конечная точка. Чтобы задать параметр запроса, мы вызываем метод QueryParam() интерфейса UriBuilder :

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");

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

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "{title}")
    .queryParam("color", "{authorId}")
    .queryParam("deliveryDate", "{date}")
    .build("AndroidPhone", "black", "13/04/2019"))
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");

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

Если обратить внимание на ожидаемые URI, мы увидим, что они были закодированы по-разному . В частности, символ косой черты (/) был экранирован в последнем примере. Вообще говоря, RFC3986 не требует кодирования косых черт в запросе.

Однако для некоторых серверных приложений может потребоваться такое преобразование. Поэтому мы рассмотрим, как изменить это поведение позже в этом руководстве.

5.2. Параметры массива

Аналогично, нам может потребоваться передать массив значений. Тем не менее, строгих правил для передачи массивов в строке запроса не существует. Поэтому представление массива в строке запроса отличается от проекта к проекту и обычно зависит от базовых структур . Мы рассмотрим наиболее широко используемые форматы.

Давайте начнем с /products/?tag[]={tag1}&tag[]={tag2} конечная точка:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("tag[]", "Snapdragon", "NFC")
    .build())
  .retrieve();
verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");

Как мы видим, конечный URI содержит несколько параметров тегов, за которыми следуют закодированные квадратные скобки. Метод query Param() принимает переменные аргументы в качестве значений, поэтому нет необходимости вызывать метод несколько раз.

В качестве альтернативы мы можем опустить квадратные скобки и просто передать несколько параметров запроса с одним и тем же ключом , но разными значениями – /продукты/?категория={категория1}&категория={категория2} :

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", "Phones", "Tablets")
    .build())
  .retrieve();
verifyCalledUrl("/products/?category=Phones&category=Tablets");

В заключение следует отметить, что существует еще один широко используемый метод кодирования массива-передача значений, разделенных запятыми. Давайте преобразуем наш предыдущий пример в значения, разделенные запятыми:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", String.join(",", "Phones", "Tablets"))
    .build())
  .retrieve();
verifyCalledUrl("/products/?category=Phones,Tablets");

Таким образом, мы просто используем метод join() класса String для создания строки, разделенной запятыми. Конечно, мы можем использовать любой другой разделитель, который ожидается приложением.

6. Режим Кодирования

Помните, как мы упоминали кодировку URL ранее.

Если поведение по умолчанию не соответствует нашим требованиям, мы можем его изменить. Нам нужно предоставить реализацию UriBuilder Factory при создании экземпляра WebClient . В этом случае мы будем использовать класс По умолчанию Uri Builder Factory . Чтобы задать кодировку, вызовите метод set Encoding Mode () . Доступны следующие модели:

  • TEMPLATE_AND_VALUES : Предварительно закодируйте шаблон URI и строго кодируйте переменные URI при расширении
  • VALUES_ONLY : Не кодируйте шаблон URI, а строго кодируйте переменные URI после их расширения в шаблоне
  • URL_КОМПОНЕНТЫ : Значение компонента encodeURIComponent после использования переменных URI
  • НЕТ : Кодировка не будет применена

Значение по умолчанию – TEMPLATE_AND_VALUES . Давайте установим режим на URI_COMPONENTS :

DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
this.webClient = WebClient
  .builder()
  .uriBuilderFactory(factory)
  .baseUrl(BASE_URL)
  .exchangeFunction(exchangeFunction)
  .build();

В результате будет выполнено следующее утверждение:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");

И, конечно же, мы можем предоставить полностью настраиваемую фабрику UriBuilder реализацию для обработки создания URI вручную.

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

В этом уроке мы рассмотрели, как создавать различные типы URI с помощью WebClient и Defaulturlbuilder.

Попутно мы рассмотрели различные типы и форматы параметров запроса. И мы закончили с изменением режима кодирования по умолчанию в построителе URL-адресов.

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