Help us understand the problem. What is going on with this article?

【Python】 Bottleのソースコードを読んでみる その1

More than 1 year has passed since last update.

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()からみていきましょう。
念の為もう一度言いますが、ソースコードは(大部分を)端折って引用しています。

bottle.py
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()

で、

bottle.py
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が何者なのか調べようという話。

bottle.py
server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,  # デフォルトだとこいつが使われる
    'waitress': WaitressServer,
    # 以下略
}

と辞書になっており、各valueはServerAdapterクラスを継承している。こいつは「形だけの親」的な存在なので、いきなりWSGIRefServerクラスにいく。

bottle.py
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のもの。

https://docs.python.org/ja/3/library/wsgiref.html 

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インスタンスが実質的な処理を担う

ってことがわかった。

実際

bottle.py
class Bottle(object):

    def __call__(self, environ, start_response):

が定義されている。

ここまで長かった。

Bottleクラスは次回にします。

ここまでのまとめ

wsgirefライブラリのmake_server関数の引数としてBottle()が渡され、こいつが実質的な処理を担う。

nyancook
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away