HTTP ルーティング
Router は、送り込まれた HTTP リクエストをアクション (コントローラの static で public なメソッド) の呼び出しに対応付けるコンポーネントです。
HTTP リクエストは MVC フレームワークにおいてイベントと見なされます。このイベントは 2 つの主要な情報を含んでいます:
- クエリ文字列を含んだ (
/clients/1542
や/photos/list
のような) リクエストパス - HTTP メソッド (GET, POST, PUT, DELETE)
REST について
REST (Representational state transfer) は、World Wide Web などの分散ハイパーメディアシステムのためのソフトウェアアーキテクチャスタイルです。
REST が述べるいくつかの主要な設計原理は以下の通りです:
- アプリケーションの機能はリソースに分割される
- あらゆるリソースは URI を使用することで一意にアドレス付けられる
- すべてのリソースはクライアントとリソースの間で状態を転送するために統一インタフェースを共有する
HTTP を使用する場合は、インタフェースは利用可能な HTTP メソッドのセットとして定義されます。 リソース状態にアクセスするために使用されるプロトコルは、以下の通りです:
- クライアント-サーバ
- ステートレス
- キャッシュ可能
- 階層化
アプリケーションが主な REST 設計原理に従うなら、そのアプリケーションは RESTful です。Play フレームワークを使うと、容易に RESTful アプリケーションを構築することができます:
- Play の router は Java の呼び出しにリクエストを転送するために URI と HTTP メソッドの両方を解釈します。正規表現ベースの URI パターンは高い柔軟性を提供します。
- プロトコルはステートレスです。これは、2 つの連続したリクエスト間において、どのような状態もサーバに保存できないことを意味します。
- Play は HTTP を重要な特徴と見なすので、HTTP に含まれる情報への完全なアクセスを提供します。
routes ファイルの構文
conf/routes
ファイルは、Router が使用する設定ファイルです。このファイルはアプリケーションに必要なすべてのルートを記載します。各ルートは Java 呼び出しに関連付けられた HTTP メソッド + URI パターンで構成されます。
ルート定義がどのようなものか見てみましょう:
GET /clients/{id} Clients.show
各ルートは HTTP メソッドから始まり、URI パターンが続きます。ルーティングの最後の要素は Java 呼び出しの定義です。
#
文字でルートファイルにコメントを追加できます。
# Display a client
GET /clients/{id} Clients.show
HTTP メソッド
HTTPによってサポートされてる有効なメソッドのうち、いずれかひとつをルートの HTTP メソッドに指定することができます:
- GET
- POST
- PUT
- DELETE
- HEAD
Moreover it supports WS
as action method to indicate a WebSocket request.
HTTP メソッドに * を指定した場合、このルートはどのようなメソッドの HTTP リクエストにもマッチします。
* /clients/{id} Clients.show
このルートは以下のいずれにも適合します:
GET /clients/1541
PUT /clients/1212
URI パターン
URI パターンはルートのリクエストパスを定義します。ルートの一部を動的に定義することが可能です。動的部分はすべて 中括弧 {…} の中に指定しなければいけません。
/clients/all
完全にマッチします:
/clients/all
しかし …
/clients/{id}
これらについてもそれぞれマッチします:
/clients/12121
/clients/toto
URI パターンにはより多くの動的な部分があるかもしれません:
/clients/{id}/accounts/{accountId}
動的部分のデフォルトのマッチング方式は、正規表現 /[^/]+/
として定義されています。動的部分の正規表現を独自に定義することができます。
この正規表現は id に数値のみを許可します:
/clients/{<[0-9]+>id}
これは id が 4~10 文字の小文字だけを含む単語であることを保証します:
/clients/{<[a-z]{4,10}>id}
ここではどのような正規表現も使用することができます。
注意
動的部分には名前が付けられます。コントローラは後から HTTP パラメータの Map から動的部分を検索することができます。
デフォルトで、Play は URL のあとに続くスラッシュを重要視します。例えば次のようなルートの場合:
GET /clients Clients.index
/clients という URL にはマッチしますが、 /clients/ にはマッチしません。スラッシュの後にクエスチョンマークを追加することで、Play に両方の URL にマッチするよう伝えることができます。例えば、次のようにします:
GET /clients/? Clients.index
この URI パターンは、最後のスラッシュ以外にはどのような追加部分も持つことはできません。
Java の呼び出し定義
ルート定義の最後の部分は Java 呼び出しです。この部分はアクションメソッドの完全修飾名で定義されます。アクションメソッドは Controller クラスの public static void
なメソッドでなければいけません。Controller クラスは controllers
パッケージに定義しなければならず、また、 play.mvc.Controller
のサブクラスでなければいけません。
コントローラが controllers パッケージの直下に定義されていない場合は、クラス名の前に Java パッケージ名を追記することができます。 controllers
パッケージ自体は暗黙的なので、特に指定する必要はありません。
GET /admin admin.Dashboard.index
404 アクション
アプリケーションにおいて無視されなければならない URL パスを印付けるために、 404
をアクションルートとして直接使用することができます。例えば:
# Ignore favicon requests
GET /favicon.ico 404
静的引数の割り当て
ある場合においては、アクションは再利用しても、いくつかの引数の値に基づいてより特別なルートを定義したいことがあります。
これがどのようなものか、以下の例を見てみましょう:
public static void page(String id) {
Page page = Page.findById(id);
render(page);
}
対応するルートは次のとおりです:
GET /pages/{id} Application.page
ここで、このページに ‘home’ という ID で別名を定義したいと思います。静的引数を使って別のルートを定義することができます:
GET /home Application.page(id:'home')
GET /pages/{id} Application.page
最初のルートは、ページの id が ‘home’ である場合、2 番目のルートと等価です。しかし、最初のルートはより高い優先度を持つので、id が ‘home’ である Application.page 呼び出しのデフォルト定義として使用されます。
変数とスクリプト
テンプレートと同様、 routes
ファイルにおいても変数に ${ … }
構文を、式に %{ … }
構文を使うことができます。例えば:
%{ context = play.configuration.getProperty('context', '') }%
# Home page
GET ${context} Secure.login
GET ${context}/ Secure.login
別の例として、すべての型についてコントローラルート定義を生成するために crud.types
タグを使ってモデルの型全体をループする CRUD モジュールの routes
ファイルがあります。
ルーティングの優先順位
複数のルートが同じリクエストにマッチすることができます。競合がある場合は、(宣言した順番に従い) 最初のルートが使用されます。
例えば:
GET /clients/all Clients.listAll
GET /clients/{id} Clients.show
これらの定義において、URI が次のとおりである場合:
/clients/all
(2 番目のルートもリクエストにマッチするとしても) 最初のルートが適用されて、Clients.listAll をコールします。
静的リソースの配信
staticDir: マッピング
静的なリソースのコンテナとして公開したい場合、各フォルダを特別なアクション staticDir
を使って示してください。
例えば:
GET /public/ staticDir:public
/public/*
パスに対するリクエストがあった場合、Play はアプリケーションの /public フォルダからファイルを配信します。
優先度は通常のルートと同じように適用されます。
staticFile: マッピング
URL パスをレンダリングする静的なファイルに直接マッピングすることもできます。
# Serve index.html static file for home requests
GET /home staticFile:/public/html/index.html
URL エンコーディング
URL をデコードして再度エンコードすることは不可能 (例えば、スラッシュがスラッシュなのか %2F なのかは分かりません) なので、URL はエンコードされて表現されるべきです。UTF-8 がデフォルトのエンコーディングですが、defaultWebEncoding 設定パラメータを使って ISO-8859-1, UTF-16BE, UTF-16LE, UTF-16 のうちひとつを使うことができます。詳細は http://download.oracle.com/javase/1.4.2/docs/api/java/nio/charset/Charset.html を参照してください。
例えば:
- map /stéphane
GET /st%C3%A9phane Application.stephane
リバースルーティング : URL の生成
Java 呼び出し中に URL を生成するのに Router が使用できます。このため、すべての URI をただひとつの設定ファイルに集約することが可能であり、より自信をもってアプリケーションをリファクタリングすることができます。
例えば、次のようなルート定義において:
GET /clients/{id} Clients.show
Clients.show を起動することができる URL を、コードから生成することができます:
map.put("id", 1541);
String url = Router.reverse("Clients.show", map).url;// GET /clients/1541
この URL 生成機能はフレームワークの様々なコンポーネントに統合されています。決して Router.reverse を直接使用するべきではありません。
URI パターンに含まれていないパラメタを加えると、これらのパラメタはクエリストリングに追加されます:
map.put("id", 1541);
map.put("display", "full");
// GET /clients/1541?display=full
String url = Router.reverse("Clients.show", map).url;
URL を生成するもっとも特別なルートを見つけるため、優先順位が再度使用されます。
content type の設定
Play は request.format
に従って HTTP レスポンスの メディアタイプ を選択します。この値は、どの view テンプレートを使用するかをファイル拡張子によって決定し、また Play の mime-types.properties
ファイルがこのフォーマットにマッピングするメディアタイプを、レスポンスの Content-type
に設定します。
Play に対するリクエストのデフォルトフォーマットは html
です。そのため、コントローラの index()
メソッド (そして html
フォーマット) に対するデフォルトのテンプレートは index.html
です。もし違うフォーマットを指定する場合、いくつかある方法のひとつとして、代替テンプレートを選択することができます。
render
メソッドを呼び出す前に、このフォーマットをプログラム上から設定することができます。例えば、 text/css
メディアタイプと共に CSS を提供する場合、次のように設定します:
request.format = "css";
一方、 routes
ファイルにおいて URL を使ってフォーマットを指定する、よりきれいなアプローチもあります。コントローラのメソッドにフォーマットを指定することで、特定のルートにフォーマットを追加することができます。例えば、以下のルートは /index.xml
に対するリクエストをハンドリングし、フォーマットを xml
に設定し、 index.xml
テンプレートをレンダリングします。
GET /index.xml Application.index(format:'xml')
同様に:
GET /stylesheets/dynamic_css css.SiteCSS(format:'css')
Play は、以下のルートのように、URL から直接フォーマットを抽出することもできます。
GET /index.{format} Application.index
このルートでは、 index.xml
に対するリクエストには xml
がフォーマットに設定されて XML テンプレートがレンダリングされますが、 index.txt
に対するリクエストにはプレーンテキストのテンプレートがレンダリングされます。
Play は HTTP コンテントネゴシエーションを使って自動的にフォーマットを設定することもできます。
HTTP コンテントネゴシエーション
Play が他の RESTful アーキテクチャと共通していることのうちのひとつは、HTTP を隠そうとしたり、HTTP の上に抽象層を置こうとしたりせず、HTTP の機能を直接使用するということです。コンテントネゴシエーション は、HTTP サーバが、HTTP クライアントによりリクエストされたメディアタイプに従って、同じ URL に対して異なる メディアタイプ を提供できるようにする HTTP の機能です。クライアントは、例えば XML レスポンスを要求する場合、以下のように Accept
ヘッダを使って受信できるコンテントタイプを指定します:
Accept: application/xml
クライアントはひとつ以上のメディアタイプを指定するかもしれませんし、ワイルドカード (*/*
) を使用して、どのようなメディアタイプも受信できると指定するかもしれません:
Accept: application/xml, image/png, */*
古臭い web ブラウザは、どのようなメディアタイプでも受信しようと Accept
ヘッダにいつでもワイルドカードを含めます: そして Play は HTML を - デフォルトの ‘フォーマット’ として提供します。コンテントネゴシエーションは JSON レスポンスを要求する Ajax リクエストや、PDF や EPUB バージョンのドキュメントを要求する e-book リーダのような、カスタマイズされたクライアントにおいて、より頻繁に利用されるでしょう。
HTTP ヘッダによるコンテントタイプの設定
Accept
ヘッダに text/html
か application/xhtml
が含まれる場合、または ワイルドカード */*
が指定されている場合、Play はデフォルトのリクエストフォーマットである html
を選択します。ワイルドカード値が存在しない場合はデフォルトフォーマットは選択されません。
Play はいくつかのフォーマット: html
, txt
, json
と xml
を組み込みでサポートします。例えば、いくつかのデータをレンダリングするコントローラメソッドを定義します:
public static void index() {
final String name = "Peter Hilton";
final String organisation = "Lunatech Research";
final String url = "http://www.lunatech-research.com/";
render(name, organisation, url);
}
このメソッドにマッピングされた URL (新しい Play アプリケーションの場合は http://localhost:9000/
) を web ブラウザからリクエストすると、web ブラウザは値に text/html
を含む Accept
ヘッダを送信するので、Play は index.html
テンプレートをレンダリングします。
Play は、リクエストフォーマットに xml
を設定し、例えば以下のような index.xml
テンプレートをレンダリングすることで、 Accept: text/xml
ヘッダを含むリクエストに応答します:
<?xml version="1.0"?>
<contact>
<name>${name}</name>
<organisation>${organisation}</organisation>
<url>${url}</url>
</contact>
index()
コントローラメソッドに対する組み込みの Accept
ヘッダとフォーマットのマッピングは下記のように動作します: accept ヘッダは、Play がやがてはテンプレートファイルに紐付けられることになるフォーマットにマッピングしたメディアタイプを含みます。
Accept header | Format | Template file name | Mapping |
---|---|---|---|
null | null | index.html | null フォーマットに対するデフォルトのテンプレート拡張 |
image/png | null | index.html | フォーマットにマッピングされないメディアタイプ |
*/*, image/png | html | index.html | html フォーマットにマッピングされるデフォルトのメディアタイプ |
text/html | html | index.html | 組み込みフォーマット |
application/xhtml | html | index.html | 組み込みフォーマット |
text/xml | xml | index.xml | 組み込みフォーマット |
application/xml | xml | index.xml | 組み込みフォーマット |
text/plain | txt | index.txt | 組み込みフォーマット |
text/javascript | json | index.json | 組み込みフォーマット |
application/json, */* | json | index.json | デフォルトのメディアタイプを無視する組み込みフォーマット |
カスタムフォーマット
HTTP リクエストが対応するメディアタイプを選択する場合にのみ、リクエストヘッダを調査し、それに従ってフォーマットを設定し、独自のメディアタイプについてコンテントネゴシエーションを追加することができます。例えば、vCard を text/x-vcard
メディアタイプと共に提供するには、コントローラにて全てのリクエストの前に独自のフォーマッとチェックを行います:
@Before
static void setFormat() {
if (request.headers.get("accept").value().equals("text/x-vcard")) {
request.format = "vcf";
}
}
これで、 Accept: text/x-vcard
を含むリクエストは、以下のような index.vcf
テンプレートをレンダリングするようになります:
BEGIN:VCARD
VERSION:3.0
N:${name}
FN:${name}
ORG:${organisation}
URL:${url}
END:VCARD
考察を続けます
Router が受け取った HTTP リクエストによって起動する Java 呼び出しを決定したら、Play フレームワークはその Java 呼び出しを起動します。 コントローラ がどのように動作するのかを見てみましょう。