トラバーサルの空騒ぎ

(あるいは、なぜあなたはそれに関心を持たなければならないか)

Note

この章は、元々は Rob Miller による ブログポストとして http://blog.nonsequitarian.org/2010/much-ado-about-traversal/ で公開 されていたもので、許可を得て追加されました。

トラバーサルは URL ディスパッチの代替手段で、 Pyramid アプリケーションが URL をコードに写像する際に用いられます。

Note

すでにトラバーサルとビュー検索の概念に精通している元 Zope ユーザは Traversal 章に直接スキップすると良いでしょう。 そこでは技術的詳細について議論します。本章は、主に元 Pylons 経験者や、トラバーサルを提供しない他のフレームワークの経験者で、 トラバーサルの「なぜ」に関するイントロダクションを必要とする読者を 対象としています。

長年にわたり Pylons およびその Routes ベースの URL マッチングを使用してきた 人々は、やって来た HTTP リクエストを callable コードにルーティングする方法 として “traversal” や “view lookup” のような新しい考えに、 Pyramid によって初めて出会います。彼らの一部は、トラバーサルは 理解するのが難しいと感じます。他の人々はその有用性について尋ねます; URL マッチングは、これまで問題なく動いていました。なぜ、彼らの脳に適合しない、 即座に明白な価値を提供しないような、別のアプローチを扱うことを考慮 しなければならないのでしょうか。

トラバーサルを理解したくなければ、その必要はないことを保証します。 URL dispatch だけを用いた Pyramid アプリケーションを 好きなだけ構築することができます。しかし、パターンマッチングメカニズム よりもトラバーサルに基づくアプローチの方がはるかに簡単に扱うことのできる、 いくつかの直裁的 (straightforward) な現実世界でのユースケースがあります。 あなた自身がまだこれらのユースケースに出会ったことがなくても、いつそれを 使うのかを知るために、これらの新しい考え方を理解することはあらゆるウェブ 開発者にとって努力の価値があります。 Traversal は、実際にはフォルダ とファイルを持つありふれたファイルシステムを使用した人なら誰でも簡単に理解 できる直裁的なメタファーです。

URL ディスパッチ

一歩下がって、解決しようとしている問題について考察してみましょう。 特定のパスに対する HTTP リクエストがウェブアプリケーションにルーティング されています。リクエストされたパスは、おそらくアプリのどこかで定義された 特定の view callable 関数を起動するでしょう。私たちは、与えられた リクエスト URL に対して、もし存在するなら どの callable 関数を起動 すべきかを決定しようとしています。

Pyramid を含む多くのシステムは単純な解決策を提供しています。それらは、 「URL マッチング」の概念を提供します。 URL マッチングは、URL パスを 解析して、その結果を正規表現あるいは他の何らかの URL パス・テンプレート 構文によって定義された複数の登録済みの「パターン」と比較することによって この問題に取り組みます。それぞれのパターンは、どこかにある callable 関数 に写像されます;リクエストパスが特定のパターンとマッチした場合、関連する 関数が呼ばれます。リクエストパスが複数のパターンとマッチする場合、 何らかの競合解消スキームが使用されます。通常これは単純な先着順で、 その結果最初のマッチは後の任意のマッチより高い優先度を持つでしょう。 リクエストパスが定義されたパターンのうちのどれともマッチしない場合、 “404 Not Found” レスポンスが返されます。

Pyramid は URL dispatch と呼ばれる URL マッチングの実装を提供 しています。 Pyramid シンタックスを用いて /{userid}/photos/{photoid} のようなマッチパターンがあり、コードの どこかに定義された photo_view() 関数に写像されるとします。このとき、 /joeschmoe/photos/photo1 のようなパスに対するリクエストがマッチして、 photo_view() 関数がそのリクエストを扱うために起動されます。同様に、 /{userid}/blog/{year}/{month}/{postid}blog_post_view() 関数に写像するとします。すると、 /joeschmoe/blog/2010/12/urlmatching はその関数を起動します。この関数は、おそらく urlmatching ブログポストを 見つけてレンダリングする方法を知っているでしょう。

歴史の復習

