CGIプログラマーのためのCherryPy

この単純なPythonフレームワークでCGIスクリプトを置き換え、強力なWebアプリケーションを構築する

Python用のCherryPyアプリケーション・フレームワークを利用すると、平凡なCGI(Common Gateway Interface)よりも容易にWebアプリケーションを書くことができます。しかもCherryPyは、ほとんど使わない機能で一杯というようなことはなく、単純であり、簡単に学べるものです。この記事では、CherryPyでWebアプリケーションを書くために必要なもの全てを紹介します。

Leonard Richardson (leonardr@segfault.org), Software Engineer, CollabNet

Leonard Richardsonは、Pythonのアプリケーションやライブラリー(NewsBruiserやBeautiful Soupその他)に関する数多くの著書を執筆しています。また、Wrox社から刊行の新しい大著、Beginning Pythonの共著者でもあります。



2005年 8月 16日

過去10年以上、WebプログラマーはCGI(Common Gateway Interface)を使って、向こうにあるWebサーバーやWebアプリケーションに接続していました。CGIには推薦すべき要素が数多くあります。どんなプログラミング言語でも使え、ほとんど全世界のWebサーバーやホスティング・サービスがサポートしています。ところが残念なことに、CGIには重大な欠点もあります。WebサーバーとCGIスクリプトの間のインターフェースが少し複雑な上、Webサーバーは、それぞれのCGIリクエストに対して別々のプロセスをバラまきます。そのためパフォーマンスは貧弱であり、複数のリクエストにまたがってパーシスタンスを持たせることができません。

不満を持ったハッカー達は長年、Webサーバーとアプリケーション・コードとの隙間の橋渡しのための、様々な代替方法を考案してきました。最近人気のある方法としては、Java?サーブレットを使う方法や、RailsフレームワークでのRuby、Apacheモジュールであるmod_perlやmod_pythonなど、幾つかのものがあります。

こうした橋渡し技術は、あまりにも数が多いため1つを選ぶのは難しく、特にPythonの世界では、こうした技術がありすぎることが深刻な問題になっています。サーバー・アプリケーション用の橋渡し技術の中には、完全装備のアプリケーション・フレームワークとして、独自のテンプレート・システムや認証サービス、オブジェクト指向マッパーなどの機能を持っているものもあります。あまりにも多くのものがあり、これらの技術を使うためには、あまりにも多くのことを学ばねばならないことから、時間のないプログラマーは自分が既に知っているものを使い続ける、という状況は驚くに当たりません。

この記事では、Python用のWebフレームワークとして、単純ながら非常に使い道のある、CherryPyを紹介します。CherryPyがすることというのは、最小限の手間でWebサーバーをPythonコードに接続する、ということだけです。CherryPyは、他にどんなツールを使うべきかを規定していないため、任意のテンプレート・システムやデータベース・マッパー、あるいはその他のツールを自由に使うことができます。ここでは、CherryPyを使ったアプリケーションを書く方法を説明します。この記事では前提として、Pythonと、HTTPリクエストとリスポンスの動作について多少の知識を持っていることが必要です。

CherryPyリクエスト

CherryPyは、Apacheやその他のWebサーバーに頼らず、独自の小さなPythonベースのWebサーバーを実行します。伝統的なWebサーバーでは、ディレクトリー・ディスクのツリーからWeb空間を作りますが、CherryPyサーバーは、PythonオブジェクトのツリーからWeb空間を作ります。

ここで、http://localhost:8080/hello/というURLへのリクエストを考えてみてください。伝統的なWebサーバーでは、このURLは、Web空間のルートの下にあるhello/ディレクトリーに対応します。Webブラウザーからこれにアクセスすると、Webサーバーはhello/ディレクトリーのindex.htmlファイルを読み取るか、あるいは、そのディレクトリーのindex.cgiスクリプトをCGIとして呼び出し、出力を送信します。

