[Python][Twisted] twistd
環境
この記事の内容は、Ubuntu Linux 6.10, Python 2.4.4c1, Twisted 2.4.0で確認しました。
はじめに
id:SumiTomohiko:20070712:1184244636でTwistedのリアクタの話をした際、yasusiiさん (http://lowlife.jp/yasusii/) から、
どのリアクタを使うかハードコーディングせずに、実行時に切り替えられるようにするため、若干トリッキーな仕組みになっています。Twisted アプリケーションの場合、twistd コマンドの -r オプションでリアクタを変更できます。twisted/application/reactors.pyあたりを読んでみてください。
というコメントをいただきました。そこで、twistdを始め、リアクタについてもう少し調べてみました。
まとめ
- twistdは、サーバの動作のうち、デーモン化やリアクターの設定といった、どのサーバでも同じ機能を受け持つプログラムです。
- twistdで動かすサーバを作るには、決まりごとを守る必要があります。
- twistdの-rオプションでリアクタの種類を選択できます。
- リアクタのクラスがあるモジュールのinstall関数を呼び出すと、twisted.internet.reactorモジュールが、選択したリアクタのオブジェクトに置き換わります。
twistd
Twistedでデーモンを書くと、アプリケーションをデーモン化するコードとか、リアクタを動かすコードとか、同じようなコードをアプリケーションを書く度に何度も記述することになります。twistdは、その同じコードを引き受けて、開発者はデーモンの中核のみを作成すればよいようにするツールです。
twistdで動作するサーバを記述する際、守らなければならない決まりごとがあります。それは、以下の2点です。
- twisted.application.service.Application関数(クラスのように見える名前ですが、これは関数です)の戻り値を、"application"という名前の変数に代入します。
- サーバの親に、上のapplication変数を設定します。
実際に、twistdで動作するサーバのコードを、以下に記します。このサーバは、ユーザからのリクエストを表示するだけのウェブサーバで、
Twisted Network Programming Essentials
- 作者: Abe Fettig
- 出版社/メーカー: Oreilly & Associates Inc
- 発売日: 2005/10
- メディア: ペーパーバック
- 購入: 1人 クリック: 24回
- この商品を含むブログ (8件) を見る
に掲載されているコードを元にして作成しました。このコードのうち、一番下の3行が、twistdのためのコードです。
# -*- coding: utf-8 -*- from twisted.application import internet, service from twisted.internet import protocol, reactor from twisted.protocols import basic class HttpEchoProtocol(basic.LineReceiver): def __init__(self): self.lines = [] self.gotRequest = False def lineReceived(self, line): self.lines.append(line) if not line and not self.gotRequest: self.sendResponse() self.gotRequest = True def sendResponse(self): responseBody = "You said:\r\n\r\n" + "\r\n".join(self.lines) self.sendLine("HTTP/1.0 200 OK") self.sendLine("Content-Type: text/plain") self.sendLine("Content-Length: %i" % (len(responseBody))) self.sendLine("") self.transport.write(responseBody) self.transport.loseConnection() f = protocol.ServerFactory() f.protocol = HttpEchoProtocol # リアクターを直接動かす場合 #reactor.listenTCP(8000, f) #reactor.run() server = internet.TCPServer(8000, f) application = service.Application("my_httpd") server.setServiceParent(application)
このコードをwebecho.pyとして保存し、
$ twistd -y webecho.py
と実行すると、サーバがデーモンとして動作します。
リアクタ
twistdのヘルプを読むと、以下の記述があります。
-r, --reactor= Which reactor to use out of: kqueue, poll, qt, default, win, cfreactor, gtk, cf, glade, win32, gtk2, iocp, glib2, threadedselect, wx.
すなわち、-rオプションでリアクタの種類を設定できるとのことです。この、リアクタを設定するまでの流れを追ってみたいと思います。
まず、twistdのソースコードを読むと、最後が以下のようになっています。
from twisted.python.runtime import platformType if platformType == "win32": from twisted.scripts._twistw import run else: from twisted.scripts.twistd import run run()
そこで、/usr/lib/python2.4/site-packages/twisted/scripts/twistd.pyのrun関数を読んでみます。
def run(): app.run(runApp, ServerOptions)
ここで、appは、
from twisted.application import app, service
でインポートされています。また、runAppは、以下の関数です。
def runApp(config): checkPID(config['pidfile']) passphrase = app.getPassphrase(config['encrypted']) app.installReactor(config['reactor']) config['nodaemon'] = config['nodaemon'] or config['debug'] oldstdout = sys.stdout oldstderr = sys.stderr startLogging(config['logfile'], config['syslog'], config['prefix'], config['nodaemon']) app.initialLog() application = app.getApplication(config, passphrase) startApplication(config, application) app.runReactorWithLogging(config, oldstdout, oldstderr) removePID(config['pidfile']) app.reportProfile(config['report-profile'], service.IProcess(application).processName) log.msg("Server Shut Down.")
なんとなく、これがtwistdの中核であり、あとからコールバックされるような感じがします。ServerOptionsはクラスで、名前から判断するにコマンドラインなどからのオプションを保持する役割を持っていそうです。runApp関数がコールバックされる際、引数として与えられそうです。
次に、/usr/lib/python2.4/site-packages/twisted/application/app.pyを読んでみます。twisted.application.app.run関数は、以下のようになっていました。
def run(runApp, ServerOptions): config = ServerOptions() try: config.parseOptions() except usage.error, ue: print config print "%s: %s" % (sys.argv[0], ue) else: runApp(config)
案の定、twisted.scripts.twistd.ServerOptionsオブジェクトを引数にして、twisted.scripts.twistd.runApp関数をコールバックしています。
config.parseOptions()
という箇所では、コマンドライン引数のパースをやっていそうですが、今回の目的からは外れるため、無視することにします。そうすると、話は先ほどのtwisted.scripts.twistd.runApp関数に戻ります。
twisted.scripts.twistd.runApp関数の中で、リアクターに関する行として、
app.installReactor(config['reactor'])
があります。何やらリアクターを設定していそうな名前です。config['reactor']の中身が何かは別として、twisted.application.app.installReactor関数を読んでみます。inistallReactor関数は、以下の辞書とともに見つかりました。
reactorTypes = { 'wx': 'twisted.internet.wxreactor', 'gtk': 'twisted.internet.gtkreactor', 'gtk2': 'twisted.internet.gtk2reactor', 'glib2': 'twisted.internet.glib2reactor', 'glade': 'twisted.manhole.gladereactor', 'default': 'twisted.internet.selectreactor', 'win32': 'twisted.internet.win32eventreactor', 'win': 'twisted.internet.win32eventreactor', 'poll': 'twisted.internet.pollreactor', 'qt': 'twisted.internet.qtreactor', 'cf' : 'twisted.internet.cfreactor', 'kqueue': 'twisted.internet.kqreactor', 'iocp': 'twisted.internet.iocpreactor', 'cfreactor': 'twisted.internet.cfreactor', 'threadedselect': 'twisted.internet.threadedselectreactor', } def installReactor(reactor): if reactor: reflect.namedModule(reactorTypes[reactor]).install()
reactorTypesという辞書は、与えられた名前に対して、リアクタのモジュールを対応させるもののようです。reflectは、
from twisted.python import runtime, log, usage, reflect, failure, util, logfile
としてインポートされているモジュールで、twisted.python.reflect.namedModule関数は、以下の通りです。
def namedModule(name): """Return a module given its name.""" topLevel = __import__(name) packages = name.split(".")[1:] m = topLevel for p in packages: m = getattr(m, p) return m
これは、Pythonのドキュメント (http://www.python.jp/doc/2.4/lib/built-in-funcs.html) にも記載されているコードで、与えられた名前のモジュールを返すという関数です。よって、上のtwisted.application.app.installReactor関数というのは、指定されたモジュールのinstall関数を呼び出す、ということになります。
ここで例として、twisted.internet.threadedselectreactorモジュールのinstall関数を見てみます。
def install(): """Configure the twisted mainloop to be run using the select() reactor. """ reactor = ThreadedSelectReactor() from twisted.internet.main import installReactor installReactor(reactor) return reactor
この中では、twisted.internet.threadedselectreactor.ThreadedSelectReactorオブジェクトを、twisted.internet.main.installReactor関数に与えています。ここまでくると、話はid:SumiTomohiko:20070712:1184244636のときと同じです。生成したオブジェクトで、twisted.internet.reactorモジュールを置き換えているわけです。
def installReactor(reactor): # this stuff should be common to all reactors. import twisted.internet import sys assert not sys.modules.has_key('twisted.internet.reactor'), \ "reactor already installed" twisted.internet.reactor = reactor sys.modules['twisted.internet.reactor'] = reactor
参考文献
- Python と Twisted できみにも書ける Web サーバ(1) (http://lowlife.jp/yasusii/stories/24.html)