Python と Twisted できみにも書ける Web サーバ(1)

前置き

何で今さら自分で Web サーバ書くの?

簡単に書けるから、です。それに「Web サーバは何使ってるの?」と尋ねられたとき「Apache 使ってます」と言うより「自分で書きました」と答えるほうがカッコ良さそうだからです。あと、機能てんこ盛り Apache のマニュアルや設定ファイルと格闘するくらいなら、自分でサーバ書いちゃった方が早いです、 マジ。

それに Web サーバ以外のアプリケーションを書くときでも、管理用のインターフェースとして Web サーバ機能が欲しいというケースが多いはず。デスクトップアプリケーションだって、マルチプラットフォームで動かしたいなら Web をインターフェースにしてしまうのが手っ取り早いでしょ。Web サーバの書き方をマスターしといて損はないはず。

誰でも比較的簡単に Web サーバを書くことを可能にするのが Python と Twisted というフレームワークの組合せです。

Python 初心者でも何とかなりますが、ここで使う Twisted というのが若干取っつきにくいと思うので、事前に TkInterPyGTK などでイベントドリブンなプログラミングを経験しておくと、より仕組みを理解しやすくなるはずです。

解説は Linux などの Unix 系環境を前提に書いていますが、Windows でもほとんどの部分は同じようにできるはずです。

用意するもの

次のソフトウェアを使用します。

このほか Zope Interface といものが必要なのですが、これは Twisted 2.4 のアーイカイブに同梱されています。

Twisted のアーカイブを展開したら中は各モジュール毎のサブディレクトリに分かれていますから、次の順番でインストールしてください。

  1. Zope Interface

  2. Twisted Core

  3. Twisted Web

Web サーバを書くのに必要な Twisted モジュールは Twisted CoreTwisted Web だけですから、ほかのモジュールはインストールしなくても構いません。

Twisted をインストールしたら続いて Nevow もインストールしておきます。

使用するモジュールについて若干解説

Twisted はイベントドリブンなネットワーク・プログラミング用のフレームワークです。Twisted 自体は Web サーバを書くためだけのものではなく、様々なネットワークアプリケーション(サーバあるいはクライアント)を作成するための汎用フレームワークです。きょうびアプリケーションといえば何らかのネットワーク機能を持つものがほとんどですから、Twisted の使い方をマスターすると大変重宝します。

Web サーバ/クライアント用の Twisted モジュール が Twisted Web です。実はこれ、既に開発が終了しておりバグフィクスだけのメンテナンス状態です。Twisted プロジェクトでは新たなモジュール Twisted Web2 を開発中です。だだし Web2 はまだ安定バージョンとしてリリースはされていません。

もうひとつ Twisted プロジェクトとは別に Divmod Inc. という会社が開発した Web サーバ専用モジュールがあります。それが Nevow です。 Nevow はいわば Twisted Web のサーバ部分を大幅にリライト、拡張したモジュールでTwisted Web2 の仕様の大部分を先取りしたものになっています。

この Divmod Inc.、実は Twisted の中心メンバーがやっている会社だったりします。自分たちの会社で Web サーバ機能が必要なんだけど、Twsited Web じゃ機能不足、だけど Web2 を待っていられない。仕方ないから必要なものを別モジュールとして作ってしまおう、という経緯じゃないかと思います。Nevow には Web サーバ機能だけじゃなく、XML ベースの HTML テンプレートや今流行りの Ajax/Comet などを実現する機能も付いています。

今回は Twisted Web + Nevow の組合せで説明します。

あと Twisted や Nevow では、Python で Java にあるようなインターフェースを Python で実現するためのモジュール Zope Interface がいたるところで使われています。これの解説は zope.interface の README.txt 翻訳 というのがあるのだけど、結構ややこしいので使いながらおぼえていくほうが早いと思います。

基本的なあれこれ

スタティック・ファイルの配信

準備ができたら早速サーバを書いていみます。以下は /home/yasusii/www/html に置いたコンテンツを配信するだけのシンプルなサーバです。コマンドラインから python static1.py を実行するとサーバが立ち上がります。

# 単純なスタティックファイルの配信
# static1.py

from nevow import appserver, static
from twisted.internet import reactor

DOCUMENT_ROOT = '/home/yasusii/www/html'
PORT = 8080

resource = static.File(DOCUMENT_ROOT)
site = appserver.NevowSite(resource)

