4. オブジェクト パブリッシング

4.1. はじめに

Zope はオブジェクトをWeb上に配置します。これは オブジェクト パブリッシング と呼ばれています。 Zope の独特な特徴として シンプルなURLへのアクセスでオブジェクトのメソッドを呼び出したり、 オブジェクトの関連を辿ったりする仕組みがあります。 Zope は HTTP の他に、 FTP, WebDAV, XML-RPC などのネットワークプロトコル でオブジェクトを扱うことが出来ます。

本章では、 Zope がどのようにしてオブジェクトを発行するのかを 学びます。また、 オブジェクトを Web で発行する (Web Publishing) ために必要となる事を学びます。

4.2. HTTP パブリッシング

Web ブラウザと Zope が通信するとき、ブラウザは HTTP リクエストを Zope Web サーバーに送信します。このリクエストが正しく受信されると、 リクエストは ‘ZPublisher’ という Zope のオブジェクトパブリッシング の仕組みによって処理されます。 ‘ZPublisher’ とは軽量 ORB (オブジェクト リクエストブローカー) の一種です。 これによってリクエストが扱うべきオブジェクトの位置が特定されます。 パブリッシャーは要求されたURLでオブジェクトの位置に照らし合わせます。 リクエストからオブジェクトを見つけ出すことを 探索(Traversal) と言い、パブリッシャーはオブジェクトからオブジェクトを辿って 目的のオブジェクトを見つけます。 発行対象となるオブジェクトを見つけたら、パブリッシャーは対象オブジェクト のメソッドを呼び出し、その際に必要なパラメータを渡します。 パブリッシャーはメソッドを呼び出す際のパラメータ等の情報をリクエストから 取り出して渡します。リクエストからパラメータを取り出して分解する事を 引数マーシャリング と言います。 発行オブジェクトがレスポンスを返した場合、レスポンスは Zope Web サーバー に返されます。 Web サーバーはレスポンスを呼出元の Web ブラウザーに 渡し返します。

発行の手順の概要を [2-1] のようにまとめました。

../_images/2-1.png

2.1 オブジェクト パブリッシング

主に、発行オブジェクトは永続オブジェクトで、発行モジュールが ZODB からロードします。 ZODB について詳しくは4章を参照してください。

本章ではオブジェクトパブリッシングの各ステップについて詳しく見て 行きます。オブジェクトパブリッシングの主要なステップを要約すると 以下のようになります。

  1. クライアントがパブリッシャーにリクエストを送る
  2. パブリッシャーは発行オブジェクトの位置をURLから特定
  3. パブリッシャーが発行オブジェクトをリクエスト時の引数付きで 呼び出す
  4. パブリッシャーが結果を解釈し、クライアントに結果を返送

本章では、技術の詳細、特別なケース、追加のステップなど、上記の一覧 以上の点についても触れていきます。

4.3. URL 探索

探索は、パブリッシャーが発行オブジェクトの位置を特定する ための処理です。主に、パブリッシャーは URL に従って発行オブジェクト を辿って位置を特定します。以下の例を見てみましょう:

class Classification:
    ...

class Animal:
    ...

    def screech(self, ...):
        ...

vertebrates=Classification(...)
vertebrates.mammals=Classification(...)
vertebrates.reptiles=Classification(...)
vertebrates.mammals.monkey=Animal(...)
vertebrates.mammals.dog=Animal(...)
vertebrates.reptiles.lizard=Animal(...)

このオブジェクトの集合はオブジェクトの階層構造を形成しています。 Zope を使って URL によるオブジェクトの発行が出来ます。例えば URL が ‘http://zope/vertebrates/mammals/monkey/screech‘ の場合、オブジェクト 階層の探索によって ‘monkey’ オブジェクトを見つけ、 ‘screech’ メソッドを呼び出します。

../_images/2-2.png

2.2 オブジェクト階層の探索

パブリッシャーはルートオブジェクトを起点として、 URL をキーとして オブジェクトを次々と辿っていきます。

主に次のオブジェクトは現在のオブジェクトのサブオブジェクトで、パス名 の名前が付けられています。前述の例では、パブリッシャーは ‘vertebrates’ オブジェクトを取得し、次のパス名が “mammals” なので、パブリッシャーは この名前のサブオブジェクトが現在のオブジェクトに無いかを探します。 探索のステップはURLの末端まで辿ったところで終わりになります。 最終的なオブジェクトが見つかればそのオブジェクトが発行されます。 見つからなければエラーが返されます。

それでは、もっと厳密に探索を見ていきましょう。

4.4. 探索インターフェース

Zope は発行可能なオブジェクトとモジュールという意味を持つインターフェース を定義しています。

Zopeのための開発をしているとき、たいていの場合において ‘Zope’ パッケージ を発行モジュールに使用するでしょう。もし ‘ZPublisher’ をZope 以外で 使おうとするときには、発行モジュールのインターフェースに興味をもつ と思います。

4.5. 発行可能なオブジェクトの要件

Zope は発行可能なオブジェクトについて2,3の制限を持っています。 基本ルールとして、そのオブジェクトには doc string が必須です。 このはメソッドオブジェクトであっても同様です。

他の要件として、発行可能なオブジェクトの名前はアンダースコアで 始まっていてはいけません。これらの2つの制限は、発行においてオブジェクト のプライベート状態を維持するための方式です。

最後に、発行オブジェクトはPythonモジュールオブジェクトにはなれません。

4.6. 探索メソッド

探索を行うとき、 ‘ZPublisher’ はURLをスラッシュで分割して、パスエレメント という単位で現在のオブジェクトから次のオブジェクトへと探索していきます。 ‘ZPublisher’ は次のオブジェクトを見つける方法として以下の3つの方法を 使います:

  1. ‘__bobo_traverse__’ を使う
  2. ‘getattr’ を使う
  3. 辞書アクセスを使う

最初に、パブリッシャーは ‘__bobo_traverse__’ という探索のための フックメソッド呼び出しを試みます。もし現在のオブジェクトがこのメソッド を持っていれば、 request と現在のパスエレメントを引数として呼び出します。 メソッドは次のオブジェクトを返すか、次のオブジェクトが見つからない事を 表す ‘None’ を返します。 ‘__bobo_traverse__’ は次のオブジェクトとして 複数のオブジェクトをタプル型で返すことも出来ます。これによって、 request 内に追加の親オブジェクトを設定することが出来ますが、たいていの場合 において追加の親を設定する必要はありません。

以下の例は ‘__bobo_traverse__’ を使う例です:

def __bobo_traverse__(self, request, key):
    # もしここで特別なcookie値があれば、それに見合ったオブジェクト
    # を返しますが、そうでない場合は通常のオブジェクトを返します。

    if request.cookies.has_key('special'):
        # 特別な辞書からオブジェクトを返します
        return self.special_subobjects.get(key, None)

    # そうでなければ、通常の辞書からオブジェクトを返します
    return self.normal_subobjects.get(key, None)

