檜山正幸のキマイラ飼育記 このページをアンテナに追加 RSSフィード Twitter

キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
このブログの更新は、Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama
ところで、アーカイブってけっこう便利ですよ。

2010-01-20 (水)

RESTfulなWebサイトと拡張子を含むURLについて

| 11:11 | RESTfulなWebサイトと拡張子を含むURLについてを含むブックマーク

2009年12月16日「チュートリアルを少し変更、おバカな設定例」

Catyでは、ファイル名拡張子の意味付けや扱い方がデスクトップと同じなんだけど、「クールなURIは、拡張子がねーんだぞ」とか言われそうだから、そのうちラショネールを書かなきゃ。

「ラショネール」なんて奇妙な言葉が出てきてますが、目論見や主張が正当であることを示す根拠、てな意味ですかね>ラショネール。

僕とKuwataさんが開発しているWebフレームワークCatyは、URLに、.html, .cgi などの拡張子を必ず要求します。クエリパラメータも遠慮なしに使います。「拡張子とかクエリパラメータなんて、RESTfulじゃないなー、クールじゃないなー」とか言う人がいますが、なにゆえに「拡張子やクエリパラメータがダメなのか?」 -- その根拠を示して欲しいもんです。僕らが積極的に拡張子やクエリパラメータを使う事情と根拠は、このエントリーで述べます。

事前に「そろそろ決着、HTTPメソッド、URL、そして標準化された動詞」を読んでいただくと理解が早いと思います。

内容:

  1. 用語に関する注意
  2. 拡張子とクエリパラメータはRESTfulじゃない?
  3. なぜ、拡張子やクエリパラメータは嫌われてしまったのか
  4. 恐るべきクールなURI
  5. 検索にはパラメータ、検索結果にはリソースID
  6. 拡張子がないことがメリットなのか
  7. 拡張子が邪魔になるとき
  8. リソースタイプとコレクション
  9. 均質コレクションと不均質コレクション
  10. Catyの設計判断

用語に関する注意

この記事では、URLとURIは同義語です、特に区別はしません*1。URLの一部として、特殊な記号や日本語文字をそのまま書きますが、実際にはパーセント・エスケープされるものとします。URLのプロトコルやホスト名(ドメイン)の部分は、今回の話に重要ではないので、URLの例にはパス名だけを示す場合が多いでしょう。このパス名の部分を、この記事内で「リソースID」と呼ぶことがあります。

拡張子とクエリパラメータはRESTfulじゃない?

毎度引き合いに出している書籍『RESTful Webサービス』で、URL(URI)に関する記述を探してみました。

RESTful Webサービス

RESTful Webサービス

次の箇所に説明があります。

  1. 4.3 URI (p.86)
  2. 5.5 リソースに名前を付ける (p.121)
  3. 8.9 URIの設計 (p.242)

クエリパラメータ(クエリ変数)への言及は次の通り:

  1. クエリ変数を使用して、アルゴリズムへの入力を示す。(p.122)
  2. クエリ変数は、アルゴリズムリソースを指定するのに最適である。(p.126)
  3. クエリ変数の部分のみが異なる2つのURIは、同じ基本アルゴリズムに対する異なる入力セットを意味する。(p.242)

「クエリパラメータを使うな」とは言ってません。URL(URI)がProcessorリソース(「そろそろ決着、HTTPメソッド、URL、そして標準化された動詞」参照)を表すとき、計算処理に名前付き引数を渡すにはクエリパラメータが適切な方法です。もちろん、パラメータの不適切な用例もありますが、なんだって変な使い方をする人はいるのです。「使うな」と「正しく使え」は全然別な言明です。

さて、『RESTful Webサービス』内で拡張子への言及を探してみましたが、見当たりませんでした。単なるファイルであるリソースにおいては、常識的に拡張子を使っています。p.87に http://www.example.com/software/release/1.0.3.tar.gz という例が推奨されるURIとして出ています。しかし、ほとんどの例では拡張子を使ってないので、拡張子を推奨はしてないようです。

同書p.124の例では、見た目は拡張子に見える部分に数値パラメータ(地図の縮尺)を入れています、/satelite.1/Earth, /satelite.5/Earth のように。とすると、著者たちは「拡張子はファイルタイプは表すべき」とも考えてないようです。結局、書籍『RETful Webサービス』では、「拡張子を使うな」とは言ってませんが、積極的な活用もしていません。

なぜ、拡張子やクエリパラメータは嫌われてしまったのか

というわけで、RESTの原則に「拡張子やパラメータを使うな」は含まれていないし、ましてや「拡張子やパラメータを使わなければRESTfulになる/クールになる」なんて命題が成立する余地はありません。