いま URL dispatch についての理解を復習したので、トラバーサル に関するアイデアを深追いします。しかし、その前に思い出をたどる旅に 出かけましょう。あなたが長い間ウェブの仕事をしているなら、 PylonsPyramid のような高機能な (fancy) ウェブ フレームワークがなかった時代を思い出せるかもしれません。代わりに、 主としてファイルシステムからファイルを返す多目的 HTTP サーバーがありました。 あるサイトの “root” は、ファイルシステムのどこかにある特定のフォルダに 写像されます。リクエスト URL パスのセグメントは、それぞれサブディレクトリ を表わします。最後のパスセグメントは、ディレクトリかファイルのいずれか になります。そして、適切なファイルを見つけたら、サーバはそれを HTTP レスポンスで包んでクライアントに送ります。したがって、 /joeschmoe/photos/photo1 に対するリクエストに serve up することは、 文字通りどこかに joeschmoe フォルダがあり、それは photos フォルダ を含み、そしてそれは photo1 ファイルを含んでいることを意味しました。 それを見つける途中のどの時点でも、フォルダまたはファイルがリクエスト されたパスとマッチしない場合、 404 レスポンスを返します。

しかし、ウェブがより動的になるとともに、少しだけ余分な複雑さが追加され ました。 CGI や HTTP サーバモジュールのような技術が開発されました。 ファイルはまだファイルシステム上で検索されましたが、ファイルが (例えば) .cgi.php で終わっている場合、あるいは単にファイルが特定の フォルダの中にある場合、クライアントにファイルを送る代わりに、サーバは ファイルを読み込み、ある種類のインタープリタを使用してそれを実行し、その後 最終結果としてこのプロセスからの出力をクライアントに送ります。サーバ 設定には、どのファイルが動的なコードを起動するか、そしてデフォルトケース では静的ファイルを返すことが指定されました。

トラバーサル (別名リソース location)

信じられないかもしれませんが、ファイルシステムからファイルを返す仕組みが どのように働くかを理解すれば、トラバーサルを理解したことになります。 また、与えられたリクエストによって指定されるファイルの種類に基づいて サーバが異なる動作をすることを理解すれば、ビュー検索を理解したことになります。

ファイルシステム検索とトラバーサルの主な違いは、ファイルシステム検索は ファイルシステムツリーの入れ子のディレクトリとファイルを検索するのに対して、 トラバーサルは resource tree の中の入れ子の辞書形式のオブジェクトを 検索することです。例のパスのうちの1つに注目してみましょう。そうすれば、 私が何を言いたいか分かるでしょう:

パス /joeschmoe/photos/photo1 には、 /, joeschmoe, photos, photo1 という4つのセグメントがあります。 ファイルシステム検索では、ルートフォルダ (/) が入れ子のフォルダ (joeschmoe) を含み、それが別の入れ子のフォルダ (photos) を含み、 それが最後に JPG ファイル (photo1) を含んでいるでしょう。 トラバーサルでは、その代りに、辞書形式の root オブジェクトを持っています。 joeschmoe キーを求めることは別の辞書形式のオブジェクトを与えます。 次にこのオブジェクトに photos キーを求めることは別の写像オブジェク トを与えます。それは最終的に (おそらく) photo1 キーによって参照される 値の中に探しているリソースを含んでいます。

pure Python の用語では、 /joeschmoe/photos/photo1 に対するリクエストを 満たすトラバーサルあるいは「リソース location」部分は、この擬似コードの ように見えるでしょう:

get_root()['joeschmoe']['photos']['photo1']

get_root() は root トラバーサル resource を返す何らかの関数 です。指定されたキーがすべて存在すれば、返されたオブジェクトはファイル システムの例において検索された JPG ファイルと類似して、リクエストされて いるリソースになるでしょう。途中のどこかの場所で KeyError が発生 した場合 Pyramid は 404 を返します。 (これは正確には真実ではあり ません。そのことは後でビュー検索に関して学んだときに分かるでしょう。 しかし基本概念は押さえています。)

「リソース」とは何か?

「ファイルシステム上のファイルなら理解している」とあなたは言うかもしれません。 「でも、この入れ子の辞書とは何? これらのオブジェクト、これらの『リソース』は どこにある? それは一体 なの?」