この例は、探索処理中に request の内容によって処理を変えられることを 表しています。

もし、現在のオブジェクトが ‘__bobo_traverse__’ メソッドを定義して いなければ、次の方法として ‘getattr’ で次のオブジェクトを探します。 オブジェクトの属性を辿るのは普通に Python 的です。

もし次のオブジェクトが ‘getattr’ で見つからなかった場合、 ‘ZPublisher’ は現在のオブジェクトに辞書アクセスを試みます。注意: パスエレメントは 数字ではなく文字列なので、 URL 中に数字を使用しても配列へのアクセスには なりません。

例えば現在のオブジェクトが ‘a’ だとして、次のパスエレメントが ‘next’ だとします。ここで ‘ZPublisher’ は以下の3つの方法で次のオブジェクト を見つけようとします:

  1. ‘a.__bobo_traverse__(“next”)’
  2. ‘a.next’
  3. ‘a[“next”]’

4.7. パブリッシング メソッド

探索によって発行可能なオブジェクトが特定されると、 Zope は以下の3つの 方法から可能な方法でオブジェクトを発行します。

  • 発行可能なオブジェクトの呼び出し – もし、発行可能なオブジェクトが 関数・メソッド・呼び出し可能オブジェクト、の何れかであれば、パブリッシャー は呼び出しを行います。この章の後の方で、パブリッシャーが呼び出し時に 引数をどのようにして渡すかを説明します。
  • デフォルトメソッドの呼び出し – もし発行可能なオブジェクトが呼び出し 可能ではない場合、パブリッシャーはデフォルトメソッドを呼び出します。 HTTP の ‘GET’ と ‘POST’ の request の場合、デフォルトメソッドは ‘index_html’ です。他の HTTP request 、例えば ‘PUT’ の場合などは、 パブリッシャーはそのメソッド名のメソッドを探して呼び出し、 ‘HEAD’ request の場合には発行可能なオブジェクトの ‘HEAD’ メソッドを 呼び出すでしょう。
  • 発行可能なオブジェクトの文字列への変換 – もし発行可能なオブジェクト が呼び出し可能でなく、デフォルトメソッドもオmって以内場合、 パブリッシャーは Python の ‘str’ 関数を使ってオブジェクトを 文字列に変換します。

呼び出されるメソッドが確定して呼び出された後、パブリッシャーは返値 を解釈する必要があります。

4.8. レスポンスの文字エンコーディング

もし、発行可能なオブジェクトが ‘string’ 型のオブジェクトか 8-bit 文字列 を返してきた場合、パブリッシャーはこれをそのままレスポンスの本文に 使用するでしょう。

あるいは発行メソッドが Unicode 文字列を返してきた場合、パブリッシャー は何らかの文字エンコーディングを適用します。発行メソッドはどの 文字エンコーディングを使用するかを ‘Content-Type’ レスポンスヘッダー の ‘charset’ 属性で指定することが出来ます (レスポンスヘッダーの設定方法についてはこの章の後の方で説明します)。 一般的には文字エンコーディングとして UTF-8 を選択します。 パブリッシャーが Unicode の返値を UTF-8 に変換するように指定するには、 ‘Content-Type’ ヘッダーに ‘text/html; charset=UTF-8’ という値を 設定してください。

もし ‘Content-Type’ ヘッダーに charset 属性が含まれていない (または発行メソッドでヘッダーが設定されなかった場合) には、パブリッシャー はデフォルトの文字エンコーディングを使用します。現在のところ、この デフォルトの文字エンコーディングは、 Unicode サポートの無い時代の古い Zope との互換性のため ISO-8859-1 (Latin-1) となっています。 デフォルトはそのうち UTF-8 などに変更されるでしょう。

通常であれば、発行メソッドは HTTP レスポンスの本文となることを想定した 文字列を返します。レスポンスヘッダーはレスポンスを返すオブジェクトの メソッド呼び出しの中で操作されます (この操作については本章の後の方で 説明します) 。他の返値として、発行メソッドはタイトルと本文となる 文字列のタプルを返すことも出来ます。この場合、パブリッシャーは HTML ページを生成し、タプルの先頭を HTML の ‘title’ タグに設定し、次のタプル アイテムを HTML の ‘body’ タグに設定します。 例として、以下のようなレスポンスを返すとします:

('response', 'the response')

これが以下の HTML ページに変換されます:

<html>
<head><title>response</title></head>
<body>the response</body>
</html>

4.9. ベース HREF の制御

オブジェクトがメソッド間を行き来できるような相対リンクを含む HTML を 返したとします。以下の例を見てください:

class Example:
    "example"

    def one(self):
        "method one"
        return """<html>
                  <head>
                  <title>one</title>
                  </head>
                  <body>
                  <a href="two">two</a>
                  </body>
                  </html>"""

    def two(self):
        "method two"
        return """<html>
                  <head>
                  <title>two</title>
                  </head>
                  <body>
                  <a href="one">one</a>
                  </body>
                  </html>"""

ここで、デフォルトメソッド ‘index_html’ が問題となります。 ‘index_html’ は URL に含んでいなくても呼び出されるメソッドですが、このとき ‘index_html’ が相対リンクを含むページを生成した場合、この相対リンクは意図したリンクに なりません。例えば:

class Example:
    "example"

     def index_html(self):
        return """<html>
                  <head>
                  <title>one</title>
                  </head>
                  <body>
                  <a href="one">one</a><br>
                  <a href="two">two</a>
                  </body>
                  </html>"""
     ...

‘Example’ クラスのインスタンスを ‘http://zope/example‘ という URL で発行 した場合、 ‘one’ メソッドへの相対リンクは ‘http://zope/example/one‘ と なって欲しいところですが、 ‘http://zope/one‘ という意図しないリンクに なってしまいます。

Zope はこの問題を解決するために、 ‘index_html’ メソッドがデフォルトメソッド として呼び出された場合に、 ‘base’ タグを ‘head’ タグ内に挿入します。 たいていの場合、このことに気づくことはないと思いますが、この不思議な ‘base’ タグが HTML 出力煮含まれる理由については知っておいてください。 この自動設定を行わないようにするためには、手動で ‘index_html’ メソッド の出力に ‘base’ タグを入れておく方法があります。

4.9.1. レスポンスヘッダー

パブリッシャーと Web サーバーは ‘Content-Length’ や ‘Content-Type’ などのレスポンスヘッダーを設定します。本章の後の方でこれらのヘッダー の設定方法を説明します。また、どのような例外でどんな HTTP レスポンス コードが設定されるのかも説明します。

4.9.2. 探索前フック