reactor.listenTCP(PORT, site)
reactor.run()

Web ブラウザでアクセスして動作を確認してみましょう。ちゃんと HTML が表示されますよね?たったこれだけですけど、そのへんの中小 Web サイトでトラフィックをさばくに充分な能力を持っています。デーモンとして実行する場合の書き方は後述します。

上記コードについて説明します。最終行の twisted.internet.reactor.run() がイベントループの実行です。その直前のtwisted.internet.reactor.listenTCP() は指定された TCP ポートを監視し、クライアントからのリクエストの都度、対応するイベントハンドラを登録する処理に相当します。

nevow.static.File は nevow.inevow.IResource を実装したリソースタイプのクラス、nevow.appserver.NevowSite は twisted.internet.interfaces.IProtocolFactory を実装したプロトコルファクトリタイプのクラスです。

>>> from nevow import static
>>> import zope.interface
>>> list(zope.interface.implementedBy(static.File))
[<InterfaceClass nevow.inevow.IResource>]
>>> from nevow import appserver
>>> list(zope.interface.implementedBy(appserver.NevowSite))
[<MetaInterface twisted.internet.interfaces.IProtocolFactory>]

プトコルファクトリはその名の通り、リクエストの都度プロトコル(この場合HTTP)を処理するオブジェクトを生成するファクトリです。要するに twisted.internet.reactor.listenTCP() の引数にはプトロコルファクトリを指定、プロトコルファクトリの引数にはリソースを指定する。とりあえずこれだけ覚えておけば OK です。

Nevow の機能じゃもの足りないぜ、プロトコルファクトリくらい自分で書くぜ、 というレベルにならない限りプロトコルファクトリをいじる必要はあまりあり ません。カスタマイズしたり、独自のクラスを書いたりするのはもっぱらリソー スのほうになります。

たとえば先に使用した nevow.static.File は URL にディレクトリが指定され た場合、index.html や index.htm を探して出力、見つからなければディレク トリのファイル一覧を表示する HTML を生成するようになっています。Apache でいえば Option Indexes が有効になった状態です。ファイル一覧を生成する メソッドは directoryListing() です。

