1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ratpack入門 (4) - ルーティング & 静的コンテンツ

Last updated at Posted at 2018-01-19

Ratpack入門シリーズ

  1. Ratpack入門 (1) - Ratpackとは
  2. Ratpack入門 (2) - アーキテクチャー
  3. Ratpack入門 (3) - hello world 詳解
  4. Ratpack入門 (4) - ルーティング & 静的コンテンツ
  5. Ratpack入門 (5) - Json & Registry
  6. Ratpack入門 (6) - Promise
  7. Ratpack入門 (7) - Guice & Spring
  8. Ratpack入門 (8) - セッション
  9. 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) Predicatetrueになるとき
when(boolean/Predicate, Action<Chain>) ブール値またはPredicatetrueのとき、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で見たように、HandlerContextを受け取るラムダ式です。従って、一連の処理の流れは、
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の内容が表示されます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?