探索前フックによって、探索処理が行われる前にオブジェクトに特別な操作を 行うことが出来ます。これは request の内容を変更するなどの使い方が出来ます。 このような機能の例として、認証制御や、バーチャルホスティングサポート などがあります。

もしオブジェクトに ‘__before_publishing_traverse__’ メソッドがあれば、 パブリッシャーは探索処理の前に、このメソッドを現在のオブジェクトと リクエストを引数として呼び出します。

‘ZPublisher.BeforeTraverse’ モジュールは、探索前コールバック登録のための ヘルプ関数を多く持っています。これを使うことで、オブジェクトが探索処理 されようとしているときに、複数のオブジェクトに対する複雑なコールバックの 処理を行うことが出来るようになります。

4.9.3. 探索と獲得

獲得は探索にいくつかの影響を及ぼします。”獲得” については5章で詳しく 説明します。獲得が探索に及ぼす最も明確な影響は、パスから次のオブジェクト を取り出す際に発生します。これまで説明してきたように、探索処理において 次のオブジェクトの決定にしばしば ‘getattr’ が使用されますが、獲得は ‘getattr’ に影響するため、探索にも影響することになります。結果として、 暗黙の獲得が発生すると、探索の続きが獲得されたオブジェクトで行われる 事になります。例として、オブジェクト階層のルートが ‘fruit’_ である 階層構造があるとします:

from Acquisition import Implicit

class Node(Implicit):
    ...

fruit=Node()
fruit.apple=Node()
fruit.orange=Node()
fruit.apple.strawberry=Node()
fruit.orange.banana=Node()

これらのオブジェクトが発行されるときに獲得機能が働きます。例えば、 URL /fruit/apple/orange の探索処理を見てみましょう。パブリッシャー は ‘fruit’, ‘apple’ と辿って、次に獲得機能を使って ‘orange’ に到達 します。

獲得と探索が混在する処理は複雑な結果をもたらします。 URL が /fruit/apple/orange/strawberry/banana の場合、この URL はただしく オブジェクトにたどり着きますが、なぜ正しく動作するのかすぐには理解 出来ません。さらに納得しづらいけど正しい URL の例として /fruit/apple/orange/orange/apple/apple/banana などもあります。

一般的に、獲得の仕組みに沿った URL の構築を人間が行うことは、文脈 に沿った URL の構築に比べて限界があります。獲得によってオブジェクトや メソッドを発行するのは手軽ではありますが、コンテナの外からオブジェクト やメソッドを獲得して発行するのは、良いアイディアとは言えません。 例えば:

from Acquisition import Implicit

class Basket(Implicit):
    ...
    def numberOfItems(self):
        "Returns the number of contained items"
        ...

class Vegetable(Implicit):
    ...
    def texture(self):
        "Returns the texture of the vegetable."

class Fruit(Implicit):
    ...
    def color(self):
        "Returns the color of the fruit."

 basket=Basket()
 basket.apple=Fruit()
 basket.carrot=Vegetable()

URL /basket/apple/numberOfItems はコンテナに沿って獲得が働き、 ‘numberOfItems’ メソッドが発行されます (‘apple’ は ‘numberOfItems’ 属性を持っていないと言うのに!) 。また、 URL /basket/carrot/apple/texture も獲得が働き、コンテナからではなく ‘apple’ オブジェクトから ‘texture’ メソッドに辿り着きます。この区別はわかりにくく、 URL は可能な限り シンプルに保つようにするべきでしょう。獲得をシンプルに保ち、コンテナ に沿ってのみ行われるようにすることで、アプリケーションはより明瞭になり、 脆弱性は減少します。

探索中の獲得に関するの2つめの利用例は、 request に関するものです。 パブリッシャーは発行可能なオブジェクトから request オブジェクトを 取得する際に獲得を用います。これは最初のオブジェクトが獲得ラッパー にくるまれていて、 ‘REQUEST’ という名前へのアクセス時に request オブジェクトを獲得して返す仕組みによって行われています。 つまり、通常であれば以下のようにして発行可能なオブジェクトから request オブジェクトを取得できます:

request=self.REQUEST # for implicit acquirers

あるいは以下のようにします:

request=self.aq_acquire('REQUEST') # for explicit acquirers

もちろん、オブジェクトが獲得をサポートしていなければ、あるいは 探索したどこかのオブジェクトに ‘REQUEST’ 属性を見つけなければ、 この記述は機能しません。

最後に、獲得にはオブジェクトパブリッシングとは全く異なる役割があります。 次の節ではこの役割、セキュリティーついて説明します。

4.9.4. 探索とセキュリティー

パブリッシャーがオブジェクトからオブジェクトへの探索を進めているとき、 同時にセキュリティーチェックが行われます。現在のユーザーは探索パス のうちの全てのオブジェクトについてアクセス許可されている必要があります。 パブリッシャーはいくつかの方法でアクセスを制御します。 セキュリティーについて詳しくは 6章 “セキュリティー” を参照してください。

4.9.5. 基本的なパブリッシャーセキュリティー

パブリッシャーは探索可能なオブジェクトにいくつかの基本的な制限を課します。 これらの制限の内容は発行可能なオブジェクトに対してのものと同じです。 前に説明したように、発行可能なオブジェクトはかならず doc string を 持っている必要があり、名前がアンダースコアで始まっていてはいけません。

以下の説明は Zope フレームワークを使用している上では重要な内容では ありません。しかしもし、あなたの独自モジュールを発行しようとしている のであればこの節は役に立つでしょう。

パブリッシャーはオブジェクトへのアクセス許可状態チェックを各オブジェクトの ‘__roles__’ 属性の確認で行います。もし ‘__roles__’ 属性があれば、 その値は ‘None’ か、ロール名のリストです。もしこれが None ならば、 オブジェクトは公開 (無認証でアクセス可能) されています。 None でない場合、オブジェクトへのアクセスには確認が必要です。

関数やメソッドなどの多くのオブジェクトは追加の属性設定に対応して いません (すくなくとも Python 2 以前には出来ませんでした)。 したがって、オブジェクトが ‘__roles__’ 属性を持っていない場合、 パブリッシャーはオブジェクトの親オブジェクトに、 ‘オブジェクト名_roles__‘ という属性が無いかを探します。例えば、 ‘getInfo’ 関数のロール情報は 親オブジェクトに ‘getInfo__roles__’ 属性として保持されます。

オブジェクトが ‘__roles__’ 属性をもっているて、それが ‘None’ でなく、 空でもない場合、パブリッシャーはユーザーデータベースを調べて 認証しようと試みます。ユーザーデータベースの検索は ‘__allow_groups__’ 属性を取得するために行われ、まず発行可能なオブジェクトから始めて、 次に、一つ前の探索オブジェクトを確認し、ユーザーデータベースが見つかる まで続けられます。

