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

## Ratpack入門シリーズ

1. [Ratpack入門 (1) - Ratpackとは](https://qiita.com/minebreaker/items/d7eeca815872a97069f5)
2. [Ratpack入門 (2) - アーキテクチャー](https://qiita.com/minebreaker/items/a9b256fd8a1513104669)
3. [Ratpack入門 (3) - hello world 詳解](https://qiita.com/minebreaker/items/b26d7d0677f91f17a7b5)
4. [Ratpack入門 (4) - ルーティング & 静的コンテンツ](https://qiita.com/minebreaker/items/8f26d8350f936ad669c2)
5. [Ratpack入門 (5) - Json & Registry](https://qiita.com/minebreaker/items/4353611cc541e3cb35fb)
6. [Ratpack入門 (6) - Promise](https://qiita.com/minebreaker/items/4689058d0d7b34d99c74)
7. [Ratpack入門 (7) - Guice & Spring](https://qiita.com/minebreaker/items/52b303bc18db5b33bd68)
8. [Ratpack入門 (8) - セッション](https://qiita.com/minebreaker/items/1ebaa492b257365e1e5e)
9. [Ratpack入門 (9) - Thymeleaf](https://qiita.com/minebreaker/items/50693a4d6614979f654c)


## ルーティング

前述したように、ルーティングは`Chain`クラスに各パスとマッピングするハンドラーを記述していく形で行います。
正式なドキュメントは[ChainのJavadoc](https://ratpack.io/manual/current/api/ratpack/handling/Chain.html)にありますので、ここで解説しきれない分はそちらを参照してください。


## 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メソッドが呼ばれたとき |

```java
chain.prefix( "hoge", innerChain -> {
    innerChain.get( "piyo", handler );  // /hoge/piyo
} );
```

そのほか、`Context`には`byMethod()`および`byContent`というコンビニエンスメソッドが用意されており、イディオマティックに分岐を記述できます。

```java
// パス"hoge"がリクエストされたとき、GETとPOSTで分岐させる
chain.path( "hoge", ctx -> {
    ctx.byMethod( m -> {
        m.get( iCtx -> iCtx.render( "get" ) );
        m.post( iCtx -> iCtx.render( "post" ) );
    } );
} );
```

```java
// "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](https://ratpack.io/manual/current/api/ratpack/handling/Chain.html#path-binding)より翻訳)

パスパラメーターは、`Context.getPathToken()`で取得できる`PathTokens`クラス経由で取得できます。

```java
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`クラス経由で取得します。

```java
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`

```java
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()`メソッドを利用できます。

上記の例のハンドラーを、以下のように変更してみてください。

```java
Action<Chain> handlers = chain -> {
    chain.files();
    chain.get( "hoge", ctx -> ctx.render( ctx.file( "index.html" ) ) );
};
```

`http://localhost:5050/hoge`にアクセスすると、`index.html`の内容が表示されます。