【Python】Flaskのソースコードを読んでみる

以前[Bottleのソースコードを概観してみた](https://qiita.com/nyancook/items/7c00037c225986279eb1)ので、今回はFlaskを見てみたいと思います。


# まずは起動から

Flaskのチュートリアルによると、最小限のアプリケーションはこんな感じで作成できるとな。

```python
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return "Hello World!"

if __name__ == '__main__':
    app.run()
```

Bottleとほとんど同じなわけです。
`run`メソッドの定義はこんな感じ。

```src/flask/app.py
class Flask(_PackageBoundObject):

    def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):

        if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
            from .debughelpers import explain_ignored_app_run

            explain_ignored_app_run()
            return

        if get_load_dotenv(load_dotenv):
            cli.load_dotenv()

            # if set, let env vars override previous values
            if "FLASK_ENV" in os.environ:
                self.env = get_env()
                self.debug = get_debug_flag()
            elif "FLASK_DEBUG" in os.environ:
                self.debug = get_debug_flag()

        # debug passed to method overrides all other sources
        if debug is not None:
            self.debug = bool(debug)

        _host = "127.0.0.1"
        _port = 5000
        server_name = self.config.get("SERVER_NAME")
        sn_host, sn_port = None, None

        if server_name:
            sn_host, _, sn_port = server_name.partition(":")

        host = host or sn_host or _host
        # pick the first value that's not None (0 is allowed)
        port = int(next((p for p in (port, sn_port) if p is not None), _port))

        options.setdefault("use_reloader", self.debug)
        options.setdefault("use_debugger", self.debug)
        options.setdefault("threaded", True)

        cli.show_server_banner(self.env, self.debug, self.name, False)

        from werkzeug.serving import run_simple

        try:
            run_simple(host, port, self, **options)
        finally:
            # reset the first request information if the development server
            # reset normally.  This makes it possible to restart the server
            # without reloader and that stuff from an interactive shell.
            self._got_first_request = False
```

Bottleの場合は`wsgiref`ライブラリを使っていましたね。
`wsgiref.simple_server.make_server()`でサーバーを立ち上げて、レスポンスを作成するときは`start_response()`のような生臭い処理が実装されていました。

対してFlaskでは`werkzeug`っていう便利なライブラリを使っていますね。
[ドキュメント](https://github.com/pallets/werkzeug)によると、サンプルはこんな感じ。

```python

from werkzeug.wrappers import Request, Response

@Request.application
def application(request):
    return Response('Hello, World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)
```

`return Response('Hello, World!')`のように楽にレスポンスが返せるようになっている。

`run_simple(host, port, self, **options)`で本丸の`Flask`クラスがアプリケーション関数として登録される点はBottleと同じ。

ってことは、レスポンス内容は`Flask()()`な形で`__call__`メソッドを呼び出すことで得られるはずだよね。

# 次にデコレータの定義

```src/flask/app.py

class Flask(_PackageBoundObject):

    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  This does the same thing as :meth:`add_url_rule`
        but is intended for decorator usage::

            @app.route('/')
            def index():
                return 'Hello World'

        For more information refer to :ref:`url-route-registrations`.
　　　　　（以下略）
        """

        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator


    @setupmethod
    def add_url_rule(
        self,
        rule,
        endpoint=None,
        view_func=None,
        provide_automatic_options=None,
        **options
    ):
        """
        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        （以下略）
        """
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)
        if isinstance(methods, string_types):
            raise TypeError(
                "Allowed methods have to be iterables of strings, "
                'for example: @app.route(..., methods=["POST"])'
            )
        methods = set(item.upper() for item in methods)

        # Methods that should always be added
        required_methods = set(getattr(view_func, "required_methods", ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(
                view_func, "provide_automatic_options", None
            )

        if provide_automatic_options is None:
            if "OPTIONS" not in methods:
                provide_automatic_options = True
                required_methods.add("OPTIONS")
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError(
                    "View function mapping is overwriting an "
                    "existing endpoint function: %s" % endpoint
                )
            self.view_functions[endpoint] = view_func

```

結局は

```python
self.view_functions[endpoint] = view_func
```
の部分で、ユーザが定義したview関数を辞書として登録しているわけね。
基本はBottleと同じですね。


# リクエストが来たら

`Flask`インスタンスの`__call__`メソッドからレスポンス内容を取得するであろう、という話でした。

```src/flask/app.py

class Flask(_PackageBoundObject):

    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. Instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.
        """
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.
        """
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
```

`request_started.send(self)`の部分も気になる。
これによって、プロセス管理をしているのかな。
でもこれ一旦置いておく。

で、今追いかけたいのは

```python
rv = self.preprocess_request()
if rv is None:
    rv = self.dispatch_request()
```
の部分で、基本的には`preprocess_request()`か`self.dispatch_request()`からデータを受け取っている。
`rv`って`response body`の略かしら？
なのでこの２つのメソッドをみてみる。


### １、 process_response()

```src/flask/app.py

class Flask(_PackageBoundObject):

    def process_response(self, response):
        """Can be overridden in order to modify the response object
        before it's sent to the WSGI server.  By default this will
        call all the :meth:`after_request` decorated functions.
        """
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response
```

`ctx`って"context"のことだよね？
contextに登録されているハンドラー達に処理を実行させている。
じゃあ`ctx`はいつ`_request_ctx_stack`に積み上げられた？

実は素通りしましたが、先ほど見た`Flask`クラスの`wsgi_app()`メソッドの中でcontextが作成・プッシュされていました（見返してみてください）。

ちなみに`_request_ctx_stack`は

```src/flask/globals.py
_request_ctx_stack = LocalStack()
```
で作成されたもの。プッシュは以下で定義されている。

```src/flask/ctx.py
class RequestContext(object):

    def push(self):
        """Binds the request context to the current context."""
        
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        # Before we push the request context we have to ensure that there
        # is an application context.
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, "exc_clear"):
            sys.exc_clear()

        _request_ctx_stack.push(self)
```


### ２、 dispatch_request()

```src/flask/app.py

class Flask(_PackageBoundObject):

    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.
        """
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if (
            getattr(rule, "provide_automatic_options", False)
            and req.method == "OPTIONS"
        ):
            return self.make_default_options_response()
        # otherwise dispatch to the handler for that endpoint
        return self.view_functions[rule.endpoint](**req.view_args)
```

続いてこちらでは`self.view_functions[rule.endpoint]`で取得した関数を実行しているわけですが、ここに登録されるのはいつなのか？

２つの場面を見つけました。

１つめは、最初の方で見た`Flask.add_url_rule()`メソッドの最後の方（これも見返してみてね）。

２つめは、

```python

class Flask():

    @setupmethod
    def endpoint(self, endpoint):
        def decorator(f):
            self.view_functions[endpoint] = f
            return f
        return decorator
```

の部分。

でもこれらは、例えば`add_url_rule()`した時にendpointがどうこうしてたから、利用場面は別ではないというか、うーん。
疲れてきたから確認するのが面倒くなってきた。

まあでも、`add_url_rule()`とかをみた感じ、結局`dispatch_request()`でユーザが定義したendpoint関数が実行されてるんよね。

じゃあ`process_response()`の方はプラグインとか周辺の処理になるのかな？


# 最後に

疲れたので今回はこれくらいで。