Slim3における拡張子付きURIのマッピング
CoolなURIでJSONを返すURLはどう表現するか
内部的には「/user/get?id=12345&type=json」となるようなURI(idが12345のユーザ情報のJSON形式)を、Cool URIではどのように表現するのが適切か悩んでいた。「/user/json/12345」や「/user/12345/json」はなんか違う気がするし。
そしたら「Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESSプラスシリーズ)」にその答えが書いてあった。
例えば1つのリソースをHTMLとテキストとJSONで表現できる場合には、それぞれ「.html」「.txt」「.json」という拡張子を付けてここの表現を分けると良いでしょう。
「Webを支える技術」 p.062
先述の例で言えば、「/user/12345.json」となる。なるほど確かにこれはしっくりくる。ということで、txtとjsonの拡張子に対応したURIの実装を、Slim3で試した。
URIマッピングはRouterImpl#addRouting()で設定
まず、Slim3でのURLマッピングは、RouterImplを実装したAppRouter内のaddRouting(from, to)で下記のように設定する。
public AppRouter() { addRouting("/entry/delete/{key}", "/entry/delete?key={key}"); }
※注意点として、fromとtoにはそれぞれひとつ以上の{xxx}を含める必要があるみたい。ひとつも含まないと正しくマッピングが行われなかった。
ただし、拡張子を含んだURIは、Slim3はデフォルトで静的リクエストとみなすので、マッピングが行われない。そこで、AppRouterにさらに手を加える必要がある。
拡張子を含むURIはIsStatic()をオーバーライドする
拡張子つきURIが静的リクエストと見なされるのを回避するには、RouterImpl#IsStatic()をオーバーライドして、処理を記述する。拡張子が.jsonのURIを静的とみなさないようにするには、下記のようなコードを書けば良い。
@Override public boolean isStatic(String path) throws NullPointerException { boolean isStatic = super.isStatic(path); if("json".equals(RequestUtil.getExtension(path)) || "txt".equals(RequestUtil.getExtension(path))) { return false; } else { return isStatic; } }
そのうえで、addRouting()は下記のように設定。
public AppRouter() { addRouting( "/entry/{id}\\.{extension}", "/entry/get?id={id}&extension={extension}"); }
上記によりマッピングされるGetControllerは以下のような感じ。
public class GetController extends Controller { @Override protected Navigation run() throws Exception { String extension = request.getParameter("extension"); String id = request.getParameter("id"); if ("json".equals(extension)) { response.setContentType("application/json"); // responseにJSONを書き込む処理 } else if("txt".equals(extension)) { response.setContentType("text/plain"); // responseにテキストの内容を書き込む処理 } else { // エラー } return null; } }
これで、「/user/12345.json」ならjson形式で、「/user/12345.txt」ならテキスト形式でid=12345のユーザ情報が取得できる。対応する拡張子が増えたら、AppRouter#IsStatic()とGetControtter#run()の分岐に追加すればいい。
拡張子によってControllerを分ける場合
run()の中で分岐せず、拡張子によってマッピングするコントローラ自体を切り替えたいなら、下記のようにaddRouting()すればできる。
addRouting("/entry/{id}\\.{extension}", "/entry/get{extension}?id={id}");
そのうえで、GetjsonControllerやGettxtControllerを実装すればいい。この方法だとrun()内で分岐しないのでコードがすっきりするけど、マッピングの都合上、コントローラのクラス名がPascal形式(GetJsonControllerとかGetTxtController)にできないのが惜しい。(Getつけなければいいんだけど)
次は、httpメソッドに応じてマッピング先のControllerを切り替える実装について試行錯誤する予定。Controller#isGet()とかを使わずに。