Pyramid はあまり主張の強いフレームワークではないので、 resource がどのように実装されるかに対する制限を設けません; 開発者は望むようにそれを実装することができます。使用される1つの一般的な パターンは、 root を含むリソースのすべてをグラフとしてデータベース中に永続化 することです。 root オブジェクトは辞書風オブジェクトです。 Python において 辞書風オブジェクトは、キー検索が行われるときに呼ばれる __getitem__ メソッドを提供します。内部では、 adict が辞書風オブジェクトであるときに、 Python は adict['a']adict.__getitem__('a') に翻訳します。 信じてもらえなければ、 Python インタープリタプロンプトの中でこれを してみてください:

1
2
3
4
5
6
7
8
9
Python 2.4.6 (#2, Apr 29 2010, 00:31:48)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> adict = {}
>>> adict['a'] = 1
>>> adict['a']
1
>>> adict.__getitem__('a')
1

辞書風の root オブジェクトは、そのサブリソースのすべての id をキーとして 格納し、それを取得する __getitem__ 実装を提供します。したがって、 get_root() はユニークな root オブジェクトを取得します。その一方で get_root()['joeschmoe'] は、データベースなどに格納されている異なる オブジェクトを返します。そのオブジェクトは、それ自身のサブリソースと __getitem__ 実装を持ちます。これらのリソースは、リレーショナル データベースや、最近ポピュラーになっている多くの “NoSQL” ソリューションの うちの1つ、あるいはそれ以外に永続化されるかもしれませんが、それは重要では ありません。返されたオブジェクトが辞書風 API を提供する限り (つまり、 それらに __getitem__ メソッドが適切に実装されている限り) トラバーサル は動作します。

実際、「データベース」は全く必要ありません。プレーンな辞書とともに Python ソース内に直接ハードコーディングされたサイトの URL 構造を 使用することができます。あるいは、特定のディレクトリの中でファイルを 検索して、 URL パスをファイルシステム上のフォルダ構造に直接写像する 従来のメカニズムを正確に模倣する __getitem__ メソッドを持つ オブジェクトを自明に実装することができます。トラバーサルは 実際のところファイルシステム検索のスーパーセットです。

Note

より技術的なリソースの概観については、 リソース の章を参照してください。

ビュー検索

この時点で、ほとんどそこまで来ています。これまでにトラバーサルをカバーしました。 それは特定のリソースが特定の URL パスによって検索されるプロセスです。 しかし「ビュー検索」とは何でしょうか。

ビュー検索の必要性は単純です: resource を見つけた後で、取りうる 可能なアクションが複数あります。これまでの写真の例で、例えば、ページの 中で写真を見たいと思うかもしれません。しかし、さらにユーザが写真や関連 する任意のメタデータを編集する方法も提供したいと思うかもしれません。 前者を view ビューと呼ぶことにして、後者を edit ビューと 呼ぶことにします。 (独創的ですね。) Pyramid には中央集権的な ビュー application registry があり、名前付きのビューを特定の リソースタイプに関連付けることができます。そこで、この例において photo オブジェクトに対する view ビューと edit ビューを登録して、 デフォルトとして view ビューを指定したと仮定しましょう。その結果、 /joeschmoe/photos/photo1/view/joeschmoe/photos/photo1 は 等価になります。 edit ビューは /joeschmoe/photos/photo1/edit に 対するリクエストによって sensibly 提供されるでしょう。

おそらく、 edit ビューの URL パスの最初の部分は非 edit バージョンと同じ リソース (特に get_root()['joeschmoe']['photos']['photo1'] によって 返されたリソース) になることは明らかです。しかし、トラバーサルはそこで 終了します; photo1 リソースには edit キーがありません。実際、 それは辞書風オブジェクトではないかもしれません。その場合には photo1['edit'] は無意味でしょう。 Pyramid リソース location が 末端の リソースに解決され、しかしリクエストパス全体がまだ消費され ていない場合、 直後の パスセグメントが view name として扱われます。 そして、与えられた名前のビューが与えられたタイプのリソースに対して指定 されているかどうかを確かめるためレジストリがチェックされます。もしそうなら、 渡されたリソースを関連する context オブジェクトとして (さらに request.context としても利用可能です) ビュー callable が起動されます。 ビュー callable が見つけられなければ、 Pyramid は “404 Not Found” レスポンスを返します。

/joeschmoe/photos/photo1/edit に対するリクエストは、最終的に次のような Python 風の擬似コードに変換されると概念化することができます:

context = get_root()['joeschmoe']['photos']['photo1']
view_callable = get_view(context, 'edit')
request.context = context
view_callable(request)

get_root 関数と get_view 関数は、実際には存在しません。内部的に、 Pyramid はより複雑なことをしています。しかし、上記の例は擬似コードに よるビュー検索アルゴリズムの合理的な近似です。

ユースケース

なぜトラバーサルに関心を持たなければならないのでしょうか。 URL マッチの 方が説明が簡単だし、それで十分ではないでしょうか?

いくつかの場合は yes です。しかし、もちろんすべての場合にではありません。 これまで、非常に構造化された URL を扱ってきました。パスはこのように、 少数の特定の部分を持っていました:

/{userid}/{typename}/{objectid}[/{view_name}]

これまでのすべての例において、どの名前 (“photos”, “blog” など) が 使われるか開発時に知っているという前提で、 typename 値をハードコーディング していました。しかし、もしこれらの名前がどうなるか知らなかったら、 どうしますか。あるいは、さらに悪いことに、ユーザのフォルダ内部の URL 構造について 何も 知らなかったら、どうしますか。私たちは CMS を書く こともあるでしょう。その場合、エンドユーザがフォルダ内部にコンテンツや 他のフォルダを任意に追加できることが望まれます。エンドユーザは、フォルダ を何層も深く入れ子にすることに決めるかもしれません。発展する可能性のある パスのあらゆる組み合わせを考慮できるマッチパターンを、どのように構築 するのでしょうか。

それは可能かもしれませんが、確実に容易ではないでしょう。すべてのエッジ ケースを扱おうとすると、マッチパターンは急速に複雑になるでしょう。

しかし、トラバーサルを使えば直裁的です。20 層のネストも問題になりません。 パスセグメントを使い果たすかリソースが KeyError を上げるまで、 Pyramid は適切に __getitem__ を必要な回数だけ呼び出すでしょう。 それぞれのリソースは、単にその直接の子を取得する方法を知っている必要が あります。トラバーサルアルゴリズムが残りの面倒を見ます。さらに、リソース木 の構造はコード中ではなくデータベースに入れることができるので、ユーザ自身に 個人化された「ディレクトリ」構造を構築するために、実行時にユーザに リソース木を修正させることも簡単にできます。

トラバーサルが活躍する別のユースケースは、コンテキスト依存のセキュリティ ポリシーをサポートする必要があるときです。一例を挙げると、大企業のため のドキュメント管理インフラです。そこでは、異なる部門のメンバーは他の様々 な部門のファイルに対して異なるアクセスレベルを持ちます。当然、特定の ファイルは特定の個人だけが利用可能である必要もあります。リソースが実際に ドキュメントと関連付けられたデータオブジェクトを表現している場合、 トラバーサルはここでうまく働きます。なぜなら、コード解決と呼び出しプロセスに、 リソース認可という考え方が埋め込まれているからです。リソースオブジェクトは ACL を格納することができます。それはサブリソースによって継承や オーバーライドをすることができます。

もしそれぞれのリソースがこのようにコンテキストベースの ACL を生成できるなら、 ビューコードが機密なアクションを実行しようとする場合は常に、カレント ユーザーがそのアクションを行なうことが認められるかどうかを確かめるために、 ACL に対してチェックすることができます。このようにして、従来の表形式 アプローチを使用してモデル化することが極めて難しい、いわゆる 「インスタンスベース」あるいは「列レベル」のセキュリティを達成します。 Pyramid はそのようなスキームを積極的にサポートします。そして実際、 ガードパーミッションとともにビューを登録して、認可ポリシーを使用すれば、 Pyramid はビューそれ自身がカレントユーザーに対して利用可能かどうかを 決定する際にリソースの ACL に対してチェックすることができます。

要約すると、 URL dispatch を使うよりもトラバーサルとビュー検索 を使った方が容易に扱える問題の大きなクラスがあります。あなたの問題が それを要求しない場合、素晴らしい: ずっと URL dispatch を 使い続けてください。しかし、 Pyramid を使用していて、いつかこれらの ユースケースのうちのいずれかをサポートする 必要がある と分かれば、 ツールキットにトラバーサルを持っていることに感謝することでしょう。

Note

同じ Pyramid アプリケーションの中で traversalURL dispatch を組み合わせることは可能です。詳細については、 Combining Traversal and URL Dispatch 章を参照してください。