Scala/Play: なんでも受けるアクションとビューを作る

Play Framework で、どのパスにアクセスされても同じコントローラで対応する方法を試す。

ルーティング、アクション、ビュー

Play で新しいアクションとビューを作る場合、どんなリクエストがあったときにどのアクションを呼び出すかという関連付けも行う必要がある。この関連付けをルーティングと呼ぶ。
どういう順番でやるのがあるべき姿なのかわからないので、とりあえずアクションから行こう。

アクション

作成したプロジェクトの app ディレクトリ配下にソースが格納されている。controllers.Application がテンプレートで作られたコントローラ。すでに index というアクションが定義されているが、とりあえずこれをコピって view という名前で作ってみる。アクションの名前が view というのはややこしくなりそうだが、view (表示) というアクションを作りたいのでこの際やむを得ない。

  def view(path: String) = Action {
    Ok(views.html.view(s"<h1>${path}</h1>"))
  }

この時点では、views.html.view に赤線が引かれている。まだビューを作っていないので、コンパイルエラーになっているからだ。
恥ずかしながら Scala 初心者なので細かい部分がよくわかっていないが、追い追いわかるようになるだろう。たぶん。

ビュー

app/views の下に、ビューごとの HTML テンプレートがある。
テンプレートからプロジェクトを作った時点で、index.scala.html とそこから呼ばれる main.scala.html がある。今回はこれらと同じ場所に view.scala.html を作る。

@(message: String)

@message

これまたよくわかっていないが、たぶんこのクラス自体がクラスか関数かに変換される仕組みで、1 行目がコンストラクタか関数の引数の定義だと思う。とりあえずそのまま出してみる。

ルーティング

conf/routes というファイルがルーティングの定義。GET で / が要求された時は controllers.Application の index 関数を呼ぶ、というようなことが書かれている。
1 行で 1 つの条件と対応するアクションを書いていくが、リクエストがこのファイル内の複数の条件に合致した場合、上にあるものが使われる。ということで、今回はファイルの一番下に以下の内容を追加する。

GET     /*path                      controllers.Application.view(path)

パスのうち *path は、任意の文字列を表し、アクションの引数のところにも path と書いたので、パスの該当部分をアクションの第 1 引数として渡す、という意味。* の代わりに : を書くと、/ を含まない文字列、つまりパスの 1 レベル分だけ、ということになる。

エスケープの抑止

さて、さっそくアプリを動かしてみよう。
http://localhost:9000 にアクセスすると元からある index のアクションが動いてしまうので、ブラウザのアドレスバーに http://localhost:9000/foo とか http://localhost:9000/foo/bar とか入れてみる。
……すると、ブラウザ上に

<h1>foo</h1>

とか

<h1>foo/bar</h1>

とかが表示されてしまった。うん、たぶんそうなるんじゃないかなと思ってた。
HTML を渡したつもりなのに、フレームワークがうまいことエスケープしてくれてしまった結果、見出しが表示されずにタグがそのままブラウザ上に表示されてしまったわけだ。
公式サイトの下記のページに解決法がある。

つまり、ビューを以下のように直せばよい。

@(message: String)

@Html(message)

もう一度アクセスすると、ちゃんと foo や foo/bar が見出しとして表示される。