CherryPy Webサーバーは、ディスク上のディレクトリーをルートとするWeb空間を提供するのではなく、cpg.rootという特定なPythonオブジェクトをルートとするWeb空間を提供します。このオブジェクトの各メソッド、各メンバーにアクセスするには、その名前をルートURLに付加します。従って、CherryPyでのhello/ URLは、cpg.rootのhelloメンバーに対応します。

メソッドを定義することによって、リクエストを処理する

cpg.root.helloがメソッドの場合は、CherryPyはそのメソッドを呼び、そのメソッドの出力はWebブラウザーに送られます。下記のコードは、helloメソッドを公開するオブジェクトを定義しています。

リスト1. helloメソッドを公開するオブジェクトを定義する
#!/usr/bin/env python
from cherrypy import cpg

class Application:
@cpg.expose
def hello(self):
return "Hello, world!"

cpg.root = Application()
cpg.server.start()

このスクリプトをPythonで実行すると、CherryPy Webサーバーが起動します。このスクリプトを実行している限り、http://localhost:8080/hello/にアクセスすることができ、ストリング、Hello, world!、が送られてきます。(当然ながら、この前にポート8080でサーバーを実行していない、という前提です、)Webサーバーのルートを含めて、他のURLをアクセスすると、CherryPyエラーが出ます。このアプリケーションが処理方法を知っているURLは、/hello/だけなのです。

オブジェクト・ツリーを定義することでリクエストを処理する

cpg.root.helloがメソッドではなくオブジェクトである場合には、ユーザーが/hello/をアクセスすると、CherryPyはhelloオブジェクトのindex()メソッドを呼びます。このコードは、この前の例と同じ方法で、/hello/ URLを処理します。

リスト2. CherryPyがhelloオブジェクトのindex()メソッドを呼ぶ
#!/usr/bin/env python
from cherrypy import cpg

class HelloWorld:
@cpg.expose
def index(self):
return "Hello, world!"

class Application:
hello = HelloWorld()

cpg.root = Application()
cpg.server.start()

/hello/ URLをアクセスすると、そのリクエストはオブジェクト、cpg.root.helloにマップされ、このオブジェクトのデフォルト・メソッド、index()が呼ばれてリクエストを処理します。


オブジェクトとメソッドを公開する

helloやindexメソッドの@cpg.expose装飾子の目的は何でしょう。これはそのメソッドを、Webリクエストへのレスポンスとして呼んでもOKである、とCherryPyに伝えるのです。

静的なファイルを提供するWebサイトでは、そのWeb空間にあるファイルやディレクトリーは、すべて公開されるものと想定されています。ただし例外もあります。例えばApacheは、UNIXRシステムでは.htaccessのような隠しファイルを公開しません。

オブジェクト・ツリーをCherryPy URL空間として公開する場合には、想定が逆になります。つまり、あるメソッドを明示的にexposedとしてラベル付けしない限り、そのメソッドは外部ユーザーに公開されないのです。多くのプログラミング言語での、パブリック・クラス・メンバーとプライベート・クラス・メンバーとの区別(Pythonでは_method()規則によって非公式に強制されます)を考えてみてください。よく設計されたCherryPyクラスであれば、Webクライアントに公開されるパブリック・メソッドは数個しかなく、クライアントが直接アクセスを許されない内部メソッドが数多くある、というようになるはずです。

私はCherryPyにメソッドを公開する方法としては装飾子が最良だと思いますが、次のように、そのexposedメンバーをTrueに設定することでメソッドを公開することもできます。

リスト3. exposedメンバーをTrueに設定することでメソッドを公開する
class HelloWorld:
def index(self):
return "Hello, world!"
index.exposed = True

2.4以前のバージョンのPythonでは、装飾子は存在しません。ですからCherryPyにメソッドを公開する方法としては、exposedを使う以外にない場合もあるかも知れません。


ユーザー入力を集める

