Spec-Zone.ru › Vert.x 5

Vert.x Веб-клиент

Vert.x Веб-клиент — это асинхронный HTTP- и HTTP/2-клиент.

Веб-клиент упрощает взаимодействие с веб-сервером посредством запросов/ответов HTTP и предоставляет расширенные возможности, такие как:

  • Кодирование/декодирование JSON тела запроса/ответа

  • Обработка запросов/ответов

  • Параметры запроса

  • Унифицированная обработка ошибок

  • Отправка форм

Веб-клиент не устаревает API Vert.x Core HttpClient, на самом деле он основан на этом клиенте и наследует его конфигурацию и такие преимущества, как пулинг, поддержка HTTP/2, поддержка пайплайнинга и т. д. HttpClient следует использовать, когда необходим точный контроль над HTTP-запросами/ответами.

Веб-клиент не предоставляет API WebSocket, следует использовать Vert.x Core HttpClient. Также в настоящее время он не обрабатывает куки.

Использование Веб-клиента

Чтобы использовать Vert.x Веб-клиент, добавьте следующую зависимость в раздел dependencies вашего файла описания проекта:

  • Maven (в вашем pom.xml):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-client</artifactId>
  <version>5.0.0</version>
</dependency>
  • Gradle (в вашем файле build.gradle):

dependencies {
  compile 'io.vertx:vertx-web-client:5.0.0'
}

Обзор HTTP-клиента Vert.x Core

Vert.x Веб-клиент использует API Vert.x core, поэтому стоит ознакомиться с основными концепциями использования HttpClient с помощью Vert.x core, если вы этого ещё не сделали.

Создание Веб-клиента

Вы создаёте экземпляр WebClient с параметрами по умолчанию следующим образом

WebClient client = WebClient.create(vertx);

Если вы хотите настроить параметры клиента, вы создаёте его следующим образом

WebClientOptions options = new WebClientOptions()
  .setUserAgent("My-App/1.2.3");
options.setKeepAlive(false);
WebClient client = WebClient.create(vertx, options);

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

Если у вас уже есть HTTP-клиент в приложении, вы также можете его переиспользовать

WebClient client = WebClient.wrap(httpClient);
В большинстве случаев Web-клиент следует создавать один раз при запуске приложения и затем повторно использовать. В противном случае вы теряете много преимуществ, таких как пулинг соединений, и можете утечь ресурсы, если экземпляры не закрыты должным образом.

Создание запросов

Простые запросы без тела

Часто вам потребуется отправлять HTTP-запросы без тела. Это обычно относится к запросам HTTP GET, OPTIONS и HEAD

WebClient client = WebClient.create(vertx);