ユーザーデータベースが見つかれば、パブリッシャーはユーザーが ユーザーデータベース内にいるか確認します。もしユーザーが見つからなければ パブリッシャーは正しいユーザーが見つかるか、ユーザーデータベースが 見つからなくなるまで検索を続けます。

ユーザーデータベースオブジェクトは validate メソッドを提供します:

validate(request, http_authorization, roles)

‘request’ はマッピングオブジェクトで、リクエスト情報を持っています。 ‘http_authorization’ は HTTP ‘Authorization’ ヘッダーの値で、 ヘッダーがなければ ‘None’ になります。 ‘roles’ はユーザーロール名のリストです。

validate メソッドは成功すればユーザーオブジェクトを返しますが、 ユーザーが確認出来なければ ‘None’ を返します。ユーザーオブジェクト については 6 章で詳しく説明します。通常、 validate メソッドが ‘None’ を返した場合、パブリッシャーは他のユーザーデータベースを使おうとしますが、 ユーザーデータベースは例外を発生させてこれを中止させることができます。

validation が失敗したとき、 Zope はブラウザーにユーザー名とパスワード の入力を促すダイアログを表示するよう HTTP ヘッダーを設定するでしょう。 基本認証のための realm 名は ‘__bobo_realm__’ モジュールで制御する ことができます。多くの Web ブラウザーは realm 名をユーザー名と パスワードを入力するダイアログボックスに表示します。

validation が成功した場合、パブリッシャーはユーザーオブジェクトを request の ‘AUTHENTICATED_USER’ 変数に割り当てます。 The publisher places no restriction on user objects. パブリッシャーはユーザーオブジェクトの配置に制限を持っていません。

4.9.6. Zope セキュリティー

独自モジュールの発行に比べ、 Zope を使っているとパブリッシャーが 獲得を使ってユーザーフォルダーを見つけ、セキュリティーチェックを 行ってくれます。これはつまり、発行可能なオブジェクトは ‘Acquisition.Implicit’ か ‘Acquisition.Explicit’ を継承している 必要があると言うことになります。これらのクラスについて詳しくは 5 章 “獲得” を参照してください。 ところで、探索の途中で返される各オブジェクトも獲得が可能となっている 必要があります。これは探索時のオブジェクト取得が ‘getattr’ で 行われていれば自動的に満たしますが、 ‘__getitem__’ か ‘__bobo_traverse__’ でオブジェクトを返している場合には、以下の 例のように手動で行う必要があります:

class Example(Acquisition.Explicit):
    ...

    def __bobo_traverse__(self, name, request):
        ...
        next_object=self._get_next_object(name)
        return  next_object.__of__(self)

最後に、 ‘__allow_access_to_unprotected_subobjects__’ 属性による 探索セキュリティーの抜け穴について 6 章 “セキュリティー” で説明します。

4.10. 環境変数

パブリッシャーの多くの挙動を環境変数設定で制御することが出来ます。

  • ‘Z_DEBUG_MODE’ – デバッグモードを設定します。デバッグモードでは、 トレースバックがエラーページに隠れることがありません。また、 ‘DTMLFile’ オブジェクト、Externalメソッドオブジェクト、ヘルプトピック などが変更時にディスクからリロードされるようになります。 Zope の起動時に ‘-D’ オプションを渡してもデバッグモードで起動する 事が出来ます。
  • ‘Z_REALM’ – 基本認証の realm を設定します。 realm 名は Web ブラウザー のユーザー名とパスワードを入力するダイアログに表示されます。 別の方法として ‘__bobo_realm__’ モジュール変数に設定しておく方法も あります。
  • ‘PROFILE_PUBLISHER’ – プロファイリングを有効にし、プロファイル結果 を記録するファイル名を設定します。 Python プロファイラーについて 詳しくは Python ドキュメント を参照してください。

さらに多くのオプションが起動スクリプトで設定できます。 詳しくは Zope 管理者ガイド を参照してください。

4.10.1. テスト

ZPublisher はテストとデバッガーの仕組みを内蔵しています。 これについては 7 章 “テストとデバッグ” で詳しく説明します。

4.10.2. 発行可能なモジュール

本節は、 Zope フレームワークを使っている人向けではなく、独自のモジュール を ‘ZPublisher’ で発行しようとしている人向けの内容です。

パブリッシャーは探索処理において、パスの最初のエレメントについては モジュールのグローバルな名前空間からオブジェクトを特定します。 別の方法として、最初のオブジェクトを2つのフックから決めることが 出来ます。、

もしモジュールが ‘web_objects’ か ‘bobo_application’ オブジェクトを 定義していれば、最初のオブジェクトはこれらのオブジェクトから検索されます。 ここでの検索は、探索の通常のルールである ‘__bobo_traverse__’, ‘getattr’, ‘__getitem__’ に従って行われます。

モジュール探索の前と後とでコールバック呼び出しを受けることが出来ます。 もしオブジェクトが ‘__bobo_before__’ オブジェクトを定義していれば、 探索処理前に引数無しで呼び出されます。このときの返値は無視されます。 同様に、モジュールが ‘__bobo_after__’ オブジェクトを定義していれば、 探索処理の後で引数無しで呼び出されます。これらのコールバックは 獲得やロックの解放のために使われます。

4.10.3. 発行可能なモジュールの呼び出し

ここまでで、パブリッシャーがどうやって発行可能なオブジェクトを見つけ出し、 結果の値を取り出すかを学びました。次は、発行可能なオブジェクトの呼び出され方 について、もっと詳しく見ていきましょう。

パブリッシャーは引数を request から取り出し、自動的に発行可能なオブジェクト に適用します。これによって Web フォームからのデータを自分で解析せずに フォームのパラメータを使うことが出来ます。あなたのオブジェクトが Web から 呼び出されるとき、大抵は特別な処理をなにもする必要がありません。 以下の関数を見てみましょう:

def greet(name):
    "誰かに挨拶をする"
    return "Hello, %s" % name

この関数の ‘name’ 引数を URL から渡すには greet?name=World のようにします。 あるいはフォーム変数を渡すように HTTP の ‘POST’ リクエストに ‘name’ を 含めて渡す事も出来ます。

次のセクションでは、パブリッシャーがどのようにして引数を生成しているのか、 もっと詳しく見ていきましょう。

4.10.4. リクエストからの引数生成

パブリッシャーは GET や POST リクエストのデータから引数を生成します。 シンプルなフォームフィールドは Python の文字列になります。 複数の値を持つフィールド (チェックボックスと複数選択リスト) は文字列 の配列になります。ファイルアップロードフィールドは ‘FileUpload’ オブジェクトに変換されます。ファイルアップロードオブジェクトは Python の普通のファイルオブジェクトのように振る舞い、これにファイル名を 格納した ‘filename’ 属性と、ファイルアップロード時のヘッダー情報を 格納した’headers’ 属性が追加されています。