ユーザーがフォームを送信すると、あるいは、CGIスクリプトに対して情報を提供すると、Webブラウザーはその情報を集め、環境変数を通してスクリプトに情報を渡します。この情報を構文解析し、意味を理解するのは、スクリプトの責任ですが、この情報に関する適切なフォーマットや使い方に関して一連の規則があります。CherryPyは、このプロセスの幾つかのステップを省略するために、こうした非公式な規則を利用しており、ユーザー入力を、CherryPyが呼ぼうとしているPythonメソッドへの引数として提供します。

クエリー・ストリングをキーワード引数に変換する

例えばhttp://localhost:8080/hello/?what=hello&who=worldのような、クエリー引数(query arguments)を持つURLを考えてみてください。ユーザーは、このURLをクリックしたのかも知れず、あるいはHTMLフォームに書き込んで送信したのかも知れません。伝統的なCGIベースのWebサーバーでは、QUERY_STRING環境変数の中にあるCGIスクリプトにwhat=hello&who=worldを渡します。CGIスクリプトは、その変数をフェッチすることに責任を持ち、ストリングを構文解析します。Pythonのcgiモジュールが、構文解析をしてくれるのです。しかしCherryPyでは、何もする必要がありません。CherryPy Webサーバーが、URLクエリー・ストリング(query string)を一連のキーワード引数に自動的に変換してくれるのです。

リスト4. CherryPy WebサーバーがURLクエリー・ストリングをキーワード引数に自動変換する
class Application:
@cpg.expose
def hello(self, what='Hello', who='world'):
return '%s, %s!' % (what, who)

ユーザーが/hello/ URLをヒットすると、CherryPyは、クエリー・ストリングの中にある全てのwhatやwhoを、hello()メソッドへの引数にと変換します。URLからPythonメソッド・コールへの変換は、完全に透明に行われます。オリジナルのHTTPリクエストが、GETメソッドから入って来たのかPOSTメソッドから入って来たのかさえも無関係なのです。

追加のパス部分を位置引数に変換する

CGIスクリプトに対するユーザー入力になるものとして、もう一つはPATH_INFO環境変数です。この変数は一般的に、WebアプリケーションのURLを本物のWebページのように見せるために使われます。例えば、URL http://localhost:8080/hello/world/を考えてみてください。/hello/がCGIスクリプトを指定している場合には、/hello/world/をアクセスすると、そのスクリプトが(PATH_INFO環境変数を/world/に設定されて)呼び出されます。

CGI環境では、追加のパス情報を構文解析するのはCGIスクリプトの責任です。ところがCherryPyではクエリー・ストリング引数の場合と同じく、典型的な使用規則に基づいて、CherryPyが構文解析を行ってくれるのです。一方、クエリー・ストリングの鍵と値の対はCherryPyアプリケーションのキーワード引数になり、追加パス情報引数は、オブジェクトのdefault()メソッドに対する位置引数(positional arguments)になります。

リスト5. 追加パス情報引数はオブジェクトのdefault()メソッドに対する位置引数になる
class Hello:
@cpg.expose
def default(self, who):
return 'Hello, %s!' % who

class Application:
hello = Hello()

cpg.root = Application()
cpg.server.start()

/hello/world/ URLのhello/部分は、Helloのインスタンスであるcpg.root.helloにマップされます。Helloオブジェクトには、worldというメソッドやメンバー・オブジェクトは無いので、URLのworld部分は、そのオブジェクトのdefault()メソッドに対する位置引数として渡されます。


リクエスト・オブジェクトからのリード・ヘッダー

CherryPyは、ユーザーが要求したURLの構文解析を行い、また適当な引数を付けてPythonメソッドにディスパッチする処理を行います。しかしHTTPリクエストは、単なるURL以上のものです。では着信HTTPヘッダーはどうなのでしょう。

