5 ハンドラー
この章では、Ratpackアプリケーションの基本コンポーネントであるハンドラーについて説明します。
1.5 ハンドラーとは何か?
概念的には、ハンドラー(Handler
)は、ハンドリングコンテキスト(Context
)に対して動作する関数です。
「hello world」ハンドラーは次のようになります…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
public class Example implements Handler {
public void handle(Context context) {
context.getResponse().send("Hello world!");
}
}
前の章で見たように、必須の起動設定プロパティの1つは、プライマリハンドラーを提供するHandlerFactoryの実装です。このファクトリが作成するハンドラーは、事実上アプリケーションとなります。
ハンドラーがエンドポイントである必要がないことを認識するまでは、これは制限されているように見えるかもしれません(つまり、HTTPレスポンスを生成する以外のことを行うことができます)。ハンドラーは、いくつかの方法で他のハンドラーに委譲することもでき、より多くのルーティング機能を果たします。ルーティングステップとエンドポイントの間にフレームワークレベル(つまり、型)の区別がないという事実は、柔軟性を大幅に向上させます。これは、あらゆる種類のカスタムリクエスト処理パイプラインを合成されたハンドラーによって構築できることを意味します。この合成アプローチは、魔法のようなフレームワークではなくツールキットであるというRatpackの哲学の典型的な例です。
この章の残りの部分では、HTTPレベルの懸念事項(ヘッダーの読み取り、レスポンスの送信など)を超えるハンドラーの側面について説明します。これはHTTPの章で説明されています。
2.5 ハンドラーの委譲
ハンドラーがレスポンスを生成しない場合、別のハンドラーに委譲する必要があります。1つ以上のハンドラーを挿入するか、単に次のハンドラーに委譲することができます。
リクエストパスに基づいて2つの異なるハンドラーのいずれかにルーティングするハンドラーを考えてみましょう。これは次のように実装できます…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
public class FooHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("foo");
}
}
public class BarHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("bar");
}
}
public class Router implements Handler {
private final Handler fooHandler = new FooHandler();
private final Handler barHandler = new BarHandler();
public void handle(Context context) {
String path = context.getRequest().getPath();
if (path.equals("foo")) {
context.insert(fooHandler);
} else if (path.equals("bar")) {
context.insert(barHandler);
} else {
context.next();
}
}
}
委譲の鍵は、1つ以上のリンクされたハンドラーに制御を渡すcontext.insert()
メソッドです。context.next()
メソッドは、次のリンクされたハンドラーに制御を渡します。
次のような例を考えてみましょう…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrintThenNextHandler implements Handler {
private final String message;
private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenNextHandler.class);
public PrintThenNextHandler(String message) {
this.message = message;
}
public void handle(Context context) {
LOGGER.info(message);
context.next();
}
}
public class Application implements Handler {
public void handle(Context context) {
context.insert(
new PrintThenNextHandler("a"),
new PrintThenNextHandler("b"),
new PrintThenNextHandler("c")
);
}
}
Application
がプライマリハンドラー(つまり、起動設定のHandlerFactory
によって返されるハンドラー)であるとすると、このアプリケーションがリクエストを受け取ると、次のものがSystem.out
に出力されます…
a
b
c
そして、その後どうなるのでしょうか?「c」ハンドラーが次に委譲するとどうなるのでしょうか?最後のハンドラーは、常に内部ハンドラーであり、HTTP 404クライアントエラー(後で説明するcontext.clientError(404)
経由)を発行します。
挿入されたハンドラーがさらにハンドラーを挿入できることを考慮してください…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrintThenInsertOrNextHandler implements Handler {
private final String message;
private final Handler[] handlers;
private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenInsertOrNextHandler.class);
public PrintThenInsertOrNextHandler(String message, Handler... handlers) {
this.message = message;
this.handlers = handlers;
}
public void handle(Context context) {
LOGGER.info(message);
if (handlers.length == 0) {
context.next();
} else {
context.insert(handlers);
}
}
}
public class Application implements Handler {
public void handle(Context context) {
context.insert(
new PrintThenInsertOrNextHandler("a",
new PrintThenInsertOrNextHandler("a.1"),
new PrintThenInsertOrNextHandler("a.2"),
),
new PrintThenInsertOrNextHandler("b",
new PrintThenInsertOrNextHandler("b.1",
new PrintThenInsertOrNextHandler("b.1.1")
),
),
new PrintThenInsertOrNextHandler("c")
);
}
}
これは、System.out
に次のように出力されます…
a
a.1
a.2
b
b.1
b.1.1
c
これは、ハンドラーを挿入するハンドラーの次のハンドラーが、挿入されたハンドラーの最後のハンドラーの次のハンドラーになる方法を示しています。この文章を複数回読む必要があるかもしれません。
ある種のネスト機能が出現していることがわかるでしょう。これは、合成可能性にとって重要であり、後でこの章でレジストリコンテキストを検討する際に重要になるスコープにとっても重要です。
この時点で、典型的なWebアプリケーション(特定のリクエストパスに一致するリクエストをエンドポイントにディスパッチするアプリケーション)のハンドラー構造を構築するには多くの作業が必要だと考えるのは自然でしょう。読み進めてください。
3.5 ハンドラーチェーンの構築
チェーン(Chain
)は、ハンドラーを合成(またはチェーン)するためのビルダーです。チェーン自体はリクエストに応答しませんが、代わりにリクエストを添付されたハンドラーに渡します。
Foo-Barルーターの例をもう一度考えてみましょう…
import ratpack.core.handling.Chain
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import ratpack.func.Action;
public class FooHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("foo");
}
}
public class BarHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("bar");
}
}
public class RouterChain implements Action<Chain> {
private final Handler fooHandler = new FooHandler();
private final Handler barHandler = new BarHandler();
@Override
void execute(Chain chain) throws Exception {
chain.path("foo", fooHandler)
chain.path("bar", barHandler)
}
}
今回は、パスを手動でチェックして各コードブランチを処理する必要はありませんでした。ただし、結果は同じです。このチェーンは最終的にハンドラーとして扱われます。このハンドラーは、リクエストからパスを読み取り、最初に「foo」と比較し、次に「bar」と比較するように設定されます。いずれかに一致した場合、指定されたハンドラーをcontext.insert()
します。そうでない場合、context.next()
を呼び出します。
ハンドラーと同様に、コンテキストは魔法のピースではありません。代わりに、より柔軟なツールであるハンドラーから構築された強力なツールです。
1.3.5 ハンドラーとチェーンの追加
したがって、チェーンは最も単純にハンドラーのリストとして考えることができます。チェーンのリストにハンドラーを追加する最も基本的な方法は、all(Handler)
メソッドです。「all」という単語は、チェーンのこの時点に到達するすべてのリクエストが指定されたハンドラーを通過することを表しています。
少し発想を広げて、チェーンをハンドラー(ハンドラーを挿入することを専門とするハンドラー)と考えると、チェーンにさらにチェーンを追加できることも理にかなっています。実際、それは可能であり、all(Handler)
メソッドと一致させるために、insert(Action<Chain>)
メソッドを使用できます。同様に、これはすべてのリクエストがルーティングされるチェーンを挿入します。
さて、チェーンが単にハンドラーのリストを処理し、それぞれを順番に呼び出すだけの場合、チェーンはあまり役に立ちません。そのため、ハンドラーとチェーンの条件付き挿入を実行できるいくつかのメソッドもあります。
path(String,Handler)
は、前の例で使用されているように、リクエストパスに基づいて異なるハンドラーにルーティングする場合に特に便利です。空の""パスに簡単に一致させるために、path(Handler)
フレーバーもあります。onlyIf(Predicate<Context>, Handler)
は、プログラムによる動作に基づいてルーティングするために使用できます。host(String, Action<Chain>)
は、リクエストに特定のHostヘッダー値がある場合に別のチェーンを挿入します。when(Predicate<Context>, Action<Chain>)
は、プログラムによる動作が満たされた場合にチェーンを挿入します。
2.3.5 レジストリ
TODO (Chain
のJavadocに技術的な定義があります)
3.3.5 パスバインディング
(例: /player/:id )
TODO (Chain
のJavadocに技術的な定義があります)
4.3.5 パスとメソッドのバインディング
TODO (Chain
のJavadocに技術的な定義があります)