bottle.pyを読む
Pythonのwebフレームワーク、Bottleのコードリーディングをしてみました。
Bottleのソースコードはbottle.pyに集約されているので、このファイルを読むことになります。
GitHubはこちら。
https://github.com/bottlepy/bottle
以下で引用するコードは、bottle.pyから必要な部分のみを引用したものになります。
また、ところどころ勝手にコメントを付け加えています。
最初から細かい部分まで追うと大変なので、まずは全体の流れを掴みたいと思います。
最初の入り口: run()
起動から順番にソースを追っていきたいと思います。
Bottleの起動は、例えばドキュメントによると以下のようにする。
from bottle import route, run, template
@route('/hello/<name>')
def index(name):
return template('<b>Hello {{name}}</b>!', name=name)
run(host='localhost', port=8080)
run()
からみていきましょう。
念の為もう一度言いますが、ソースコードは(大部分を)端折って引用しています。
def run(app=None,
server='wsgiref',
host='127.0.0.1',
port=8080,
interval=1,
reloader=False,
quiet=False,
plugins=None,
debug=None,
config=None, **kargs):
app = app or default_app()
if server in server_names:
server = server_names.get(server)
if isinstance(server, basestring):
server = load(server)
if isinstance(server, type):
server = server(host=host, port=port, **kargs)
if not isinstance(server, ServerAdapter):
raise ValueError("Unknown or unsupported server: %r" % server)
if reloader:
lockfile = os.environ.get('BOTTLE_LOCKFILE')
bgcheck = FileCheckerThread(lockfile, interval)
with bgcheck:
server.run(app)
if bgcheck.status == 'reload':
sys.exit(3)
else:
server.run(app)
要するに
app = app or default_app() # デフォルトだと app=None
server = server_names.get(server) # デフォルトだと server='wsgiref'
server.run(app)
ってことですね。
まずは一行目のdefault_app()
の定義をみてみます。
AppStackクラスってなんだ?
定義箇所は
apps = app = default_app = AppStack()
で、
class AppStack(list):
""" A stack-like list. Calling it returns the head of the stack. """
def __call__(self):
""" Return the current default application. """
return self.default
def push(self, value=None):
""" Add a new :class:`Bottle` instance to the stack """
if not isinstance(value, Bottle):
value = Bottle()
self.append(value)
return value
new_app = push
@property
def default(self):
try:
return self[-1]
except IndexError:
return self.push()
ほほう。AppStackはlistを継承していると。
AppStack()
を使う側(server
)からして大事なのは__call__
メソッドでしょう。
callメソッドの復習がてらまとめると、
app = AppStack()
app()
と呼び出すと、(デフォルト引数なら)Bottle
インスタンスが返ってくるらしい。
と同時に、そいつをlistとして蓄積していく仕組みなんですね。
Bottle
クラスのソースをまとめたいところですが、名前通りこいつがBottleの中核らしく、長い。
そもそもAppStack
インスタンスは
server.run(app)
でserverのrunメソッドに引数として渡されており、その先でapp()
とcallされるはずなので、先にserver
について調べてみよう。
Serverクラス
少し振り返ると、
app = app or default_app() # デフォルトだと app=None
server = server_names.get(server) # デフォルトだと server='wsgiref'
server.run(app)
の部分で定義されるserver
が何者なのか調べようという話。
server_names = {
'cgi': CGIServer,
'flup': FlupFCGIServer,
'wsgiref': WSGIRefServer, # デフォルトだとこいつが使われる
'waitress': WaitressServer,
# 以下略
}
と辞書になっており、各valueはServerAdapter
クラスを継承している。こいつは「形だけの親」的な存在なので、いきなりWSGIRefServer
クラスにいく。
class WSGIRefServer(ServerAdapter):
def run(self, app): # pragma: no cover
from wsgiref.simple_server import make_server
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
import socket
server_cls = self.options.get('server_class', WSGIServer)
self.srv = make_server(self.host, self.port, app, server_cls,
handler_cls)
self.port = self.srv.server_port # update port actual port (0 means random)
try:
self.srv.serve_forever()
except KeyboardInterrupt:
self.srv.server_close() # Prevent ResourceWarning: unclosed socket
raise
server.run()
に渡されたapp = AppStack()
はmake_server
関数に渡される。
make_server
関数自体はpythonの標準ライブラリwsgiref
のもの。
Web Server Gateway Interface (WSGI) は、Web サーバソフトウェアと Python で記述された Web アプリケーションとの標準インターフェースです。標準インターフェースを持つことで、WSGI をサポートするアプリケーションを幾つもの異なる Web サーバで使うことが容易になります。
ってことで、wsgiref
のドキュメントにあるサンプルをみてみる。
from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server
def simple_app(environ, start_response):
setup_testing_defaults(environ)
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
ret = ["%s: %s\n" % (key, value)
for key, value in environ.iteritems()]
return ret
httpd = make_server('', 8000, simple_app)
print "Serving on port 8000..."
httpd.serve_forever()
・make_server
関数に第三引数として、自分で定義した関数(サンプルだとsimple_app
関数)を渡してね!
・するとリクエストが飛んできたときに、その関数に処理させることができるよ!
・ただし2つの引数(environ, start_response)
を用意してね!
ってことですね。
そんでもって、これまでのソースを振り返ってみると、その関数(サンプルにおけるsimple_app
)とはBottle()
のことでしたね。
関数じゃなくてインスタンスじゃん!
でも大丈夫!
__call__
インスタンスを用意しておけば、関数と同じように呼ばれることができるから!
くどいようですが整理すると、サンプルで
simple_app(environ, start_response)
と実行される行為は、
Bottle()(environ, start_response)
が実行されることに等しいってことよね。
つまり
当たり前のようだけど、Bottleインスタンスが実質的な処理を担う
ってことがわかった。
実際
class Bottle(object):
def __call__(self, environ, start_response):
が定義されている。
ここまで長かった。
Bottle
クラスは次回にします。
ここまでのまとめ
wsgiref
ライブラリのmake_server
関数の引数としてBottle()
が渡され、こいつが実質的な処理を担う。