CherryPyメソッドは、cgp.requestというオブジェクトにアクセスすることができます(cgp.requestは、ユーザーのHTTPリクエストに関する多くの情報を含んでいます)。このオブジェクトのメンバーのうち一番面白いのは、Webリクエストに関連する着信HTTPヘッダー全てを含んだrequestMapです。

リスト6. requestMapはWebリクエストに関連する着信HTTPヘッダーを含む
class Application:
@cpg.expose
def index(self):
items = [x + ': ' + y for x,y in cpg.request.headerMap.items()]
return "<br />".join(items)

このアプリケーションを実行し、http://localhost:8080/をアクセスすると、ブラウザーがリクエストと共に送信した、すべてのHTTPヘッダーのリストを見ることができます。


レスポンス・オブジェクトに書き込む

レスポンスの場合も、HTTPリクエストと同じです。CherryPyアプリケーション・メソッドは通常、レスポンス本体をストリングとして返します。しかし場合によると、追加のHTTPヘッダーを設定する必要があったり、HTTPレスポンス・コードをリダイレクトしたり変更したりする必要がある場合があります。これらはすべて、各メソッドで利用できるcpg.responseオブジェクトを使って行うことができます。

cpg.response.headerMapは、出力HTTPヘッダーのマップです。cpg.request.requestMapが着信ヘッダーのマップであるのと、ちょうど同じです。

リスト7. cpg.response.headerMapは出力HTTPヘッダーのマップ
#!/usr/bin/env python
from cherrypy import cpg

class Application:
@cpg.expose
def setHeader(self, header, value):
"""Hit the '/setHeader?header=Value&foo=bar' URL to get a
response in which the HTTP header "foo" has a value of
"bar"."""
cpg.response.headerMap[header] = value
return 'Set HTTP response header "%s" to "%s"' % (header, value)

HTTPステータス・コードは、Statusという、単なるHTTPヘッダーです。ですから、404とか503とか、自分にとって必要な、任意のステータスに設定することができます。

リスト8. HTTPヘッダー・ステータスを設定する
@cpg.expose
def forbidden(self):
"Hit the '/forbidden' URL to be denied access."
cpg.response.headerMap['Status'] = '503 Forbidden'
return "You don't have permission to access this resource."

HTTPリダイレクトを行うには、StatusヘッダーとLocationヘッダーを手動設定するか、あるいはそれと同じことをする、CherryPyのhttputilsヘルパー・ライブラリーのredirectメソッドを使います。

リスト9. CherryPyのhttputilsヘルパー・ライブラリーのredirectメソッドを使う
@cpg.expose
def redirect(self):
"Hit the '/redirect' URL to be redirected."
from cherrypy.lib import httptools
httptools.redirect('./destination')

@cpg.expose
def destination(self):
"This is where you end up if you hit the '/redirect' URL."
from cherrypy.lib import httptools
cpg.response.headerMap['Content-Type'] = 'text/plain'
return 'Here is some plain text.'

cpg.root = Application()
cpg.server.start()

セッション中のパーシスタンス情報を保持する

あるアプリケーションとして、URL /name/set?name=leonardrをヒットして、ちょっとしたデータを設定するものを考えてみてください。次にURL /name/showをヒットすると、「Your previously set name is leonardr(先ほど設定した名前はleonardrです)」と表示されるのです。2番目のリクエストは最初のリクエストの情報を使うため、両方のリクエストで1つのセッションとなっている必要があります。最初のリクエストで送信したleonardrというストリングは、サーバー上のどこかに保存されています。私が2番目のリクエストを送信すると、何らかの方法によって、最初にリクエストを送信した人物と私が同じ人物であると認識され、最初のリクエストで一緒に送信した情報が取り出されるのです。