パブリッシャーはさらに CGI 環境変数や cookie からも引数を生成します。 引数を割り当てるときにパブリッシャーはまず CGI 環境変数を見て、 次に他のリクエスト変数、フォームデータ、最後に cookie を見ます。 一度変数が見つかると、そこで変数を探す処理を止めます。例えば、 発行オブジェクトが ‘SERVER_URL’ という名前のフォーム変数付きで 呼び出されることを期待しても、これは失敗するでしょう。 この名前の変数は CGI 環境変数の方から先に見つけてしまうので、 フォームのこの変数にはたどり着きません。

パブリッシャーは ‘URL0’ のようないくつかの追加の特別な変数を提供します。 この値はリクエストの内容から生成されます。これらについて詳しくは ‘HTTPRequest’ API ドキュメントを参照してください。

4.10.5. 引数の変換

パブリッシャーは引数の変換をサポートしています。例えば以下の関数:

def onethird(number):
    "3で割った値を返す"
    return number / 3.0

この関数は Web からは呼び出せません。なぜならここで期待している引数は 数値ですが、パブリッシャーはデフォルトでは引数を文字列として受け取る からです。これが、パブリッシャーが多くのコンバータを提供する理由です。 引数の変換を指定するために、フォームの変数名に続けてコロンと型名を 記載してください。例えば、上記の関数に 66 という数値型の引数を 渡したいのであれば、 URL に onethird?number:int=66 と書きます。 パブリッシャーは以下のような多くのコンバータをサポートしています:

  • boolean – 値を真か偽に変換します。変数が 0, None, 空文字列, 空配列, の場合に偽となり、それ以外は真となります。
  • int – 値を Python の integer に変換します。
  • long – 値を Python の long integer に変換します。
  • float – 値を Python の浮動小数点値に変換します。
  • string – 値を Python の文字列に変換します。
  • ustring – 値を Python の unicode 文字列に変換します。
  • required – 変数が設定されなかったり、空文字列などの場合に例外が 発生します。
  • ignore_empty – 変数が空文字列の場合に request のフォーム変数から 取り除きます。
  • data – 文字列を DateTime オブジェクトに変換します。文字列フォーマット は ‘10/16/2000’ や ‘12:01:13 pm’ など、かなり柔軟に受け付けます。
  • list – 値を Python の list の要素として複数、または単一の要素値に 変換します。
  • tuple – 値を Python の tuple の要素として複数、または単一の要素値に 変換します。
  • lines – 文字列を改行で分割して Python の文字列の list に変換します。
  • tokens – 文字列を空白で分割して Python の文字列の list に変換します。
  • text – 変数の改行コードを平準化した文字列に変換します。ブラウザ間で 改行コードが異なっていいますが、このコンバータは改行コードを平準化し、 プログラムがブラウザ毎の改行コードを意識しなくて済むようにします。
  • ulines, utokens, utext – lines, tokens, text の unicode 文字列版です。

パブリッシャーがリクエスト変数をコンバータで変換出来なかった場合には、 例外が発生するでしょう。これはシンプルなアプリケーションには有用ですが、 エラーメッセージを仕立てるといった気の利いた処理が制限されてしまいます。 もし独自のエラーメッセージを提供したければ、発行オブジェクト内で引数を 手動で変換するのが良いでしょう。あるいは可能性として、 JavaScript を 使ってクライアントサイドでデータを投稿する前に入力のチェックを行うという 方法も考えられます。

型コンバータは複数並べることができます。例えばいくつかの数値で構成された list を作るためには:

<input type="checkbox" name="numbers:list:int" value="1">
<input type="checkbox" name="numbers:list:int" value="2">
<input type="checkbox" name="numbers:list:int" value="3">

これらの型コンバータに加え、パブリッシャーは method と record の引数 もサポートしています。

4.10.6. 引数の文字エンコーディング

パブリッシャーはブラウザから送られるフォームフィールドの 文字エンコーディングが何であるかを知っておく必要があります。 これは、 GET か POST (どちらで送信されたかはパブリッシャー自身が 知っています) で送信されたフォームや、フォームを含むページの 文字エンコードが何だったか (これをパブリッシャーが知るには、 あなたの助けが必要です) に関連します。

多くのケースでは、あなたが各フィールドの型コンバータに 文字エンコーディングを指定する必要があります。こういった指定の方法を 含め、全体的にどのように動作するのかについて、以下で説明します。 ただし、たいていの人は全ての詳細までは知る必要はないでしょう。

  1. もしあなたのページの文字エンコーディングが全て UTF-8 (あるいは少なくとも全てのページがフォームを含む) なら、ブラウザは 常に引数に UTF-8 を使うでしょう。この場合、 ‘:utf8’ を以下の例のように 全ての型コンバータ指定の後ろに付けてください:

    <input type="text" name="name:utf8:ustring">
    <input type="checkbox" name="numbers:list:int:utf8" value="1">
    <input type="checkbox" name="numbers:list:int:utf8" value="1">
  1. もしあなたのページの文字エンコーディングが全て ASCII またはそのサブセット (Latin-1, UTF-8, など) なら、boolean, int, long, float, date 型には 文字エンコーディングを指定する必要はありません。 string, tokens, lines, text 型についても、 ASCII 文字コードしかフォームで扱わないのであれば、 文字エンコーディング指定を省略することが出来ます。

4.10.6.1. 引数の文字エンコーディング; その全体像

以下に2つの簡単な分類を作りました。あなたがどちらにも属さないのであれば、 まず、ブラウザで使用する、そしてフォームから送信する文字エンコーディングを 何にするかを決める必要があります。

  1. フォームの送信に GET か POST を使用しており、その際にパラメータ “application/x-www-form-urlencoded” (デフォルト) を付けて送っている。
    1. ページで unicode 系のエンコーディングが使われている場合、フォームは UFT8 で送られます。これは RFC 2718 2.2.5 で定められています。
    2. ページに 8 bit の地域固有のエンコーディングが使われている場合、 フォームはそのページと同じエンコーディングで送信されます。 もし、このようなエンコーディングを使用した場合、ブラウザ毎の 挙動を確認した方が良いでしょう。
  1. フォームの送信に “multipart/form-data” を使う場合:

    HTML 4.01 (セクション 17.13.4) によると、ブラウザは各フィールドの 文字エンコーディングとして Content-Type ヘッダーを使うことになっており、 大抵はこのように動作します。現状、ブラウザはフォームのあるページの エンコーディングと同じものを使用しています。

    全てのフィールドには文字エンコーディング名をコンバータ名のうしろに 付ける必要があります。タグパーサーは、タグがアルファベットと アンダースコアだけで構成される文字列を要求します。このため、 エンコーディング名には Python の ‘encodings’ ライブラリにある 短い名前を使用するのが望ましいでしょう (UTF-8 よりは utf8 を使いましょう)。

