Ratpack入門シリーズ
- Ratpack入門 (1) - Ratpackとは
- Ratpack入門 (2) - アーキテクチャー
- Ratpack入門 (3) - hello world 詳解
- Ratpack入門 (4) - ルーティング & 静的コンテンツ
- Ratpack入門 (5) - Json & Registry
- Ratpack入門 (6) - Promise
- Ratpack入門 (7) - Guice & Spring
- Ratpack入門 (8) - セッション
- Ratpack入門 (9) - Thymeleaf
ルーティング
前述したように、ルーティングはChainクラスに各パスとマッピングするハンドラーを記述していく形で行います。
正式なドキュメントはChainのJavadocにありますので、ここで解説しきれない分はそちらを参照してください。
Handlerを追加するための各メソッド
Chainにおける一番基本的なメソッドはall(Handler)であり、これは全てのリクエストがこのハンドラーによって処理されることを示します。他の各メソッドは、実はこのメソッドに「リクエストを受け付ける条件」を追加した、コンビニエンスメソッドにすぎません。
内部的にはHandlersの各staticユーティリティーメソッドで、渡されたHandlerをラップすることで処理しています。
Handlerのインスタンス自体ではなくClass<Handler>を引数に取るものは、コンテキストのRegistryから当該クラスを使用します。
あまり意味があるように思わないかもしれませんが、GuiceなどのDIコンテナと組み合わせるときに威力を発揮します。
以下、主要なメソッドを解説します(量が多いのですべては無理)。
| メソッド | ハンドラーが呼び出される条件 |
|---|---|
all(Handler) |
必ず |
files() |
後述 |
onlyIf(Predicate<Context>, Handler) |
Predicateがtrueになるとき |
when(boolean/Predicate, Action<Chain>) |
ブール値またはPredicateがtrueのとき、Action<Chain>に委譲 |
prefix(String, Action<Chain>) |
パスが指定文字列から始まるとき、相対パスで解決されるAction<Chain>に委譲 |
path(Handler) |
リクエストが指定のパスにマッチするとき |
get(String, Handler) |
指定のパスにマッチし、GETメソッドが呼ばれたとき |
post(String, Handler) |
指定のパスにマッチし、POSTメソッドが呼ばれたとき |
patch(String, Handler) |
指定のパスにマッチし、PATCHメソッドが呼ばれたとき |
put(String, Handler) |
指定のパスにマッチし、PUTメソッドが呼ばれたとき |
delete(String, Handler) |
指定のパスにマッチし、DELETEメソッドが呼ばれたとき |
chain.prefix( "hoge", innerChain -> {
innerChain.get( "piyo", handler ); // /hoge/piyo
} );
そのほか、ContextにはbyMethod()およびbyContentというコンビニエンスメソッドが用意されており、イディオマティックに分岐を記述できます。
// パス"hoge"がリクエストされたとき、GETとPOSTで分岐させる
chain.path( "hoge", ctx -> {
ctx.byMethod( m -> {
m.get( iCtx -> iCtx.render( "get" ) );
m.post( iCtx -> iCtx.render( "post" ) );
} );
} );
// "hoge"リクエストの、`Accept`ヘッダーの値で分岐する
chain.path( "hoge", ctx -> ctx.byContent( accept -> {
accept.json( iCtx -> iCtx.render("json") );
accept.xml( iCtx -> iCtx.render( "xml" ) );
accept.noMatch( iCtx -> iCtx.render( "no match" ) );
} ) );
ちなみにここでは変数名をベタ書きしていますが、メソッドチェーンで記述することもできます。
パスのリテラル
パスに指定する文字列には、正規表現などのリテラルが使え、パスパラメーターの設定なども可能です。
| 種類 | シンタックス | 例 |
|---|---|---|
| リテラル | foo |
"foo" |
| 正規表現リテラル | ::<<regex>> |
"foo/::\d+" |
| オプションのパストークン | :<<token-name>>? |
"foo/:val?" |
| 必須パストークン | :<<token-name>> |
"foo/:val" |
| オプションの正規表現パストークン | :<<token-name>>?:<<regex>> |
"foo/:val?:\d+" |
| 必須正規表現パストークン | :<<token-name>>:<<regex>> |
"foo/:val:\d+" |
(Javadocより翻訳)
パスパラメーターは、Context.getPathToken()で取得できるPathTokensクラス経由で取得できます。
chain.get( "hoge/:name", ctx -> ctx.render( "name: " + ctx.getPathTokens().get( "name" ) ) );
chain.get( "piyo/:id:\\d+", ctx -> ctx.render( "id: " + ctx.getPathTokens().asLong( "id" ) ) );
| パス | 結果 |
|---|---|
| /hoge | 404 |
| /hoge/John | "name: John" |
| /piyo/hoge | 404 |
| /piyo/123 | "id: 123" |
Contextの動作
Contextクラスは、ある一つのリクエストがどのように呼び出されているかの情報を保持しているクラスです。
Hello worldで見たように、HandlerはContextを受け取るラムダ式です。従って、一連の処理の流れは、
Contextを受け取って -> いろいろな処理を行い -> Context.render()を呼び出し、レスポンスを作成する
という形になります。
クエリパラメーター
URLの?から後の部分のやつです。
getRequest().getQueryParams()から、マップとして取得できます。
パスパラメーター
上述したように、getPathToken()を使用します。
FORMパラメーター
HTTPの<form>タグなどから送信されるフォームパラメーターは、parse()メソッドを使用して、Formクラス経由で取得します。
chain.path( "hoge", ctx -> ctx.parse( Form.class ).then( form -> {
ctx.render( "Received: " + form.entrySet() );
} ) );
parse()については後述します。
リクエストのボディー
getRequest().getBody()から取得できます。
ここで注意するべきなのは、getBody()メソッドはボディーの内容を返すのではなく、ボディーのPromiseを返す点です。
Promiseの使い方は今後の投稿で説明予定です。
ヘッダー
header(CharSequence)もしくはgetRequest().getHeaders()から取得できるHeadersクラス経由で取得します。
クッキー
Request.getCookies()で取得できます。
レスポンスを返す
レスポンスの設定は、getResponse()からResponseクラスを取得し、そのインスタンスに設定していく形で行います。実際の送信は、Context.render(Object)や、Response.send()で行います。
ステータスコード
status(int)メソッドで指定できます。
クッキー
Cookieクラスの作成はcookie()メソッドの呼び出しで行われます。返されるCookieオブジェクトを、あらためてgetCookies()メソッドで返されるセットに追加する必要はありません。
ヘッダー
Response.getHeaders()で、ヘッダーの可変セットが取得できます。contentType()など、コンビニエンスメソッドも用意されています。
Context.redirect(String)
302リダイレクトを発生させ、指定のパスに遷移させます。
Context.clientError(int)
ステータスコード400番台のエラーを発生させたいときに使用します。
404に関しては、コンビニエンスメソッドnotFound()が用意されています。
Context.render(Object)
レスポンスを返す最も基本的なメソッドです。
BaseDir
静的ファイルを提供するために、RatpackはBaseDirという仕組みを提供しています。
まず、ServerConfigにBaseDirのPathを設定する必要があります。普通にJDKのPathを作成してもよいのですが、クラスローダーからリソースを読み込むためのBaseDir.find()メソッドが用意されています。ServerConfigBuilder.findBaseDir()というコンビニエンスメソッドもあります。find()は、指定されたパスにある.ratpackという空のファイルを探します。見つかった場合、そのパスを内部的な基準となるパスとして、静的コンテンツの提供を行います。
次に、BaseDirからファイルを提供するハンドラーを作る必要があります。Chain.files()メソッドは、上記で設定したBaseDirから、相対パスとして解決されたURLにリクエストがあった時、そのファイルの内容をレスポンスとして送信します。
具体例です。ここではリソース内にpublicというフォルダを作り、そこをBaseDirに設定してみます。
リソースとして下記を作成します。
resources/public/.ratpack
resources/public/index.html
Path baseDir = BaseDir.find( "public/.ratpack" );
ServerConfig config = ServerConfig.builder()
.baseDir( baseDir )
.development( true ).build();
Action<Chain> handlers = chain -> {
chain.files();
};
RatpackServer.start( server -> server
.serverConfig( config )
.handlers( handlers ) );
http://localhost:5050/index.htmlにアクセスすると、作成したHTMLファイルの内容が表示されるはずです。
独自のパスにファイルを結び付けたい場合は、Context.render()メソッドにPathを渡します。BaseDirからファイルを取得するために、Context.file()メソッドを利用できます。
上記の例のハンドラーを、以下のように変更してみてください。
Action<Chain> handlers = chain -> {
chain.files();
chain.get( "hoge", ctx -> ctx.render( ctx.file( "index.html" ) ) );
};
http://localhost:5050/hogeにアクセスすると、index.htmlの内容が表示されます。