// Send a GET request
client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .onSuccess(response -> System.out
    .println("Received response with status code" + response.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

// Send a HEAD request
client
  .head(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .onSuccess(response -> System.out
    .println("Received response with status code" + response.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Вы можете добавить параметры запроса к URI запроса в удобном формате.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .addQueryParam("param", "param_value")
  .send()
  .onSuccess(response -> System.out
    .println("Received response with status code" + response.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Любой параметр URI запроса будет предварительно заполнен.

HttpRequest<Buffer> request = client
  .get(
    8080,
    "myserver.mycompany.com",
    "/some-uri?param1=param1_value&param2=param2_value");

// Add param3
request.addQueryParam("param3", "param3_value");

// Overwrite param2
request.setQueryParam("param2", "another_param2_value");

Установка URI запроса отбрасывает существующие параметры запроса.

HttpRequest<Buffer> request = client
  .get(8080, "myserver.mycompany.com", "/some-uri");

// Add param1
request.addQueryParam("param1", "param1_value");

// Overwrite param1 and add param2
request.uri("/some-uri?param1=param1_value&param2=param2_value");

Запись тел запросов

Когда вам нужно отправить запрос с телом, вы используете тот же API и вызываете методы, которые ожидают отправки тела.

Используйте sendBuffer для отправки тела буфера.

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendBuffer(buffer)
  .onSuccess(res -> {
    // OK
  });

Отправка одного буфера полезна, но часто вы не хотите загружать всё содержимое в память, так как оно может быть слишком большим, или вы хотите обрабатывать множество одновременных запросов и использовать минимум для каждого запроса. Для этой цели Веб-клиент может отправлять ReadStream<Buffer> (например, AsyncFile - это ReadStream<Buffer>`) с помощью метода sendStream.

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendStream(stream)
  .onSuccess(res -> {
    // OK
  });

Веб-клиент позаботится о настройке передачи данных. Поскольку длина потока неизвестна, запрос будет использовать кодировку chunked transfer.

Когда вы знаете размер потока, вы должны указать заголовок content-length.

fs.open("content.txt", new OpenOptions())
  .onSuccess(fileStream -> {
    String fileLen = "1024";

    // Send the file to the server using POST
    client
      .post(8080, "myserver.mycompany.com", "/some-uri")
      .putHeader("content-length", fileLen)
      .sendStream(fileStream)
      .onSuccess(res -> {
        // OK
      });
});

POST-запрос не будет использовать кодировку chunked.

Тела JSON

Часто вы захотите отправить запрос с телом JSON. Для отправки JsonObject используйте метод sendJsonObject.

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendJsonObject(
    new JsonObject()
      .put("firstName", "Dale")
      .put("lastName", "Cooper"))
  .onSuccess(res -> {
    // OK
  });

В Java, Groovy или Kotlin вы можете использовать метод sendJson, который отображает POJO (Plain Old Java Object) в объект JSON, используя метод Json.encode.

client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendJson(new User("Dale", "Cooper"))
  .onSuccess(res -> {
    // OK
  });
метод Json.encode использует Jackson mapper для кодирования объекта в JSON.

Отправка форм

Вы можете отправить тела HTTP-форм с помощью варианта sendForm.

MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");

// Submit the form as a form URL encoded body
client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendForm(form)
  .onSuccess(res -> {
    // OK
  });

По умолчанию форма отправляется с заголовком типа содержимого application/x-www-form-urlencoded. Вы можете установить заголовок content-type на multipart/form-data вместо этого.

MultiMap form = MultiMap.caseInsensitiveMultiMap();
form.set("firstName", "Dale");
form.set("lastName", "Cooper");

// Submit the form as a multipart form body
client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .putHeader("content-type", "multipart/form-data")
  .sendForm(form)
  .onSuccess(res -> {
    // OK
  });

Если вы хотите загрузить файлы и отправить атрибуты, создайте MultipartForm и используйте метод sendMultipartForm.

MultipartForm form = MultipartForm.create()
  .attribute("imageDescription", "a very nice image")
  .binaryFileUpload(
    "imageFile",
    "image.jpg",
    "/path/to/image",
    "image/jpeg");

// Submit the form as a multipart form body
client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .sendMultipartForm(form)
  .onSuccess(res -> {
    // OK
  });

Запись заголовков запроса

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

HttpRequest<Buffer> request = client
  .get(8080, "myserver.mycompany.com", "/some-uri");

MultiMap headers = request.headers();
headers.set("content-type", "application/json");
headers.set("other-header", "foo");

Заголовки являются экземпляром MultiMap, который предоставляет операции добавления, установки и удаления записей. HTTP-заголовки позволяют использовать более одного значения для определённого ключа.

Вы также можете записать заголовки с помощью putHeader.

HttpRequest<Buffer> request = client
  .get(8080, "myserver.mycompany.com", "/some-uri");

request.putHeader("content-type", "application/json");
request.putHeader("other-header", "foo");

Настройка запроса для добавления аутентификации

Аутентификация может быть выполнена вручную путём установки соответствующих заголовков или с помощью наших предопределённых методов (мы настоятельно рекомендуем включить HTTPS, особенно для аутентифицированных запросов):

При базовой HTTP-аутентификации запрос содержит заголовок в формате Authorization: Basic <credentials>, где credentials – это base64-кодировка id и password, соединённых двоеточием.

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

HttpRequest<Buffer> request = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .authentication(new UsernamePasswordCredentials("myid", "mypassword"));

В OAuth 2.0 запрос содержит заголовок в формате Authorization: Bearer <bearerToken>, где bearerToken – это токен, выпущенный сервером авторизации для доступа к защищённым ресурсам.

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

HttpRequest<Buffer> request = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .authentication(new TokenCredentials("myBearerToken"));

Повторное использование запросов

Метод send может быть вызван многократно, что делает очень простым конфигурирование и повторное использование объектов HttpRequest.

HttpRequest<Buffer> get = client
  .get(8080, "myserver.mycompany.com", "/some-uri");

get
  .send()
  .onSuccess(res -> {
    // OK
  });

// Same request again
get
  .send()
  .onSuccess(res -> {
    // OK
  });

Обратите внимание, что экземпляры HttpRequest изменяемы. Поэтому вы должны вызвать метод copy перед изменением кэшированного экземпляра.

HttpRequest<Buffer> get = client
  .get(8080, "myserver.mycompany.com", "/some-uri");

get
  .send()
  .onSuccess(res -> {
    // OK
  });

// The "get" request instance remains unmodified
get
  .copy()
  .putHeader("a-header", "with-some-value")
  .send()
  .onSuccess(res -> {
    // OK
  });

Таймауты

Вы можете установить таймаут подключения для определённого HTTP-запроса, используя connectTimeout.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .connectTimeout(5000)
  .send()
  .onSuccess(res -> {
    // OK
  })
  .onFailure(err -> {
    // Might be a timeout when cause is java.util.concurrent.TimeoutException
  });

Если клиент не может установить соединение с сервером в течение установленного таймаута, исключение будет передано обработчику ответа.

Вы можете установить таймаут бездействия для определённого HTTP-запроса, используя idleTimeout.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .idleTimeout(5000)
  .send()
  .onSuccess(res -> {
    // OK
  })
  .onFailure(err -> {
    // Might be a timeout when cause is java.util.concurrent.TimeoutException
  });

Если запрос не возвращает данных в течение установленного таймаута, исключение будет передано обработчику ответа.

Вы можете установить оба таймаута, используя timeout

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .timeout(5000)
  .send()
  .onSuccess(res -> {
    // OK
  })
  .onFailure(err -> {
    // Might be a timeout when cause is java.util.concurrent.TimeoutException
  });

Обработка ответов http

При отправке запроса Веб-клиентом вы всегда работаете с одним асинхронным результатом HttpResponse.

При успешном результате обратный вызов происходит после получения ответа.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

По умолчанию запрос Vert.x Web Client завершается ошибкой только в том случае, если что-то не так с уровнем сети. Другими словами, ответ 404 Not Found или ответ с неправильным типом содержимого не считаются ошибками. Используйте ожидания http-ответов, если вы хотите, чтобы Web Client автоматически выполнял проверки целостности.

Ответы полностью буферизируются, используйте BodyCodec.pipe для передачи ответа в поток записи

Декодирование ответов

По умолчанию Web Client предоставляет тело ответа http в виде Buffer и не применяет никакого декодирования.

Настройка декодирования тела ответа может быть выполнена с помощью BodyCodec:

  • Простая строка

  • Объект Json

  • Json-отображаемый POJO

  • WriteStream

Тело кодирования может декодировать произвольный бинарный поток данных в определенный экземпляр объекта, экономя вам шаг декодирования в обработчиках ответов.

Используйте BodyCodec.jsonObject для декодирования объекта Json:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.jsonObject())
  .send()
  .onSuccess(res -> {
    JsonObject body = res.body();

    System.out.println(
      "Received response with status code" +
        res.statusCode() +
        " with body " +
        body);
  })
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

В Java, Groovy или Kotlin можно декодировать пользовательские Json-отображаемые POJO

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.json(User.class))
  .send()
  .onSuccess(res -> {
    User user = res.body();

    System.out.println(
      "Received response with status code" +
        res.statusCode() +
        " with body " +
        user.getFirstName() +
        " " +
        user.getLastName());
  })
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Когда ожидаются большие ответы, используйте BodyCodec.pipe. Этот кодек тела перекачивает буферы тела ответа в WriteStream и сигнализирует об успехе или ошибке операции в ответе асинхронного результата.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.pipe(writeStream))
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Часто встречаются API, возвращающие поток JSON-объектов. Например, API Twitter может предоставлять ленту твитов. Для обработки этого случая можно использовать BodyCodec.jsonStream. Вы передаете JSON-парсер, который генерирует потоки считанных JSON-данных из ответа HTTP:

JsonParser parser = JsonParser.newParser().objectValueMode();
parser.handler(event -> {
  JsonObject object = event.objectValue();
  System.out.println("Got " + object.encode());
});
client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.jsonStream(parser))
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Наконец, если вас совсем не интересует содержимое ответа, BodyCodec.none просто отбрасывает всё тело ответа.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .as(BodyCodec.none())
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Если тип содержимого http-ответа заранее неизвестен, можно использовать методы bodyAsXXX(), которые декодируют ответ в определенный тип.

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .onSuccess(res -> {
    // Decode the body as a json object
    JsonObject body = res.bodyAsJsonObject();

    System.out.println(
      "Received response with status code" +
        res.statusCode() +
        " with body " +
        body);
  })
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));
Это справедливо только для ответа, декодированного как буфер.

Ожидания ответов

По умолчанию запрос Vert.x Web Client завершается ошибкой только в том случае, если что-то не так с уровнем сети.

Другими словами, вы должны самостоятельно выполнить проверки целостности после получения ответа:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .onSuccess(res -> {
    if (
      res.statusCode() == 200 &&
        res.getHeader("content-type").equals("application/json")) {
      // Decode the body as a json object
      JsonObject body = res.bodyAsJsonObject();

      System.out.println(
        "Received response with status code" +
          res.statusCode() +
          " with body " +
          body);
    } else {
      System.out.println("Something went wrong " + res.statusCode());
    }
  })
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Вы можете пожертвовать гибкостью ради ясности и краткости, используя ожидания ответа.

Response expectations может завершить запрос, если ответ не соответствует критериям.

Web Client может использовать предварительно определённые ожидания Vert.x HTTP Client:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .expecting(HttpResponseExpectation.SC_SUCCESS.and(HttpResponseExpectation.JSON))
  .onSuccess(res -> {
    // Safely decode the body as a json object
    JsonObject body = res.bodyAsJsonObject();
    System.out.println(
      "Received response with status code" +
        res.statusCode() +
        " with body " +
        body);
  })
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Вы также можете создавать пользовательские ожидания, когда существующих ожиданий недостаточно:

Expectation<HttpResponseHead> methodsPredicate = new Expectation<HttpResponseHead>() {
  @Override
  public boolean test(HttpResponseHead resp) {
    String methods = resp.getHeader("Access-Control-Allow-Methods");
    return methods != null && methods.contains("POST");
  }
};

// Send pre-flight CORS request
client
  .request(
    HttpMethod.OPTIONS,
    8080,
    "myserver.mycompany.com",
    "/some-uri")
  .putHeader("Origin", "Server-b.com")
  .putHeader("Access-Control-Request-Method", "POST")
  .send()
  .expecting(methodsPredicate)
  .onSuccess(res -> {
    // Process the POST request now
  })
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Предварительно заданные ожидания

Для удобства Vert.x HTTP Client поставляется несколько ожиданий для распространенных сценариев, которые также применяются к Web Client.

Для кодов состояния, например, HttpResponseExpectation.SC_SUCCESS для проверки того, что ответ имеет код 2xx, вы также можете создать пользовательский:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .expecting(HttpResponseExpectation.status(200, 202))
  .onSuccess(res -> {
    // ....
  });

Для типов содержимого, например, HttpResponseExpectation.JSON для проверки того, что тело ответа содержит данные JSON, вы также можете создать пользовательский:

client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .send()
  .expecting(HttpResponseExpectation.contentType("some/content-type"))
  .onSuccess(res -> {
    // ....
  });

Обратитесь к документации HttpResponseExpectation за полным списком предварительно заданных предикатов.

Создание пользовательских ошибок

По умолчанию ожидания ответов (включая предварительно заданные) используют конвертер ошибок по умолчанию, который отбрасывает тело и передаёт простое сообщение. Вы можете настроить класс исключения, сопоставив ошибку:

Expectation<HttpResponseHead> expectation = HttpResponseExpectation.SC_SUCCESS
  .wrappingFailure((resp, err) -> new MyCustomException(err.getMessage()));

Многие веб-API предоставляют детали в ответах об ошибках. Например, API Marvel использует этот формат JSON-объекта:

{
  "code": "InvalidCredentials",
  "message": "The passed API key is invalid."
}

Чтобы не терять эту информацию, можно преобразовать тело ответа:

HttpResponseExpectation.SC_SUCCESS.wrappingFailure((resp, err) -> {
  // Invoked after the response body is fully received
  HttpResponse<?> response =(HttpResponse<?>) resp;

  if (response
    .getHeader("content-type")
    .equals("application/json")) {

    // Error body is JSON data
    JsonObject body = response.bodyAsJsonObject();

    return new MyCustomException(
      body.getString("code"),
      body.getString("message"));
  }

  // Fallback to defaut message
  return new MyCustomException(err.getMessage());
});
создание исключения в Java может иметь затраты производительности при захвате стека, поэтому вы можете создать исключения, которые не захватывают стек. По умолчанию исключения сообщаются с использованием исключения, которое не захватывает стек.

Обработка перенаправлений 30x

По умолчанию клиент следует перенаправлениям, вы можете настроить поведение по умолчанию в WebClientOptions:

WebClient client = WebClient
  .create(vertx, new WebClientOptions().setFollowRedirects(false));

Клиент будет следовать не более чем 16 запросам перенаправлений, это можно изменить в тех же параметрах:

WebClient client = WebClient
  .create(vertx, new WebClientOptions().setMaxRedirects(5));
По соображениям безопасности клиент не будет следовать перенаправлениям для запросов с методами, отличными от GET или HEAD

Выгрузка нагрузки на стороне клиента

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

Клиент может быть настроен на выполнение выгрузки нагрузки на стороне клиента вместо этого.

WebClient client = WebClient.wrap(vertx
  .httpClientBuilder()
  .withLoadBalancer(LoadBalancer.ROUND_ROBIN)
  .build());

Vert.x предоставляет несколько политик выгрузки нагрузки, которые можно использовать «из коробки»

  • Round-robin

  • Least requests

  • Power of two choices

  • Consistent hashing

Большинство политик выгрузки нагрузки довольно понятны.

Маршрутизация на основе хеша может быть достигнута с помощью политики LoadBalancer.CONSISTENT_HASHING.

WebClient client = WebClient.wrap(vertx
  .httpClientBuilder()
  .withLoadBalancer(LoadBalancer.ROUND_ROBIN)
  .build());

Вы можете узнать больше о выгрузке нагрузки на стороне клиента в документации Vert.x Core HTTP client.

Кэширование ответов HTTP

Vert.x web предоставляет средство кэширования ответов HTTP; для его использования создайте CachingWebClient.

Создание кэшируемого клиента веб-приложения

WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);

Настройка кэшируемых данных

По умолчанию, кэшируемый веб-клиент кэширует только ответы от метода GET, у которого код состояния равен 200, 301 или 404. Кроме того, ответы, содержащие заголовок Vary, по умолчанию не кэшируются.

Это можно настроить, передав CachingWebClientOptions при создании клиента.

CachingWebClientOptions options = new CachingWebClientOptions()
  .addCachedMethod(HttpMethod.HEAD)
  .removeCachedStatusCode(301)
  .setEnableVaryCaching(true);

WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client, options);

Ответы, содержащие директиву private в заголовке Cache-Control, не будут кэшироваться, если клиент также не является WebClientSession. См. Обработка частных ответов.

Использование внешнего хранилища

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

WebClient client = WebClient.create(vertx);
CacheStore store = new NoOpCacheStore(); // or any store you like
WebClient cachingWebClient = CachingWebClient.create(client, store);

Обработка частных ответов

Для включения кэширования частных ответов, CachingWebClient можно комбинировать с WebClientSession. При этом публичные ответы, содержащие директиву public в заголовке Cache-Control, будут кэшироваться в хранилище, с которым создавался клиент. Частные ответы, содержащие директиву private в заголовке Cache-Control, будут кэшироваться в рамках сессии, чтобы предотвратить утечку кэшированных ответов другим пользователям (сессиям).

Для создания клиента, способного кэшировать частные ответы, передайте CachingWebClient в WebClientSession.

WebClient client = WebClient.create(vertx);
WebClient cachingWebClient = CachingWebClient.create(client);
WebClient sessionClient = WebClientSession.create(cachingWebClient);

Шаблоны URI

Шаблоны URI предлагают альтернативу строковым URI HTTP-запросов, основанную на RFC 6570 для шаблонов URI.

Вы можете ознакомиться с документацией Vert.x по шаблонам URI здесь.

Вы можете создать HttpRequest с UriTemplate URI вместо Java-строки URI.

Сначала разоберите строку шаблона в UriTemplate

UriTemplate REQUEST_URI = UriTemplate.of("/some-uri?{param}");

Затем используйте его для создания запроса

HttpRequest<Buffer> request = client.get(8080, "myserver.mycompany.com", REQUEST_URI);

Установите параметры шаблона

request.setTemplateParam("param", "param_value");

И, наконец, отправьте запрос, как обычно

request.send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

или с использованием цепочки методов

client.get(8080, "myserver.mycompany.com", REQUEST_URI)
  .setTemplateParam("param", "param_value")
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Расширение шаблонов URI

Перед отправкой запроса Vert.x WebClient расширяет шаблон до строки с параметрами запроса шаблона.

Расширение строк заботится о кодировании параметров для вас.

String euroSymbol = "\u20AC";
UriTemplate template = UriTemplate.of("/convert?{amount}&{currency}");

// Request uri: /convert?amount=1234&currency=%E2%82%AC
Future<HttpResponse<Buffer>> fut = client.get(template)
  .setTemplateParam("amount", amount)
  .setTemplateParam("currency", euroSymbol)
  .send();

По умолчанию используется синтаксис расширения простой строки, доступны и другие синтаксисы расширения.

  • расширение сегмента пути ({/varname})

  • расширение запроса в стиле формы ({?varname})

  • и т.д.

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

В соответствии с RFC, расширение шаблона заменит отсутствующие параметры шаблона пустой строкой. Вы можете изменить это поведение, вызвав ошибку:

WebClient webClient = WebClient.create(vertx, new WebClientOptions()
  .setTemplateExpandOptions(new ExpandOptions()
    .setAllowVariableMiss(false))
);

Значения параметров шаблона

Параметры шаблона принимают значения String, List<String> и Map<String, String>.

Расширение каждого типа зависит от стиля расширения (обозначено префиксом ?), вот пример параметра query, который раскрывается (обозначен суффиксом *) и расширяется с помощью расширения запроса в стиле формы:

Map<String, String> query = new HashMap<>();
query.put("color", "red");
query.put("width", "30");
query.put("height", "50");
UriTemplate template = UriTemplate.of("/{?query*}");

// Request uri: /?color=red&width=30&height=50
Future<HttpResponse<Buffer>> fut = client.getAbs(template)
  .setTemplateParam("query", query)
  .send();

Расширение запроса в стиле формы расширяет переменную {?query*} как ?color=red&width=30&height=50, по определению.

Использование HTTPS

Vert.x Web Client можно настроить для использования HTTPS точно так же, как Vert.x HttpClient.

Вы можете указать поведение для каждого запроса

client
  .get(443, "myserver.mycompany.com", "/some-uri")
  .ssl(true)
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Или использовать методы создания с абсолютным URI в качестве аргумента

client
  .getAbs("https://myserver.mycompany.com:4043/some-uri")
  .send()
  .onSuccess(res ->
    System.out.println("Received response with status code" + res.statusCode()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

Управление сессиями

Vert.x web предоставляет средство управления веб-сессиями; для его использования создайте WebClientSession для каждого пользователя (сессии) и используйте его вместо WebClient.

Создание WebClientSession

Вы создаёте экземпляр WebClientSession следующим образом

WebClient client = WebClient.create(vertx);
WebClientSession session = WebClientSession.create(client);

Отправка запросов

После создания WebClientSession можно использовать вместо WebClient для выполнения HTTP(s) запросов и автоматического управления полученными от сервера(ов) куки.

Установка заголовков уровня сессии

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

WebClientSession session = WebClientSession.create(client);
session.addHeader("my-jwt-token", jwtToken);

Эти заголовки будут добавлены ко всем запросам. Обратите внимание, что эти заголовки будут отправляться всем хостам. Если вам нужно отправлять разные заголовки на разные хосты, вы должны добавлять их вручную к каждому отдельному запросу, а не к самому WebClientSession.

Безопасность OAuth2

Vert.x web предлагает систему управления сессиями веб-приложений; для использования её создайте OAuth2WebClient для каждого пользователя (сессии) и используйте её вместо WebClient.

Создание клиента Oauth2

Создайте экземпляр OAuth2WebClient следующим образом

WebClient client = WebClient.create(vertx);
OAuth2WebClient oauth2 = OAuth2WebClient.create(
    client,
    OAuth2Auth.create(vertx, new OAuth2Options(/* enter IdP config */)))

  // configure the initial credentials (needed to fetch if needed
  // the access_token
  .withCredentials(new TokenCredentials("some.jwt.token"));

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

KeycloakAuth.discover(
    vertx,
    new OAuth2Options().setSite("https://keycloakserver.com"))
  .onSuccess(oauth -> {
    OAuth2WebClient client = OAuth2WebClient.create(
        WebClient.create(vertx),
        oauth)
      // if your keycloak is configured for password_credentials_flow
      .withCredentials(
        new UsernamePasswordCredentials("bob", "s3cret"));
  });

Обработка запросов

После создания, OAuth2WebClient можно использовать вместо WebClient для выполнения HTTP(s) запросов и автоматической обработки любых cookie, полученных от сервера(ов).

Избегайте истекших токенов

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

OAuth2WebClient client = OAuth2WebClient.create(
    baseClient,
    oAuth2Auth,
    new OAuth2WebClientOptions()
      .setLeeway(5));

Если запрос должен быть выполнен, проверяется текущий активный объект пользователя на истечение срока действия с добавлением дополнительного временного интервала. Это позволит клиенту выполнить обновление токена при необходимости, вместо прерывания операции с ошибкой.

Запрос всё равно может завершиться ошибкой из-за истекших токенов, так как вычисление срока действия всё равно будет выполнено на стороне сервера. Для уменьшения работы с пользователем, клиент может быть настроен на выполнение одной попытки повторного выполнения запроса, который вернул код состояния 401 (Запрещено). Когда флаг опции: refreshTokenOnForbidden установлен на значение true, клиент выполнит новый запрос токена и повторит исходный запрос, прежде чем передать ответ обработчику пользователя/promise.

OAuth2WebClient client = OAuth2WebClient.create(
  baseClient,
  oAuth2Auth,
  new OAuth2WebClientOptions()
    // the client will attempt a single token request, if the request
    // if the status code of the response is 401
    // there will be only 1 attempt, so the second consecutive 401
    // will be passed down to your handler/promise
    .setRenewTokenOnForbidden(true));

API RxJava 3

RxJava HttpRequest предоставляет rx-версию оригинального API, метод rxSend возвращает Single<HttpResponse<Buffer>>, который выполняет HTTP-запрос при подписке, следовательно, к Single можно подписываться многократно.

Single<HttpResponse<Buffer>> single = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .rxSend();

// Send a request upon subscription of the Single
single.subscribe(response -> System.out.println("Received 1st response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));

// Send another request
single.subscribe(response -> System.out.println("Received 2nd response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));

Полученный Single может быть составлен и соединён естественным образом с API RxJava.

Single<String> url = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .rxSend()
  .map(HttpResponse::bodyAsString);

// Use the flatMap operator to make a request on the URL Single
url
  .flatMap(u -> client.getAbs(u).rxSend())
  .subscribe(response -> System.out.println("Received response with status code" + response.statusCode()), error -> System.out.println("Something went wrong " + error.getMessage()));

Доступны те же API:

Single<HttpResponse<JsonObject>> single = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .putHeader("some-header", "header-value")
  .addQueryParam("some-param", "param value")
  .as(BodyCodec.jsonObject())
  .rxSend();
single.subscribe(resp -> {
  System.out.println(resp.statusCode());
  System.out.println(resp.body());
});

Следует отдавать предпочтение rxSendStream для отправки тел Flowable<Buffer>.

Flowable<Buffer> body = getPayload();

Single<HttpResponse<Buffer>> single = client
  .post(8080, "myserver.mycompany.com", "/some-uri")
  .rxSendStream(body);
single.subscribe(resp -> {
  System.out.println(resp.statusCode());
  System.out.println(resp.body());
});

При подписке, body будет подписано и его содержимое будет использовано для запроса.

Ожидания HTTP-ответов

Взаимодействие с HTTP-бекендом часто подразумевает проверку кодов HTTP-ответов и/или типов контента.

Для упрощения процесса проверки используйте методы HttpResponseExpectation:

Single<HttpResponse<Buffer>> single = client
  .get(8080, "myserver.mycompany.com", "/some-uri")
  .rxSend()
  // Transforms the single into a failed single if the HTTP response is not successful
  .compose(HttpResponseExpectation.status(200))
  // Transforms the single into a failed single if the HTTP response content is not JSON
  .compose(HttpResponseExpectation.contentType("application/json"));

Сокеты Unix-доменной области

Веб-клиент поддерживает сокеты Unix-доменной области. Например, вы можете взаимодействовать с локальным демоном Docker.

Для этого необходимо запустить приложение с JDK16+ или создать экземпляр Vertx с помощью нативного транспорта.

SocketAddress serverAddress = SocketAddress
  .domainSocketAddress("/var/run/docker.sock");

// We still need to specify host and port so the request
// HTTP header will be localhost:8080
// otherwise it will be a malformed HTTP request
// the actual value does not matter much for this example
client
  .request(
    HttpMethod.GET,
    serverAddress,
    8080,
    "localhost",
    "/images/json")
  .as(BodyCodec.jsonObject())
  .send()
  .expecting(HttpResponseExpectation.SC_ACCEPTED)
  .onSuccess(res ->
    System.out.println("Current Docker images" + res.body()))
  .onFailure(err ->
    System.out.println("Something went wrong " + err.getMessage()));

© 2025 Eclipse Vert.x™Eclipse Vert.x™ is open source and dual-licensed under the Eclipse Public License 2.0 and the Apache License 2.0.Website design by Michel Krämer.
https://vertx.io/docs/vertx-web-client/java/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API