こんにちは、鈴木です。
今回は Yesod のルーティングについて調べます。
Yesod のルーティングの特徴
Yesod のサイト(「Basics」や「Routing and Handlers」)を読むと、ルーティングについて以下のように説明されています。
- Front Controller パターンを採用している
- ルーティングは宣言的に記述することができる
- URL と Handler を一対一で記述する(正規表現を用いない)
- 正規の URL (canonical URL) にリダイレクトする
Front Controller パターンを採用している
一つ目は PHP や ASP のようにリクエストがあったときに個々のファイルが直接呼び出される方式ではなく、全てのリクエストを一箇所で受け取り、そこから適切な Handler にディスパッチされる、ということです。
ルーティングは宣言的に記述することができる
二つ目の「ルーティングの記述が宣言的」については、実際のルーティングの設定を見ていただくと分かりやすいです。
1 |
/ HomeR GET POST |
これは「/」に GET か POST でアクセスされた場合に HomeR という Handler を呼び出す、という設定です。Web アプリケーションフレームワークの中にはルーティングの設定を XML などで記述するものもありますが、そのようなものと比較すると、読みやすく、書きやすいでしょう。
宣言的に書かれたルーティングの設定は、コンパイル時点に Haskell のコードに変換されます。つまり、メタプログラミングによってコード生成が行われます。
URL と Handler を一対一で記述する(正規表現を用いない)
三つ目は便利さと間違いやすさのトレードオフで、そのような決定が行われたようです。
正規表現を用いると記述が楽になる場合はありますが、一方で意図しないパターンにマッチしてしまうというリスクがあります。
正規表現を使わなければ、ルーティングの記述量が増える場合があるものの、そのようなリスクは無くなります。
どちらも一長一短ですが、Yesod では URL と Handler を一対一で記述します。
正規の URL (canonical URL) にリダイレクトする
Yesod は URL には「正規の URL (canonical URL)」があると考えます。
「正規の URL」とは以下の条件を満たす URL です。
- 「/foo/bar/」のように末尾が「/」で終わっていない
- 「/foo//bar」のように「/」が連続していない
もし正規ではない URL にアクセスされると、Yesod は正規の URL にリダイレクトします。
つまり、以下のようにリダイレクトされます。
- /foo/bar/ → /foo/bar
- /foo//bar → /foo/bar
このようなリダイレクトを自動的に行うフレームワークは初めてだったので、少し驚きました。
ルーティングの書き方
config/routes を確認する
ルーティングは config/routes に記述します。
プロジェクトを作成した直後では、以下のような内容が記述されています。
1 2 3 4 5 6 |
/static StaticR Static getStatic /favicon.ico FaviconR GET /robots.txt RobotsR GET / HomeR GET POST |
先ほど ルーティングは宣言的に記述できると述べましたが、上記を見れば大体の内容は予想できますね。
ルーティングは「URLのパス」「Handler」「HTTPのメソッド」の順に書く
ルーティングの書き方は「URLのパス」「Handler」「HTTPのメソッド」をスペース区切りで記述すれば良さそうです。
1 行目の「/static StaticR Static getStatic」はこのルールから外れていますが、サブサイトというものを扱う書き方のようです。サブサイトについては深入りせずに、ここでは static ディレクトリにあるファイルを参照するためのものとしておきます。
3, 4 行目はそれぞれ config ディレクトリにある favicon.ico と robots.txt へのルーティングです。
6 行目は Handler/Home.hs で定義されている HomeR へのルーティングの設定です。「/」への GET または POST でのリクエストは HomeR にディスパッチされます。
URL パスの一部を付加的な情報として受け取る
URL パスの一部を付加的な情報として受け取るには、以下のように記述します。
1 |
/echo/#String EchoR GET |
上記のようにデータ型の前に「#」を付けることで、URL パスの一部を特定のデータ型として受け取ることができます。
Handler では関数の引数(message)として値を受け取ることができます。
1 2 |
getEchoR :: String -> Handler Html getEchoR message = defaultLayout $(widgetFile "echo") |
URL パスから受け取るパラメータを可変個にしたい場合は「#」ではなく「*」を使用します。
1 |
/echo/*Texts EchoR GET |
この場合は Handler では次のようにパラメータを受け取ります。
1 2 |
getEchoR :: Texts -> Handler Html getEchoR messages = defaultLayout $(widgetFile "echo") |
この方法を使えば「/echo/aaa」にも「/echo/aaa/bbb」にもマッチさせることができるので、Wiki のような CMS サイトを作るときなどに活用できそうです。
まとめ
ルーティングについて基本的なことは理解できたと思います。
ルーティングの書き方以外では、「正規の URL (canonical URL)」という考え方があることがポイントだと感じました。