6 コンテキスト
Context
型はRatpackの中核を成しています。
これは以下の機能を提供します。
リクエスト/レスポンスを直接操作するには、HTTPの章を参照してください。
委譲に関する情報は、ハンドラの章を参照してください。
1.6 コンテキストオブジェクト
コンテキストはレジストリです。これは、ハンドラパイプラインの上流で利用可能になったオブジェクトへのタイプルックアップによるアクセスを提供します。これは、Ratpackにおけるハンドラ間の連携メカニズムです。
以下の例を考えてみましょう。
import ratpack.test.embed.EmbeddedApp;
import ratpack.exec.registry.Registry;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static interface Person {
String getId();
String getStatus();
String getAge();
}
public static class PersonImpl implements Person {
private final String id;
private final String status;
private final String age;
public PersonImpl(String id, String status, String age) {
this.id = id;
this.status = status;
this.age = age;
}
@Override
public String getId() {
return id;
}
@Override
public String getStatus() {
return status;
}
@Override
public String getAge() {
return age;
}
}
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandlers(chain -> chain
.prefix("person/:id", (personChain) -> personChain
.all(ctx -> {
String id = ctx.getPathTokens().get("id"); // (1)
Person person = new PersonImpl(id, "example-status", "example-age");
ctx.next(Registry.single(Person.class, person)); // (2)
})
.get("status", ctx -> {
Person person = ctx.get(Person.class); // (3)
ctx.render("person " + person.getId() + " status: " + person.getStatus());
})
.get("age", ctx -> {
Person person = ctx.get(Person.class); // (4)
ctx.render("person " + person.getId() + " age: " + person.getAge());
}))
)
.test(httpClient -> {
assertEquals("person 10 status: example-status", httpClient.get("person/10/status").getBody().getText());
assertEquals("person 6 age: example-age", httpClient.get("person/6/age").getBody().getText());
});
}
}
(2)
では、ダウンストリームハンドラが使用するためにPerson
インスタンスをレジストリにプッシュし、(3)
と(4)
では、それらをどのように取得するかを示しています。作成の詳細と使用を切り離し、status
とage
ハンドラで作成コードを複製するのを避けています。複製を避けることの利点は明らかです。やや微妙なのは、ダウンストリームハンドラが匿名クラスとして実装されていない場合、デカップリングによりテストが容易になることです(詳細はテストの章を参照してください)。
(1)
でもコンテキストオブジェクトを使用しています。prefix()
チェーンメソッドはリクエストパスにバインドし、トークンを取得する可能性があります。バインディングが成功すると、バインディング結果を記述するPathBinding
オブジェクトがコンテキストに登録されます。これには、バインディングの一部として取得されたパストークンが含まれます。上記の場合、2番目のパスコンポーネントをid
として取得しています。コンテキストのgetPathTokens()
メソッドは、同じコンテキストのget(PathBinding.class).getPathTokens()
の略記です。これは、ハンドラ間の通信にコンテキストオブジェクトメカニズムを使用するもう1つの例です。
コンテキストオブジェクトを使用するもう1つの例は、ファイルシステムからファイルにアクセスするためのショートカットです。ファイルシステムから静的アセットを取得するためにコンテキストのfile
メソッドを使用する次のスクリプトを考えてみましょう。
import static ratpack.groovy.Groovy.ratpack
ratpack {
handlers {
get {
def f = file('../')
render f ?: "null-value"
}
}
}
上記の例では、コンテキストのfile()
メソッドを呼び出して、指定されたパスのjava.io.File
インスタンスを取得しています。コンテキストのfile()
メソッドは、レジストリからFileSystemBinding
オブジェクトを取得するためのショートカットであり、文字通りget(FileSystemBinding.class).file(path/to/file)
のショートカットです。コンテキストは常にアプリケーションルートを基準にファイルアセットを解決するため、絶対パスが提供された場合、アセットへのパスにはアプリケーションが存在するパスが接頭辞として付加されることに注意してください。たとえば、アプリケーションが/home/ratpack/app
に存在し、ハンドラがfile
メソッドを使用して/etc/passwd
を解決する場合、実際に解決されるパスは/home/ratpack/app/etc/passwd
になります。アプリケーションのルート内からファイルを解決できない場合、file()
メソッドはnull値を返す可能性があり、上記の例で示されています。ファイルへのアクセスがnullオブジェクトを返す可能性のあるシナリオは、開発者が処理する必要があります。
1.1.6 パーティショニング
コンテキストオブジェクトメカニズムは、異なるパーティションに異なるオブジェクトを提供することで、アプリケーションロジックのパーティショニングをサポートします。これは、コンテキストに登録されたオブジェクトは、登録方法に応じて暗黙的にスコープされるためです。next()
メソッドで登録されたオブジェクトは、同じ挿入(つまりcontext.insert()
を含むネストされた挿入)の一部であったすべてのダウンストリームハンドラで使用できます。insert()
メソッドで登録されたオブジェクトは、挿入されたハンドラとネストされた挿入で使用できます。
これの典型的な用途は、アプリケーションの異なる部分に対して異なるエラー処理戦略を使用することです。
import ratpack.core.error.ServerErrorHandler;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandlers(chain -> chain
.prefix("api", api -> api
.register(r -> r.add(ServerErrorHandler.class, (context, throwable) ->
context.render("api error: " + throwable.getMessage())
)
)
.all(ctx -> {
throw new Exception("in api - " + ctx.getRequest().getPath());
})
)
.register(r -> r.add(ServerErrorHandler.class, (ctx, throwable) ->
ctx.render("app error: " + throwable.getMessage())
)
)
.all(ctx -> {
throw new Exception("in app - " + ctx.getRequest().getPath());
})
).test(httpClient -> {
assertEquals("api error: in api - api/foo", httpClient.get("api/foo").getBody().getText());
assertEquals("app error: in app - bar", httpClient.get("bar").getBody().getText());
});
}
}