4.10.7. method 引数

多くの場合、フォームデータを元にオブジェクトの発行を制御したいと考えます。 例えば、セレクトボックスの選択状態によってどのメソッドを呼び出すかを 変えたい場合などがあります。同様に、送信ボタンが複数ある場合に、 どのボタンが押されたかによって異なるメソッドが呼び出されるようにしたい、 という事もあります。

パブリッシャーはそのための方法として、 method という引数型 を提供しています。 ‘method’ 引数を使うとそのフィールドの名前か値を元に ‘PATH_INFO’ を変換します。

フォームのフィールド名が ‘:method’ なら、そのフィールドの値が ‘PATH_INFO’ に追加されます。例えば、元々の ‘PATH_INFO’ が ‘foo/bar’ で、 ‘:method’ フィールドの値が ‘x/y’ だとすると、最終的に ‘PATH_INFO’ は ‘foo/bar/x/y’ という値に変換されます。これをセレクトボックスの値 に応用すると便利です。メソッド名を option エレメントの値に指定する という使い方が出来ます。

フォームフィールドの名前の末尾に ‘:method’ と付いていた場合、 ‘:method’ より前の名前の部分が ‘PATH_INFO’ に追加されます。 例えば、元々の ‘PATH_INFO’ が ‘foo/bar’ で、 ‘x/y:method’ という フィールドがあるとすると、最終的に ‘PATH_INFO’ は ‘foo/bar/x/y’ という値に変換されます。このときのフィールドの値は無視されます。 これを送信ボタンに応用すると便利です。送信ボタンの value 値は表示に 使用するため、メソッド名を value に指定できないからです。

メソッドフィールドは1つだけ使うようにするべきです。もし複数の メソッドフィールドがリクエストに含まれていた場合、そのときの挙動は 未定義のため何が起きるか分かりません。

4.10.8. record 引数

時々、複数のフィールドの値をそれぞれ異なる引数としてではなく、1つの 構造体として受け取りたいことがあります。これは record 引数で実現出来ます。

‘record’ 型のコンバータは複数のフォーム変数を一つの変数に統合します。 例えば:

<input name="date.year:record:int">
<input name="date.month:record:int">
<input name="date.day:record:int">

このフォームは一つの変数 ‘data’ に変化され、この変数にそれぞれ ‘year’, ‘month’, ‘day’ という属性が付いています。

空のレコード要素を受け取りたくない場合は ‘ignore_empty’ を使って 以下のようにしてください:

<input type="text" name="person.email:record:ignore_empty">

例えばフォームの email フィールドが空文字列として返ってくるよりも、 そのフィールドを無視する方がよいでしょう。ユーザーが email 欄を空のまま 送信すると、レコード ‘person’ が ‘email’ という属性の無い状態で返されます。

‘default’ コンバータを指定すると、デフォルト値を持たせることも出来ます:

<input type="hidden"
       name="pizza.toppings:record:list:default"
       value="All">
<select multiple name="pizza.toppings:record:list:ignore_empty">
<option>Cheese</option>
<option>Onions</option>
<option>Anchovies</option>
<option>Olives</option>
<option>Garlic<option>
</select>

‘default’ 型のフィールドに設定された値は、他のフィールドの値が空の時に 代わりに使われる値です。前述の例では、 toppings の一覧からユーザーが 値を選ばなかった場合に、デフォルト値が使用されます。レコード ‘pizza’ の ‘toppings’ 属性は list 型となり、1つの値 “ALL” が格納されます。 他の選択肢を選択していた場合はその値が list に格納されます。

他にも大きなフォームデータを複数のレコードに格納する ‘records’ 型の コンバータがあります。以下の例を見てみましょう:

<h2>メンバー1</h2>
名前:
<input type="text" name="members.name:records"><BR>
Email:
<input type="text" name="members.email:records"><BR>
年齢:
<input type="text" name="members.age:int:records"><BR>

<H2>メンバー2</H2>
名前:
<input type="text" name="members.name:records"><BR>
Email:
<input type="text" name="members.email:records"><BR>
年齢:
<input type="text" name="members.age:int:records"><BR>

このフォームのデータは ‘members’ という名前の list になり、要素は record になります。各レコードには ‘name’, ‘email’, ‘age’ という属性 があるでしょう。

レコードマーシャリングによって複雑なフォームを作る事が出来るようになります。 ただし、 Web のインターフェースを出来るだけシンプルに保つことはとても 良いことなので、忘れないようにして下さい。

4.10.9. 例外処理

捕まえられなかった例外はオブジェクトパブリッシャーで捕まえられ、 HTTP出力用のきれいなフォーマットに自動的に変換されます。

例外が発生すると、例外の種類でHTTPのステータスコードが割り当てられられます。 例外がHTTPステータスコードに割り当てられていない場合は、 “Internal Error” (500) になります。スタンダードHTTPステータス名として、次のものがあります: “OK”, “Created”, “Accepted”, “No Content”, “Multiple Choices”, “Redirect”, “Moved Permanently”, “Moved Temporarily”, “Not Modified”, “Bad Request”, “Unauthorized”, “Forbidden”, “Not Found”, “Internal Error”, “Not Implemented”, “Bad Gateway”, “Service Unavailable” 。これらのそれぞれの名前のバリエーション として、大文字小文字の違いや空白の有無なども許されています。

発生した例外の値は、レスポンスのBodyとして返せるか確認します。 オブジェクトパブリッシャーは例外の値をチェックして、値がいくつかの空白を 含む文字列であれば、エラーメッセージのBodyとして返すようにします。 それが HTML のようであれば、コンテントタイプを ‘text/html’ に設定し、 そうでなければ ‘text/plain’ に設定します。 例外の値が空白を含む文字列ではない場合、オブジェクトパブリッシャーが 自身でエラーメッセージを生成するでしょう。

さらに以下の二つの種類の例外があります:

  1. 例外の型が次の場合: “Redirect”, “Multiple Choices” “Moved Permanently”, “Moved Temporarily”, “Not Modified”, かつ、例外の値が絶対 URI の場合、 Bodyは空となり ‘Location’ ヘッダーに、与えられた URI が設定されます。
  2. 例外の型が “No Content” の場合、 Body 無しで返されます。

Body が返されると、トレースバック情報は出力のコメント内に含まれるように なります。先に述べたように、トレースバックがどう含まれてるかを制御するのに、 環境変数 ‘Z_DEBUG_MODE’ を使うことができます。もしこの変数が設定されていれば、 トレースバック情報はコメント内ではなく ‘PRE’ タグの中に出力されます。 これはデバッグ時にとても便利です。

