Ratpack HTTPクライアント

Ratpack HTTPクライアント

1. 前書き

過去数年にわたって、Javaでアプリケーションを作成する機能的で反応的な方法の台頭を目の当たりにしてきました。 Ratpackは、同じラインに沿ってHTTPアプリケーションを作成する方法を提供します。

ネットワークのニーズにNettyを使用しているため、it’s completely asynchronous and non-blocking。 Ratpackは、コンパニオンテストライブラリを提供することにより、テストのサポートも提供します。

このチュートリアルでは、RatpackHTTPクライアントと関連コンポーネントの使用について説明します。

そうすることで、introductory Ratpack tutorialの終わりに残したところからさらに理解を深めようとします。

2. Mavenの依存関係

開始するには、必要なRatpack dependenciesを追加しましょう。


    io.ratpack
    ratpack-core
    1.5.4


    io.ratpack
    ratpack-test
    1.5.4
    test

興味深いことに、これだけでアプリケーションの作成とテストができます。

ただし、他のRatpackライブラリを使用して、いつでも追加および拡張を選択できます。

3. バックグラウンド

飛び込む前に、Ratpackアプリケーションでの作業方法について説明しましょう。

3.1. ハンドラーベースのアプローチ

Ratpackはリクエスト処理にハンドラーベースのアプローチを使用します。 アイデア自体は非常に単純です。

そして、最も単純な形式では、特定の各パスでリクエストを処理する各ハンドラーを使用できます。

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

3.2. チェーン、レジストリ、およびコンテキスト

Handlers interact with the incoming request using a Context object.これにより、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));
};

このハンドラーは、いくつかの前処理を実行し、結果をRegistryに入れてから、要求を他のハンドラーに委任する役割を果たします。

Through the use of the Registry, we can achieve inter-handler communication。 次のhandlerは、オブジェクトタイプを使用して、Registryから以前に計算された結果をクエリします。

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

本番アプリケーションでは、複雑なビジネスロジックの抽象化、デバッグ、開発を改善するために、これらのハンドラーを個別のクラスとして使用することに注意してください。

Now we can use these handlers inside a Chain in order to create complex custom request processing pipelines

例えば:

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

Chaininsert(..)メソッドを使用して複数のチェーンを一緒に構成し、それぞれに異なる懸念事項を持たせることで、このアプローチをさらに進めることができます。

次のテストケースでは、これらの構造の使用方法を示します。

@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());
      });
}

ここでは、Ratpackのテストライブラリを使用して、実際のサーバーを起動せずに、機能を個別にテストしています。

4. Ratpackを使用したHTTP

4.1. 非同期に向けての作業

HTTPプロトコルは本質的に同期です。 その結果、多くの場合、Webアプリケーションは同期的であるため、ブロックされます。 着信リクエストごとにスレッドを作成するため、これは非常にリソースを消費するアプローチです。

むしろ、ノンブロッキングで非同期のアプリケーションを作成したいと思います。 これにより、スレッドの小さなプールを使用して要求を処理するだけで済みます。

4.2. コールバック関数

When dealing with asynchronous API’s, we usually provide a callback function to the receiver so that the data can be returned to the caller. Javaでは、これは通常、匿名の内部クラスとラムダ式の形式を取ります。 しかし、アプリケーションがスケーリングするにつれて、または複数のネストされた非同期呼び出しがあるため、そのようなソリューションは維持が難しく、デバッグが難しくなります。

Ratpackは、この複雑さをPromisesの形式で処理するための洗練されたソリューションを提供します。

4.3. ラットパックの約束

RatpackPromiseは、JavaFutureオブジェクトに類似していると見なすことができます。 It’s essentially a representation of a value which will become available later.

値が使用可能になったときに通過する操作のパイプラインを指定できます。 各操作は、前の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));
    }
}

promise is especially useful when we define what to do with the eventual valueであることを覚えておく必要があります。 これは、端末操作then(Action) を呼び出すことで実行できます。

promiseを返送する必要があるが、データソースが同期している場合でも、それを行うことができます。

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

4.4. HTTPクライアント

Ratpackは非同期HTTPクライアントを提供し、そのインスタンスはサーバーレジストリから取得できます。 ただし、we’re encouraged to create and use alternative instances as the default one doesn’t use connection pooling and has quite conservative defaults.

HttpClientSpec.タイプのActionをパラメータとして受け取るof(Action)メソッドを使用してインスタンスを作成できます

これを使用して、クライアントを好みに合わせて調整できます。

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);
});

非同期の性質から推測できるように、HttpClientPromiseオブジェクトを返します。 その結果、非ブロッキング方式で複雑な操作のパイプラインを作成できます。

説明のために、クライアントにこのHttpClientを使用してEmployeeHandlerを呼び出させましょう。

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で利用可能なプライマリライブラリコンストラクトについて説明しました。これにより、非ブロッキングおよび非同期のWebアプリケーションを開発できます。

RatpackのHttpClient とそれに付随するPromise classを調べました。これは、Ratpackで非同期のすべてのものを表します。 また、付属のTestHttpClientを使用してHTTPアプリケーションを簡単にテストする方法も確認しました。

そして、いつものように、このチュートリアルのコードスニペットはGitHub repositoryで利用できます。