CherryPyでは、この複雑さの大部分が見えないようになっており、クッキー・ベースのセッションが設定しやすく、また使いやすくなっています。これは重要な機能ですが、CGIそのままでサポートされている機能ではありません。cpg.request.sessionMapマップには任意のPythonオブジェクトを保存することができ、同じユーザーがそのページを次にヒットした時にも、そのオブジェクトはそこにあります。下記は、上で説明した動作をするアプリケーションの例です。

リスト10. cpg.request.sessionMapマップに任意のPythonオブジェクトを保存する
#!/usr/bin/env python
from cherrypy import cpg

class Application:
@cpg.expose
def set(self, name):
cpg.request.sessionMap['name'] = name
return 'Set name to %s' % name

@cpg.expose
def show(self):
return 'Your previously set name is %s.' % \
cpg.request.sessionMap.get('name', '[none]')

ここまでは非常に単純でした。しかし、このコードが動作するためには、セッション・クッキーと各HTTPレスポンスを関連付けるようにCherryPyサーバーを設定しておく必要があります。そうしないと、CherryPyは2つのリクエストを同じセッションに関連付けることができません。この設定は、CherryPyサーバーのコンフィギュレーション・ファイルで行うことができる(参考文献)のですが、CherryPy Webサーバーを起動するPythonコードの一部として見た方が分かりやすいでしょう。

リスト11. CherryPy Webサーバーを起動するPythonコードの一部
cpg.root = Application()
cpg.server.start(configMap={'sessionStorageType' : 'ram',
            'sessionCookieName' : 'CherryPySessionCookie',
            'sessionTimeout' : 60}) #Session expires in an hour

まとめ

CherryPyは、WebサーバーをWebアプリケーションに結びつけるために、CGIと同じ概念を使っています。ただし、パフォーマンスが改善されており、すべてのリクエストを1つのプロセス内で処理することによって、リクエストにまたがるパーシスタンスを実現しています。CherryPyはPythonコードとしかバインドできないため、CGIにつきものの、不明瞭な情報受け渡し技術を必要としません。これによってコードの行数は減り、またコードが理解しやすいものになります。CherryPyはCGIの置き換えとして素晴らしいものであり、PythonのWebアプリケーションを構築する上で、良いベースとなります。


ダウンロード

内容ファイル名サイズ
Sample applicationsos-CherryPy-sample-code.zip3KB

参考文献

  • The Web Framework Shootoutは、CherryPyを含めて、数多くの一般的なPython Webフレームワークを比較しています。
  • CherryPy Web siteには、基本的なチュートリアルや、より高度な使用方法サードパーティー製のアドオンのリストなどが用意されています。
  • Apacheが提供するURLにアプリケーションをマウントする、mod_pythonのようなフレームワークとは異なり、CherryPyはリライト・ルールでApacheに接続されます。デフォルト設定では、CherryPyサーバーもApacheも、皆さんのマシンで同時に実行し、CherryPyリクエストがApacheリクエストを代行します。これは複雑に思えるかも知れませんが、実際にはmod_pythonよりも簡単に設定できるのです。Apacheを通してCherryPyリクエストを処理する方法に関して学ぶには、CherryPy Wikiを見てください。
  • この記事で取り上げた単純なセッション例は、そのままで動作するはずです。もし皆さんが、ユーザーのセッションでもっと複雑な操作を行いたい場合には、同じユーザーからの2つの同時リクエストによってセッションが矛盾した状態にならないように、セッションを確実にロックする必要があります。WikiのThe "Locking sessions" sectionを読むと分かりやすいでしょう。
  • CherryPy Webサーバーのコンフィギュレーション・ファイルのフォーマットと、そこに入れられるオプションに関しては、このWikiを読んでください。
  • Charming Python: Review of Python IDEs」を読んで、Pythonツールに関してさらに学んでください。
  • Discover Python, Part 1: Python's built-in numerical types」は、Java開発者のために書かれたシリーズの最初の記事です。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=236389
ArticleTitle=CGIプログラマーのためのCherryPy
publish-date=08162005