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

HTTP-клиент Rat pack

Узнайте, как асинхронно отвечать на HTTP-запросы с помощью HttpClient Rat pack.

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

1. введение

За последние несколько лет мы стали свидетелями роста функционального и реактивного способа создания приложений на Java. Rat pack предлагает способ создания HTTP-приложений в том же духе.

Поскольку он использует Netty для своих сетевых нужд, он полностью асинхронен и не блокирует . Rat pack также обеспечивает поддержку тестирования, предоставляя сопутствующую библиотеку тестов.

В этом уроке мы рассмотрим использование HttpClient пакета Rat и связанных с ним компонентов.

И при этом мы постараемся продолжить наше понимание с того момента, на котором мы остановились в конце нашего вводного урока по Ratpack .

2. Зависимости Maven

Для начала давайте добавим необходимые зависимости Rat pack :


    io.ratpack
    ratpack-core
    1.5.4


    io.ratpack
    ratpack-test
    1.5.4
    test

Интересно, что нам нужно только это, чтобы создать и протестировать наше приложение.

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

3. Предыстория

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

3.1. Подход, основанный на Обработчиках

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

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

public class FooHandler implements Handler {
    @Override
    public void handle(Context ctx) throws Exception {
        ctx.getResponse().send("Hello Foo!");
    }
}

3.2. Цепочка, реестр и контекст

Обработчики взаимодействуют с входящим запросом с помощью объекта контекста. Через него мы получаем доступ к HTTP-запросу и ответу, а также возможности делегирования другим обработчикам.

Возьмем, к примеру, следующий обработчик:

Handler allHandler = context -> {
    Long id = Long.valueOf(context.getPathTokens().get("id"));
    Employee employee = new Employee(id, "Mr", "NY");
    context.next(Registry.single(Employee.class, employee));
};

Этот обработчик отвечает за некоторую предварительную обработку, помещая результат в R egistry , а затем делегируя запрос другим обработчикам.

С помощью Реестра мы можем обеспечить взаимодействие между обработчиками . Следующий обработчик запрашивает ранее вычисленный результат из Реестра с использованием типа объекта:

Handler empNameHandler = ctx -> {
    Employee employee = ctx.get(Employee.class);
    ctx.getResponse()
      .send("Name of employee with ID " + employee.getId() + " is " + employee.getName());
};

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

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

Например:

Action chainAction = chain -> chain.prefix("employee/:id", empChain -> {
    empChain.all(allHandler)
      .get("name", empNameHandler)
      .get("title", empTitleHandler);
});

Мы можем использовать этот подход дальше, составляя несколько цепочек вместе, используя метод insert(..) в Chain и делая каждую из них ответственной за разные проблемы.

В следующем тестовом примере показано использование этих конструкций:

@Test
public void givenAnyUri_GetEmployeeFromSameRegistry() throws Exception {
    EmbeddedApp.fromHandlers(chainAction)
      .test(testHttpClient -> {
          assertEquals("Name of employee with ID 1 is NY", testHttpClient.get("employee/1/name")
            .getBody()
            .getText());
          assertEquals("Title of employee with ID 1 is Mr", testHttpClient.get("employee/1/title")
            .getBody()
            .getText());
      });
}

Здесь мы используем библиотеку тестирования Rat pack для тестирования нашей функциональности изолированно и без запуска реального сервера.

4. HTTP С Ratpack

4.1. Работа В Направлении Асинхронности

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

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

4.2. Функции обратного вызова

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

Rat pack предоставляет элегантное решение для решения этой сложности в виде Promise s.

4.3. Обещания Ratpack

Пакет крыс Promise можно считать сродни объекту Java Future . По сути, это представление значения, которое станет доступным позже.

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

Как и следовало ожидать, это приводит к небольшому количеству переключений контекста между потоками и делает ваше приложение эффективным.

Ниже приведена реализация обработчика, которая использует Promise :

public class EmployeeHandler implements Handler {
    @Override
    public void handle(Context ctx) throws Exception {
        EmployeeRepository repository = ctx.get(EmployeeRepository.class);
        Long id = Long.valueOf(ctx.getPathTokens().get("id"));
        Promise employeePromise = repository.findEmployeeById(id);
        employeePromise.map(employee -> employee.getName())
          .then(name -> ctx.getResponse()
          .send(name));
    }
}

Мы должны иметь в виду, что обещание особенно полезно, когда мы определяем, что делать с конечным значением . Мы можем сделать это, вызвав на нем терминальную операцию then(Действие) .

Если нам нужно отправить обещание, но источник данных синхронен, мы все равно сможем это сделать:

@Test
public void givenSyncDataSource_GetDataFromPromise() throws Exception {
    String value = ExecHarness.yieldSingle(execution -> Promise.sync(() -> "Foo"))
      .getValueOrThrow();
    assertEquals("Foo", value);
}

4.4. HTTP-Клиент

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

Мы можем создать экземпляр, используя метод of(Action) , который принимает в качестве параметра Действие типа Спецификация HttpClient.

Используя это, мы можем настроить нашего клиента в соответствии с нашими предпочтениями:

HttpClient httpClient = HttpClient.of(httpClientSpec -> {
    httpClientSpec.poolSize(10)
      .connectTimeout(Duration.of(60, ChronoUnit.SECONDS))
      .maxContentLength(ServerConfig.DEFAULT_MAX_CONTENT_LENGTH)
      .responseMaxChunkSize(16384)
      .readTimeout(Duration.of(60, ChronoUnit.SECONDS))
      .byteBufAllocator(PooledByteBufAllocator.DEFAULT);
});

Как мы могли бы догадаться по его асинхронной природе, HttpClient возвращает объект Promise . В результате мы можем иметь сложный конвейер операций неблокирующим способом.

Для иллюстрации, пусть клиент позвонит нашему EmployeeHandler , используя это HttpClient :

public class RedirectHandler implements Handler {
 
    @Override
    public void handle(Context ctx) throws Exception {
        HttpClient client = ctx.get(HttpClient.class);
        URI uri = URI.create("http://localhost:5050/employee/1");
        Promise responsePromise = client.get(uri);
        responsePromise.map(response -> response.getBody()
          .getText()
          .toUpperCase())
          .then(responseText -> ctx.getResponse()
            .send(responseText));
    }
}

Быстрый звонок cURL подтвердил бы, что мы получили ожидаемый ответ:

curl http://localhost:5050/redirect
JANE DOE

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

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

Мы взглянули на стаю Крыс HttpClient и сопутствующий класс Promise , который представляет все асинхронные вещи в Ratpack. Мы также увидели, как мы можем легко протестировать ваше HTTP-приложение с помощью прилагаемого TestHttpClient .

И, как всегда, фрагменты кода из этого учебника доступны в нашем репозитории GitHub .