4.10.10. 例外とトランザクション

Zope はリクエストを受信するとその時点でトランザクションを開始し、 探索処理を始めます。 Zope は探索で発行するべきオブジェクトを見つけ、 発行が完了した後に、自動的にトランザクションをコミットします。 ですので、通常は各リクエスト毎に Zope が1つずつトランザクションを 用意してくれることになります。トランザクションについて詳しくは 4章を参照してください。

もし処理されない例外が発行処理中に発生した場合、 Zope はトランザクション を中断 (abort) します。4章では Zope が ‘ConflictErrors’ 発生時にリクエスト を3回再処理する仕組みについて、詳しく説明します。例外処理は、 ‘zpublisher_exception_hook’ 呼び出しで終了します。

最後に、そのエラーフックでエラーメッセージをユーザーに提供します。 Zope はエラーフックでエラーメッセージを作成するために ‘raise_standardErrorMessage’ メソッドを呼び出します。このメソッドは ‘SimpleItem.Item’ で実装されています。ここで ‘standard_error_message’ DTML オブジェクトを獲得し、例外情報を渡して呼び出します。

たいていの場合 ‘raise_standardErrorMessage’ メソッドを独自のクラスで オーバーライドする必要はありません。ほかのコンポーネントによって起された 例外は捕まえる必要はありません。たいていのエラーはあなたのコードで捕まえて 必要であればエラーメッセージをログに出力してください。 アプリケーションのエラー報告画面の表示をカスタマイズする必要があれば、 ‘standard_error’message’ DTML オブジェクトオーバーライドしてもよいでしょう。

4.10.11. リクエストとレスポンスへの手動アクセス

多くの場合、リクエストとレスポンスにアクセスする必要はありません。 実際、パブリッシャーの設計目標は、オブジェクト自身は Web 上で発行 されているということを意識しなくて済むようにすることです。しかし、 必要に応じてリクエストから読み取ったり、返すレスポンスをより正確に コントロールすることが許されています。

通常は、発行オブジェクトはメソッドの引数に渡されるリクエストオブジェクトを 使ってリクエストとレスポンスにアクセスします。 もしこれが出来ない場合、獲得によってリクエストオブジェクト にアクセスすることができます。陸エス尾tオブジェクトを取得できれば、 そこからレスポンスオブジェクトを以下のように取得することができます:

response=REQUEST.RESPONSE

リクエストオブジェクトとレスポンスオブジェクトの API については、 API ドキュメントを参照してください。ここではそのなかから一般的に よく使うものを紹介します。

リクエストオブジェクトにアクセスする理由の1つは、フォームデータの正確な 情報を取得することです。前に説明したように、引数マーシャリングでは クッキー、フォームデータ、そして CGI 環境変数、といった複数箇所から値が 取得されます。たとえば、フォームとクッキーのデータを個別に使いたい場合には 以下のように取得できます:

cookies = REQUEST.cookies # クッキーデータの辞書
form = REQUEST.form # フォームデータの辞書

レスポンスオブジェクトにアクセスする理由のひとつに、レスポンスヘッダー への設定があります。パブリッシャーと Web サーバーの協調動作のために、 レスポンスヘッダーを調整することができます。手動でヘッダーを操作したい 場合には以下のように設定できます:

RESPONSE.setHeader('Pragma', 'No-Cache')

レスポンスオブジェクトにアクセスする他の理由に、レスポンスデータの ストリーミング出力があります。以下のようにして ‘write’ メソッドを使います:

while 1:
    data=getMoreData() #この呼び出しはおそらく多少時間がかかります
    if not data:
        break
    RESPONSE.write(data)

ここで、メソッドが Web から呼び出されているかどうかを確認するためのコードの 例として以下の関数を見てみましょう:

def feedParrot(parrot_id, REQUEST=None):
    ...

    if REQUEST is not None:
        return "<html><p>Parrot %s fed</p></html>" % parrot_id

‘feedParrot’ 関数は Python からも Web からも呼び出し可能です。引数に ‘REQUEST=None’ が含まれており、この値で Python から呼び出されたか Web から呼び出されたかを見分けることができます。関数が Python から呼び出された 場合、この関数は None を返しますが、 Web から呼び出された場合には HTML の確認メッセージを返します。

4.11. HTTP以外のネットワークプロトコル

4.11.1. FTP

Zope は FTP サーバーとしても動作させることが出来ます。 FTP サーバーでは Zope のオブジェクト階層をファイルサーバーのように辿ることができます。 3章では Zope が提供している基本クラス (‘SimpleItem’ と ‘ObjectManager’) によって簡単な FTP 機能が Zope の全オブジェクトに提供されている事 についてまとめています。 FTP の API については API リファレンスで カバーしています。

あなたのオブジェクトを FTP 対応にするために、オブジェクトをファイル のように見せる必要があります。これは全てのオブジェクトが可能な事では無いし、 簡単に出来ないこともあります。そのオブジェクトに FTP 経由でアクセスして 何をしたいのかをよく考える必要があります。また、利用者がどのツールで オブジェクトを編集したいのかも調べておいたほうが良いでしょう。例えば、 XML はオブジェクトを FTP で見せるのに適した方法かもしれませんが、 利用者がそれを編集するのは簡単ではありません。 ここに例として RFC 822 フォーマットのファイルとして変換表現したクラスがあります:

from rfc822 import Message
from cStringIO import StringIO

class Person(...):

    def __init__(self, name, email, age):
        self.name=name
        self.email=email
        self.age=age

    def writeState(self):
        "オブジェクトのステートを文字列で返す"
        return "Name: %s\nEmail: %s\nAge: %s" % (self.name,
                                                 self.email,
                                                 self.age)
    def readState(self, data):
        "オブジェクトのステートを文字列で受け取って設定する"
        m=Message(StringIO(data))
        self.name=m['name']
        self.email=m['email']
        self.age=int(m['age'])

‘writeState’ と ‘readState’ メソッドは、オブジェクトの ‘name’, ‘age’, ‘email’ 属性を文字列にシリアライズ/復元します。 インスタンスの属性を RFC 822 形式のファイルにする効率的な方法は他にもありますが、 RFC 822 は利用者にとってテキストエディタで編集しやすいシンプルなフォーマットです。

FTP をサポートするために必要なことは ‘manage_FTPget’ と ‘PUT’ メソッド を実装することが全てです。例えば:

def manage_FTPget(self):
    "FTP にステートを返します"
    return self.writeState()

def PUT(self, REQUEST):
    "FTP からのステートを設定します"
     self.readState(REQUEST['BODY'])