にも関わらず、「拡張子やクエリパラメータはダメだ」という感覚や雰囲気が共有されているのは、実際にダメな例があるからでしょう。例えば:

  • /blogs.jsp?id=hiyama&date=20100120

これはダメダメです。

  • 拡張子.jspは、使用されているテクノロジを示すものです。リソースの本性には何の関係もなく、誰も知りたいとは思わない情報です。「URIは記述的であるべき」(『RESTful Webサービス』p.87)に反します。
  • クエリパラメータidとdateの値は、リソースを一意に識別する情報であり、計算処理への名前付き引数ではありません。これはクエリパラメータの誤用です。

この例は批判されて当然な理由があります。その理由は「拡張子やクエリパラメータを使っているから」ではありません。「拡張子やクエリパラメータを誤用しているから」です。ですから、「拡張子やパラメータを使わなければいいんだな」なんて教訓を引き出すなら、それは短絡的なトンチンカンとしか言いようがありません。

恐るべきクールなURI

「拡張子やパラメータはダメなんだ」「拡張子やパラメータを使わなければいいんだ」と信じている人は相当数いるようです。例えば、次のクエリパラメータ付きURIを考えます。

これを次のように直したらクールだと本気で思っている人がいるようなのです。

この“クール”なURIは、次のようなマッピング機構で実現されているんだとか(フィクションではありません!)。

  • クラスArticlesが /articles にマップされる。
  • メソッドsearchが /search にマップされる。

ここまでなら、まー理解できなくもないのですが、

悪い冗談としか思えません。

ひょっとして、「パラメータを消去したら、RESTfulになる」と思ったのかも知れませんが、実際には『RESTful Webサービス』に載っている「やってはいけないこと」をやらかしています。

  • 存在しない階層の暗示を避ける (『RESTful Webサービス』 p.122)

どういうことかと言うと、words/Web%20フレームワーク/lang/ja というパスは、"words"の下に従属リソースとして"Web%20フレームワーク"があり、その下に"lang"があり、階層末端のリソースとして"ja"がある、と解釈されるということです。実際にはそんな階層構造がないので、存在しない階層を暗示してしまっています。妄信的教条主義の残念な結末。

検索にはパラメータ、検索結果にはリソースID

検索を表すURL /articles/search は、サーバー側でアルゴリズムによる処理が走ります。アルゴリズムに対する入力をパラメータで渡すのは何ら責められることではありません。ただし、毎回アルゴリズムが走るのを節約するために、キャッシュするのはいいことだと思います。

例えば、/articles/search?words=Web+フレームワーク&lang=ja の結果が /articles/topics/ja/Web%20フレームワーク にキャッシュされるとしましょう。ユーザーは、/articles/topics/ja/Web%20フレームワーク をブックマークすることができます。次のアクセスからは、いちいち検索処理が走ることはありません。検索プログラム自体も、キャッシュを調べてヒットすれば、キャッシュへのリダイレクトを送れば済みます。

拡張子がないことがメリットなのか

次は拡張子の話。.jsp や .php が好ましくないのは分かりました。ではお聞きしますが、.html や .txt のような拡張子をすべて排除した方がいいと本気で思いますか? あるいは、ディレクトリのリストを取った(dirやlsコマンドを実行した)とき、拡張子がないファイルがズラーと並んでいたらどうでしょうか? 僕ならイヤーな気分がします。「なんじゃ、こりゃ」って怒り出すかも。

リソースIDの場合なら、拡張子のない名前は次のような感じでしょう。

  • /hiyama/OnCartesianCategories00
  • /hiyama/OnCartesianCategories
  • /hiyama/MyPet
  • /hiyama/20101020

これは拡張子がないから“クール”? それでは、次のような“ダサイ”拡張子付きリソースIDならどうでしょう。

  • /hiyama/OnCartesianCategories00.txt
  • /hiyama/OnCartesianCategories.pdf
  • /hiyama/MyPet.jpg
  • /hiyama/2010-01-20.blog-entry

拡張子付きリソースIDを見れば、次のような想像が(事実はともかくとして)できるでしょう。

  1. OnCartesianCategories00 はちょっとしたメモで、OnCartesianCategories はちゃんと仕上げた文書だろう。
  2. MyPetはhiyama(檜山)のペットの写真かな。
  3. 日付が2010年1月20日のブログを書いているな。

拡張子は、物理的なファイルフォーマット(Content-Type)だけではなく、リソースに関する意味情報/メタ情報を示唆してくれることがあるんです。“クール”なURIではそういう付加情報が抜け落ちています。えっ? 付加情報もクールに付けられるって:

  • /hiyama/OnCartesianCategories-draft_text_memo
  • /hiyama/OnCartesianCategories_finalVersion_in_pdf
  • /hiyama/MyPet_photo_as_jpeg
  • /hiyama/BlogEntry-date2010-01-20

