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

この記事は[前回](https://qiita.com/nyancook/items/7c00037c225986279eb1)の続きになっていますが、単独でも問題なく読めるはずです。
まあ、自分用の整理のための記事ですが...

# 本丸のBottleクラス

いよいよ天守閣の`Bottle`クラス。
まずは前回からの流れで、`__call__`メソッドから。

```bottle.py

class Bottle(object):

    def __call__(self, environ, start_response):
        """ Each instance of :class:'Bottle' is a WSGI application. """
        return self.wsgi(environ, start_response)

    def wsgi(self, environ, start_response):
        """ The bottle WSGI-interface. """
        try:
            out = self._cast(self._handle(environ))
            # rfc2616 section 4.3
            if response._status_code in (100, 101, 204, 304) or environ['REQUEST_METHOD'] == 'HEAD':
                if hasattr(out, 'close'): out.close()
                out = []
            start_response(response._status_line, response.headerlist)
            return out
```

つまりレスポンスのメッセージbody部分は`self._cast(self._handle(environ))`から生成されてるってことね。
`self.cast()`はデータをHTTPのメッセージとして適した形式にエンコードする処理だから、実質的には`self._handle(environ)`でレスポンス内容が作られてる。

これが少し長い。
なのでexceptブロックを除いてスッキリさせてみる。

```bottle.py

#: A thread-safe instance of :class:`LocalResponse`. It is used to change the
#: HTTP response for the *current* request.
response = LocalResponse()

class Bottle(object):

    def _handle(self, environ):
        path = environ['bottle.raw_path'] = environ['PATH_INFO']
        if py3k:
            environ['PATH_INFO'] = path.encode('latin1').decode('utf8', 'ignore')

        environ['bottle.app'] = self
        request.bind(environ)
        response.bind()

        try:
            while True: # Remove in 0.14 together with RouteReset
                out = None
                try:
                    self.trigger_hook('before_request')
                    route, args = self.router.match(environ)
                    environ['route.handle'] = route
                    environ['bottle.route'] = route
                    environ['route.url_args'] = args
                    out = route.call(**args)
                    break
                
                finally:
                    if isinstance(out, HTTPResponse):
                        out.apply(response)
                    try:
                        self.trigger_hook('after_request')

        return out

```

ここでいきなり登場した`response = LocalResponse()`だが、`LocalResponse`クラスの親クラス`BaseResponse`は以下で定義されている。

```bottle.py
class BaseResponse(object):
    """ Storage class for a response body as well as headers and cookies.

        This class does support dict-like case-insensitive item-access to
        headers, but is NOT a dict. Most notably, iterating over a response
        yields parts of the body and not the headers. # 以下のコメントは略
    """

    default_status = 200
    default_content_type = 'text/html; charset=UTF-8'

    def __init__(self, body='', status=None, headers=None, **more_headers):
        self._cookies = None
        self._headers = {}
        self.body = body
```

クッキー、ヘッダー、ボディーなんかの情報を保持してくれるらしい。
今はこれ以上深追いしないでおく。

さて、`Bottle()._handler()`は

```python
route, args = self.router.match(environ)
out = route.call(**args)
return out
```

って部分がキモで、ここでルーティングの処理が行われているみたいですね。
ここで`self.router`とは`Router() `のこと（`Bottle.__init__`で定義されている）なので、このクラスをみてみましょう。

```bottle.py

class Router(object):
    """ A Router is an ordered collection of route->target pairs. It is used to
        efficiently match WSGI requests against a number of routes and return
        the first target that satisfies the request. The target may be anything,
        usually a string, ID or callable object. A route consists of a path-rule
        and a HTTP method.

        The path-rule is either a static path (e.g. `/contact`) or a dynamic
        path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
        and details on the matching order are described in docs:`routing`.
    """

    def __init__(self, strict=False):
        self.rules = []  # All rules in order
        self._groups = {}  # index of regexes to find them in dyna_routes
        self.builder = {}  # Data structure for the url builder
        self.static = {}  # Search structure for static routes
        self.dyna_routes = {}
        self.dyna_regexes = {}  # Search structure for dynamic routes
        #: If true, static routes are no longer checked first.
        self.strict_order = strict
        self.filters = {
            're': lambda conf: (_re_flatten(conf or self.default_pattern),
                                None, None),
            'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
            'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
            'path': lambda conf: (r'.+?', None, None)
        }

    def match(self, environ):
        """ Return a (target, url_args) tuple or raise HTTPError(400/404/405). """
        verb = environ['REQUEST_METHOD'].upper()
        path = environ['PATH_INFO'] or '/'

        if verb == 'HEAD':
            methods = ['PROXY', verb, 'GET', 'ANY']
        else:
            methods = ['PROXY', verb, 'ANY']

        for method in methods:
            if method in self.static and path in self.static[method]:
                target, getargs = self.static[method][path]
                return target, getargs(path) if getargs else {}
            elif method in self.dyna_regexes:
                for combined, rules in self.dyna_regexes[method]:
                    match = combined(path)
                    if match:
                        target, getargs = rules[match.lastindex - 1]
                        return target, getargs(path) if getargs else {}
```

ここら辺、少しごちゃごちゃで結局`return target, getargs(path)`の`target`って何かわかりづらいですが、`route, args = self.router.match(environ)`を見て察しがつく通り、`Route`インスタンスが返されるわけですね。

つまりいつの間にか、`self.static[method][path]`として`Route()`が登録されているわけです。

<b>いつの間に！？</b>

以下のソースをみると、全体像がだんだん見えてきます！

```bottle.py

class Bottle(object):

    def route(self,
              path=None,
              method='GET',
              callback=None,
              name=None,
              apply=None,
              skip=None, **config):
        """ A decorator to bind a function to a request URL. Example::

                @app.route('/hello/<name>')
                def hello(name):
                    return 'Hello %s' % name

            The ``<name>`` part is a wildcard. See :class:`Router` for syntax
            details.
        """
        if callable(path): path, callback = None, path
        plugins = makelist(apply)
        skiplist = makelist(skip)

        def decorator(callback):
            if isinstance(callback, basestring): callback = load(callback)
            for rule in makelist(path) or yieldroutes(callback):
                for verb in makelist(method):
                    verb = verb.upper()
                    route = Route(self, rule, verb, callback, name=name, plugins=plugins, skiplist=skiplist, **config)
                    self.add_route(route)
            return callback

        return decorator(callback) if callback else decorator


    def add_route(self, route):
        """ Add a route object, but do not change the :data:`Route.app`
            attribute."""
        self.routes.append(route)
        self.router.add(route.rule, route.method, route, name=route.name)
        if DEBUG: route.prepare()

```

つまり我々が

```python

@app.route('/hello/<name>')
    def hello(name):
        return 'Hello %s' % name
```
とデコレータをつけることによって、リクエストが飛んできた際に

```python

route = Route(self, rule, verb, callback, name=name, plugins=plugins, skiplist=skiplist, **config)
self.add_route(route)
```

と`Router`インスタンスに`Route`インスタンスの情報を与えていたわけですね。

なんか頭がこんがらがってきた...


# レスポンスデータ

以上からわかったことは、

```python
route, args = self.router.match(environ)
out = route.call(**args)
return out  # レスポンスデータが含まれている！
```

における`route`は`Route`インスタンスであり（当たり前っぽいけど）、`Route().call()`にお望みのレスポンスデータが入っているってこと！

そこで`Route`クラスの定義を見てみると

```bottle.py

class Route(object):
    """ This class wraps a route callback along with route specific metadata and
        configuration and applies Plugins on demand. It is also responsible for
        turing an URL path rule into a regular expression usable by the Router.
    """

    def __init__(self, app, rule, method, callback,
                 name=None,
                 plugins=None,
                 skiplist=None, **config):
        #: The application this route is installed to.
        self.app = app
        self.callback = callback
        #: A list of route-specific plugins (see :meth:`Bottle.route`).
        self.plugins = plugins or []

    @cached_property
    def call(self):
        """ The route callback with all plugins applied. This property is
            created on demand and then cached to speed up subsequent requests."""
        return self._make_callback()

    def _make_callback(self):
        callback = self.callback
        for plugin in self.all_plugins():
            try:
                if hasattr(plugin, 'apply'):
                    callback = plugin.apply(callback, self)
                else:
                    callback = plugin(callback)
            except RouteReset:  # Try again with changed configuration.
                return self._make_callback()
            if not callback is self.callback:
                update_wrapper(callback, self.callback)
        return callback
```

つまりまあ、プラグインを挟み込んでいて、大事な部分は`self.callback`なわけだけど、そもそもこいつは`Bottle.route`の中の

```bottle.py

        def decorator(callback):
            if isinstance(callback, basestring): callback = load(callback)
            for rule in makelist(path) or yieldroutes(callback):
                for verb in makelist(method):
                    verb = verb.upper()
                    route = Route(self, rule, verb, callback, name=name, plugins=plugins, skiplist=skiplist, **config)
                    self.add_route(route)
            return callback

        return decorator(callback) if callback else decorator
```

で出てきた`callback`が渡されただけだよね。
これは

```python
@app.route('/hello/<name>')
    def hello(name):
        return 'Hello %s' % name
```

における`hello`関数に当たるわけで、これもまた至極当然だけど、ここで自分が定義した関数に従ってレスポンスデータが生成されるとな。

ちゃんちゃん。


# 誰か助けて

ってことで、ようやく全体像が見えてきました（ロギングとかエラー処理の部分は無視してきたけど）。

しかし疑問なのは、

```python
from bottle import route
```

でどうして`route`デコレータが読み込まれるのか、さっぱり理解できていません。
`Route`クラス内で定義されているデコレータ関数が、いつの間にか`route`という名前で環境登録されている、という感覚です。

もし理解している方がいらっしゃったら教えていただきたいです...泣