上記の他に、 ‘get_size’ メソッドを実装して ‘manage_FTPget’ の返値の データサイズを返すようにすることもできます。これが必要になるのは、 ‘manage_FTPget’ の呼び出し処理に非常に時間がかかる場合くらいですが、 ファイルサイズを知らせるためにはとても効果的な方法です。 前述の例では ‘get_size’ メソッドを実装する意味はありません。

‘PUT’ メソッドを実装する副作用として、そのオブジェクトは HTTP PUT による発行をサポートするようになります。次の WebDAV のセクションで、 HTTP PUT についてより詳しく説明します。

以上で、あなたのオブジェクトは FTP で動作するようになりました。 次の WebDAV サポートでやることはほとんど同じです。

4.11.2. WebDAV

WebDAV はリモートサーバーからファイルを協調的に管理・編集するための プロトコルです。このプロトコルの多くの機能は FTP と同じですが、 HTTP プロトコル上で動作します。

オブジェクトに WebDAV サポートを追加するのは難しくありません。 実装で一番難しいのはオブジェクトをどんなフォーマットのファイルとして 表現するかという部分です。

あなたの class を ‘webdav.Resource’ を継承するようにすれば、基本的な DAV 機能はサポートされます。ところで、前述の ‘SimpleItem’ クラスは ‘Resource’ を継承しているので、あなたの class は既に ‘Resource’ を継承していることになります。同様に、コンテナを WebDAV 対応にするには ‘webdav.Collection’ を継承する必要がありますが、 ‘ObjectManager’ が ‘Collection’ を継承しているため、あなたのコンテナが ‘ObjectManager’ を継承していれば対応済みということになります。

DAV クラスからの継承の他に、 ‘PUT’ と ‘manage_FTPget’ メソッドを実装 する必要があります。この2つのメソッドは FTP サポートでも必要でした。 つまり、 WebDAV サポートを実装すると、同時に FTP サポートが実装される ということになります。

前述の2つのメソッドにパーミッションを設定することで、 WebDAV 経由の読み書き それぞれのアクセス権をコントロールできますが、オブジェクトを WebDAV 経由で見せるには “WebDAV access” パーミッションを設定します。

4.11.3. 書き込みロックのサポート

書き込みロックは WebDAV の機能で、利用者が作業中のオブジェクトにロックを 設定する事ができます。書き込みロックをサポートするのは簡単で、 以下の例のように ‘WriteLockInterface’ の実装を表明するだけです:

from webdav.WriteLockInterface import WriteLockInterface

class MyContentClass(OFS.SimpleItem.Item, Persistent):
    __implements__ = (WriteLockInterface,)

‘SimpleItem.Item’ から継承すれば必要な要件を満たし、 ‘webdav.Resource’ から継承するのと同義で、これで他の DAV サポートと同様に、 書き込みロック機能が提供されます。

次に ‘PUT’ メソッドを実装してそのメソッド内で最初に ‘dav__init’ と ‘dav_simpleifhandler’ を呼び出しておきましょう:

def PUT(self, REQUEST, RESPONSE):
    """
    Implement WebDAV/HTTP PUT/FTP put method for this object.
    """
    self.dav__init(REQUEST, RESPONSE)
    self.dav__simpleifhandler(REQUEST, RESPONSE)
    ...

最後に、あなたのクラスの編集メソッド内で、 ‘ws_isLocked’ メソッドを使って、 オブジェクトがロックされているかどうか確認しましょう。もし誰かが オブジェクトを更新しようとしていれば、そのオブジェクトはロックされ、 これを変更しようとした場合 ‘ReosurceLockedError’ を raise する事に なっています。例えば:

from webdav import ResourceLockedError

class MyContentClass(...):
    ...

    def edit(self, ...):
        if self.ws_isLocked():
            raise ResourceLockedError
        ...

WebDAV サポートの実装は難しくなく、さらに多くの WebDAV 用エディタが 増えてきている中、 WebDAV サポートの価値は増えてきています。 もし FTP サポートを追加する予定があれば、 WebDAV サポートも行うべきです。 WebDAV サポートの実装は FTP サポートが済んでいればすぐ簡単にできるでしょう。

4.11.4. XML-RPC

XML-RPC is a light-weight Remote Procedure Call protocol that uses XML for encoding and HTTP for transport. Fredrick Lund maintains a Python <XML-RPC module <http://www.pythonware.com/products/xmlrpc>`_ .

All objects in Zope support XML-RPC publishing. Generally you will select a published object as the end-point and select one of its methods as the method. For example you can call the ‘getId’ method on a Zope folder at ‘http://example.com/myfolder‘ like so:

import xmlrpclib
folder = xmlrpclib.Server('http://example.com/myfolder')
ids = folder.getId()

You can also do traversal via a dotted method name. For example:

import xmlrpclib

# traversal via dotted method name
app = xmlrpclib.Server('http://example.com/app')
id1 = app.folderA.folderB.getId()

# walking directly up to the published object
folderB = xmlrpclib.Server('http://example.com/app/folderA/folderB')
id2 = folderB.getId()

print id1 == id2

This example shows different routes to the same object publishing call.

XML-RPC supports marshalling of basic Python types for both publishing requests and responses. The upshot of this arrangement is that when you are designing methods for use via XML-RPC you should limit your arguments and return values to simple values such as Python strings, lists, numbers and dictionaries. You should not accept or return Zope objects from methods that will be called via XML-RPC.

XML-RPC does not support keyword arguments. This is a problem if your method expect keyword arguments. This problem is noticeable when calling DTMLMethods and DTMLDocuments with XML-RPC. Normally a DTML object should be called with the request as the first argument, and additional variables as keyword arguments. You can get around this problem by passing a dictionary as the first argument. This will allow your DTML methods and documents to reference your variables with the ‘var’ tag. However, you cannot do the following:

<dtml-var expr="REQUEST['argument']">

Although the following will work:

<dtml-var expr="_['argument']">

This is because in this case arguments are in the DTML namespace, but they are not coming from the web request.

In general it is not a good idea to call DTML from XML-RPC since DTML usually expects to be called from normal HTTP requests.

One thing to be aware of is that Zope returns ‘false’ for published objects which return None since XML-RPC has no concept of null.

Another issue you may run into is that ‘xmlrpclib’ does not yet support HTTP basic authentication. This makes it difficult to call protected web resources. One solution is to patch ‘xmlrpclib’. Another solution is to accept authentication credentials in the signature of your published method.

4.12. Summary

Object publishing is a simple and powerful way to bring objects to the web. Two of Zope’s most appealing qualities is how it maps objects to URLs, and you don’t need to concern yourself with web plumbing. If you wish, there are quite a few details that you can use to customize how your objects are located and published.

目次

前のトピックへ

3. コンポーネントとインターフェース

次のトピックへ

5. Zope Products

このページ

お問い合わせ

日本語訳についてのご意見などは zope.jp の連絡フォーム からお願いします。