もう好きにしてください。

拡張子が邪魔になるとき

拡張子がなくても不便ではないし、むしろないほうがスッキリする例も確かにあります。次のような例を考えてみます。

/users/ にユーザーのプロファイル情報が集められているとして、特定個人のプロファイルは、/users/hiyama.xml としてアクセスできるとしましょう。また、ユーザーのブログは /hiyama/blog/ にあり、ブログエントリーは /hiyama/blog/2010-01-20.html のようなURLとします。

この例は、困った問題があるわけではありませんが、拡張子 .xml, .html はアラズモガナな気がします。/users/hiyama, /hiyama/blog/2010-01-20 で困ることはないし、簡略なぶん読み書きにも便利です。感覚的にも、拡張子がないほうが確かにクール。

拡張子が、意味情報/メタ情報を伝える効果を持ち助かると感じるときがあります。ところが、無意味で邪魔に感じるときもあるのです。これはいったいどういうことでしょう? 背後にある事情を考えてみる必要があります。

リソースタイプとコレクション

直前に提示した疑問を解くために、リソースタイプという概念を導入します。リソースタイプは、URLで表されるリソースがいかなるものであるか、という素性<すじょう>や肩書きです。リソースタイプにより、リソースの振る舞いや用途が決まります。

次のルールが守られている、あるいは守られるべきという仮定でこれから話を進めます。

  • リソースタイプは、人間にもプログラムにも分かりやすい形でURLのなかにエンコードすべし。

リソースタイプをリソースID内にエンコードする手段のひとつが拡張子です。拡張子はもちろん、ファイルのタイプをファイル名にエンコードする目的で古くから広く使われている方法です。しかし、ファイルシステム内でも拡張子が必要とされない状況があります。例えば、Linux/UNIXにおける実行可能ファイルに通常は拡張子を付けませんが、/bin/ とか /usr/bin/ などに実行可能ファイルが集められているので不便はありません。UNIXマニュアル(man)を構成するファイルの拡張子は章番号でファイルタイプとは無関係ですが、/usr/share/man/ 以下にまとめられているのでこれも問題にはなりません。

先に出した /users/ と /hiyama/blog/ の例でも、このディレクトリ(フォルダー)より下に配置されるリソースは、それぞれ個人のプロファイル、ブログエントリーと決まっていました。つまり、ディレクトリの側で、その下にあるリソースのタイプを規定していたのです。

URLにおけるディレクトリとは、リソースのコレクションを表すリソースでした。リソースタイプはコレクションと深い関係にあり、その関係がリソースタイプのエンコードに影響を与えていることが予測されます。

均質コレクションと不均質コレクション

伝統的に、/bin/, /user/bin/ には実行可能ファイルしか入れません。/user/share/man/ にはマニュアルファイルしか入れません。先に出した例では、/users/ には個人プロファイルしか入れないと仮定しました。/hiyama/blog/ 以下にはブログエントリーしか置きません。

このように、特定のリソースタイプのリソースを集めたコレクションを均質コレクション(homogeneous collection)と呼ぶことにします。一方、従属リソース(子のリソース)のタイプがバラバラで、様々なタイプが混じっているとき不均質コレクション(heterogeneous collection)と呼びます。最初に出した /hiyama/ というディレクトリは不均質コレクションの例です。

均質コレクションでは、親のリソース側に子のリソースのタイプ情報が含まれるので、子のリソース(従属リソース、アイテム)にタイプ情報は不要です。したがって、均質コレクションの従属リソースの拡張子は冗長な印象を与えます。一方、不均質コレクションでは、個々の従属リソースの名前にタイプ情報を埋めこまないとタイプが不明になるので、拡張子を使うことになるわけです。

Catyの設計判断

初期のCatyでは、リソースが配置されるディレクトリと拡張子の両方からリソースタイプを判断する方法を採っていました。しかし、「なんか面倒だし複雑だし」という理由で、今は拡張子だけにしています。このため、均質コレクションの従属リソースにも拡張子が必要となります。

/users/hiyama, /hiyama/blog/2010-01-20 ではなくて、/users/hiyama.xml, /hiyama/blog/2010-01-20.html とするのです。物理フォーマットがモロに出てるのが嫌なら、/users/hiyama.about, /hiyama/blog/2010-01-20.entry とするのは別にかまいません(というか推奨します)が、URLが冗長になることを避けられません。

僕は、タイプを明示する冗長性にさして実害はなく、むしろ事情がクリアになるときもあるし、拡張子一元論により得られる単純さと分かりやすさのメリットのほうがずっと大きいと判断しました -- これが、Catyで拡張子をヘビーに使うことに対するラショネールです。

*1:混じってしまったので、揃えるのが面倒。それに対する言い訳だったりもします。