>>> dir(static.File)
['__doc__', '__implemented__', '__init__', '__module__', '__providedBy__', '__pr
ovides__', 'contentEncodings', 'contentTypes', 'createSimilarFile', 'directoryLi
sting', 'getFileSize', 'ignoreExt', 'indexNames', 'listNames','locateChild', 'o
penForReading', 'processors', 'putChild', 'redirect', 'renderHTTP', 'type']

ファイル一覧を生成したくない場合は directoryListing() をオーバーライドします。次の例は index.html が見つからない場合 403 FORBIDDEN を返すリソースの例。

from nevow import static
from twisted.web import error

class FileWithoutListing(static.File):

    
def directoryListing(self):
        
return error.ForbiddenResource()

エラー用のリソースはあらかじめ twisted.web.error モジュールに用意されているため、twisted.web.error.ForbiddenResource をそのまま使用しています。

nevow.static.File は putChild() というメソッドを使って複数のリソースをツリー状に構成できるようになっています。次の例ではデフォルトのコンテンツ用ディレクトリは /home/yasusii/www/html でファイル一覧表なし、 http://hostname/docs/ にアクセスしたときだけ /home/yasusii/documents の内容を出力、ファイル一覧も表示するようにしています。

# ファイル一覧表示の抑制とリソースの追加登録
# static2.py

from nevow import appserver, static
from twisted.internet import reactor
from twisted.web import error

DOCUMENT_ROOT = '/home/yasusii/www/html'
DOCUMENT_ROOT2 = '/home/yasusii/documents'
PORT = 8080

class FileWithoutListing(static.File):
    
ファイル一覧を表示しないリソース
    
def directoryListing(self):
        
return error.ForbiddenResource()

resource = FileWithoutListing(DOCUMENT_ROOT)
resource2 = static.File(DOCUMENT_ROOT2)
# resource の下に docs という名前で resource2 を配置
resource.putChild('docs', resource2)
site = appserver.NevowSite(resource)

reactor.listenTCP(PORT, site)
reactor.run()

デーモン(twisted アプリケーション)としての実行

static2.py の内容をデーモン用に書き変えると次のようになります。違っているのは import するモジュールと最後の3行だけです。

# サーバをデーモンとして実行する
# static2.tac

from nevow import appserver, static
from twisted.web import error
from twisted.application import service, internet

DOCUMENT_ROOT = '/home/yasusii/www/html'
DOCUMENT_ROOT2 = '/home/yasusii/documents'
PORT = 8080

class FileWithoutListing(static.File):
    
"""ファイル一覧を表示しないリソース"""
    
def directoryListing(self):
        
return error.ForbiddenResource()

resource = FileWithoutListing(DOCUMENT_ROOT)
resource2 = static.File(DOCUMENT_ROOT2)
# resource の下に docs という名前で resource2 を配置
resource.putChild('docs', resource2)

site = appserver.NevowSite(resource)

# デーモンとして実行する場合の記述
server = internet.TCPServer(PORT, site)
application = service.Application('my_httpd')
server.setServiceParent(application)

Twisted にはアプリケーションをデーモンとして動かすためのコマンド twistd があらかじめ用意されているため、デーモン用の細かな処理をあれこれ書く必要はありません。上記スクリプトをコマンド twistd の引数として渡すだけでデーモンのでき上がりです。twistd コマンド用のファイルは一般の Pythonスクリプトと区別するため .tac という拡張子を使うお約束になっています。

デーモンの起動は次のようにします。ps で見てみると TTY の欄が ? になっていることから、ちゃんとデーモンとして動作していることがわかります。

$ twistd -oy static2.tac
$ ps ax | grep twistd
14988 ?        S      0:00 /usr/bin/python /usr/local/bin/twistd -oy static2.tac

起動後にカレントディレクトリを見てみると twistd.log と twistd.pid という2つのファイルが作成されています。

$ ls twistd.*
twistd.log  twistd.pid

前者がサーバのログファイルです。起動メッセージやエラー内容がここに出力されるほか、サーバにアクセスすると、Apache と同じ形式でアクセスのログが出力されます。後者はデーモンのプロセス ID を記録したファイルで、こっちはサーバを終了させるときに使用します。

kill `cat twistd.pid`

コードの変更点を説明します。twisted.application.internet.TCPServer は最初に使った twisted.internet.reactor.listenTCP と引数が同じです。「同じように TCP ポートからの入力をイベントハンドラに関連付けてんだろ」というのはその通りなのですが、ここで返ってくるのはサービスタイプのオブジェクトです。

>>> list(zope.interface.providedBy(server))
[<MetaInterface twisted.application.service.IService>]

Twisted ではアプリケーションのサーバ機能をサービスという単位で管理するようになっていて、アプリケーションには複数のサービスを登録することもできます(今のところ登録しているサービスは1つだけですけど)。

twisted.application.service.Application() でアプリケーションのオブジェクトを作成、その後サービス側の setServiceParent() メソッドでアプリケーションへの登録を行なっています。アプリケーションオブジェクトの作成時、引数に指定している 'my_httpd' というのはアプリケーションの識別名で、出力するファイルの名前などに使われます。

なお tac ファイルでアプリケーションオブジェクトをセットする変数の名前は必ず application とする必要があります。twistd コマンドが直接この変数を参照するようになっているからです。app など別の名前にしてしまうと、Failed to load application: 'application' というエラーが出力されサーバは起動しません。

デバッグ時など tac ファイルをデーモン化せずに実行したいときは twistd コマンドに n オプションを指定します。

$ twistd -noy static2.tac

ちょっと Web サーバらしくなってきたかな、とか言うと、一般ユーザ権限では80番ポートが使えない、だからと言って80番を使うのに root で起動するのは気持ち悪い、一般ユーザ権限で80番を使うにはどうするんだとか、ログや PID ファイルのパスはどうやって指定するんだとか、アクセスログとエラーログが一緒なのはダサいとか、サーバ類はすべて chroot しないと心配で夜も眠れないとか、バーチャルホスト、バーチャルホストわぁ、とか、SSL できなきゃ実用品とは呼べないね、ふふんとか、Comet はいつになったら出てくんだよとか、ちょっと待ちなさい、順番に説明します。

nevow.appserver.NevowSite のオプション

プロトコルファクトリを生成する nevow.appserver.NevowSite には次のオプション引数が指定できます。

logPath

アクセスログのパス名(デフォルトは None)。None の場合、カレントディレクトリに twistd.log という名前でログが作成されます。

timeout

HTTP 接続のタイムアウト時間(秒 - デフォルトは43200秒)。

twisted.application.internet.TCPServer のオプション

サービスオブジェクトを生成する twisted.application.internet.TCPServer には次のオプション引数が指定できます。これらは twisted.internet.reactor.listenTCP のオプションにも指定できます。

backlog

バックログ(接続要求キュー)の最大値(デフォルト50)。

interface

サーバがリッスンする IP インターフェース(デフォルトは "")。"" の場合、すべてのインターフェースをリッスンします。

twisted.application.service.Application のオプション

アプリケーションオブジェクトを生成する twisted.application.service.Application には次のオプションが指定できます(Unix 系 OS のみ)。

uid

サーバを実行するときのユーザ ID を数値で指定します(デフォルトは None)。このオプションを使うにはスクリプトを root の権限で実行する必要があります。None の場合、スクリプトを実行したユーザの権限で起動されます。

gid

サーバを実行するときのグループ ID を数値で指定します(デフォルトは None)。このオプションを使うにはスクリプトを root の権限で実行する必要があります。None の場合、スクリプトを実行したユーザが属するグループの権限で起動されます。

twistd コマンドのオプション

以下の内容は twistd コマンドのオプションで指定します。ユーザ ID、グループ ID の指定は

-o 終了時に状態を保存しない(詳細は後述)。

-n デーモン化しない。

-l エラーログ出力先のパス名指定。

-f アプリケーションの情報を .tap ファイルから読み込む(詳細は後述)。

-y アプリケーションの情報を Python スクリプトから読み込む。

--pidfile= PID ファイルのパス名指定。

--chroot= アプリケーションの起動前に指定したディレクトリに chroot する。

-u アプリケーション実行時に使用するユーザ ID。

-g アプリケーション実行時に使用するグループ ID。

一般的なスクリプトの例

次のような条件で動かす場合のスクリプト例を見てみましょう。よくあるパターンです。CGI 用のディレクトリも追加しましょう。

  • ポートは80番を使用

  • ユーザ ID 33、グループ ID 33 の権限で起動

  • ドキュメントのルートディレクトリは /var/www/html

  • CGI 用のディレクトリは /var/www/cgi-bin

  • HTTP タイムアウトは300秒

  • アクセスログは /var/log/www/access.log

  • エラーログは /var/log/www/error.log

  • PID ファイルは /var/run/twistd.pid

/var/www/html にはユーザ ID 33 で read できるようパーミッションを設定しておきます。ログや PID ファイルのディレクトリは root にだけ読み書き許可があれば OK です。

# よくあるパターンの設定例
# simple.tac

from nevow import appserver, static
from twisted.web import error, twcgi
from twisted.application import service, internet

DOCUMENT_ROOT = '/var/www/html'
CGI_DIR = '/var/www/cgi-bin' # CGI 用のディレクトリ
PORT = 80
LOGPATH = '/var/www/log/access.log'
TIMEOUT = 300
UID = 33
GID = 33

class FileWithoutListing(static.File):
    
"""ファイル一覧を表示しないリソース"""
    
def directoryListing(self):
        
return error.ForbiddenResource()

resource = FileWithoutListing(DOCUMENT_ROOT)
cgiResource = twcgi.CGIDirectory(CGI_DIR) # CGI 用リソース
resource.putChild('cgi-bin', cgiResource)
site = appserver.NevowSite(resource, logPath=LOGPATH, timeout=TIMEOUT)
server = internet.TCPServer(PORT, site)
application = service.Application('my_httpd', uid=UID, gid=GID)
server.setServiceParent(application)

twisted.web.twcgi.CGIDirectory の引数には CGI を置くディレクトリを指定します。そのディレクトリの下に置かれたファイルが CGI スクリプトとして実行可能になり、上記例では http://hostname/cgi-bin/scriptname でアクセスできるようにしています。

エラーログと PID ファイルのパス名は twistd コマンドのオプションで指定します。

$ sudo twistd -l /var/log/www/error.log --pidfile=/var/run/twistd.pid \
  -oy simple.tac

Python と Twisted できみにも書ける Web サーバ(2) へつづく

最終更新 2006-11-27 17:17:04

11月 2006
   1 2 3 4 5
6 7 8 9101112
13141516171819
20212223242526
27282930   
10月
2006
 12月
2006

Python と Twisted、それに Nevow を使った Web サーバの書き方

XML-Image Letterimage

© 2006-2010, Yasushi Iwata