Skip to content

06 ミドルウェアとデコレータ

Yoshiya edited this page Apr 1, 2016 · 1 revision

ここではミドルウェアの概念を説明します。 WEB アプリケーションにおけるミドルウェアとは、リクエストを受けてからレスポンスを返すまでの事前処理、 事後処理を行うものです。例えば、レスポンスを返す前にレスポンスデータをログに残す、すべてのリクエストに対して必ず session 情報を保持しているか どうかのバリデーション、不正な連続リクエストの拒否処理などがこれらに当たります。

Flask におけるミドルウェアの実装方法には、関数デコレータベースのルーティングに対応するもの、 MethodView に対応するもの、 WSGI のレイヤーで 一括して行うもの、 Flask-restful を用いたものなど、影響範囲、実装法は多岐に渡ります。以下にコードベースで実装方法を紹介します。

リクエストレベルのミドルウェア

リクエストレベルでのミドルウェアは、 関数デコレータによるルーティング、 MethodView によるルーティング、 Flask-restful によるルーティング、全てにおいて、実装方針が同じです。基本的には、ミドルウェアとなる関数デコレータを作成し、ルーティングのメソッドの直上で宣言するだけです。このやり方は、粒度が細かいので、詳細な操作が可能です。サンプルコードには、 MethodView を用いた方法のみ紹介します。 個々のミドルウェアに対してデコレートしても問題なく動作しますが、 decorators クラス変数に設定することでコードが見やすくなります。

#__init__.py

from flask import Flask
from flask.views import MethodView
import functools

def middleware(func):
    @functools.wraps
    def wrap(*args, **kwargs):
        print('call middleware')
        return func(*args, **kwargs)
    return wrap

class Resource(MethodView):
    decorators = [middleware]

    def get(self, id=None):
        if not id: return 'index'
        return 'show'

    def post(self):
        return 'create'

    def put(self, id):
        return 'update'

    def delete(self, id):
        return 'delete'

resource_view = Resource.as_view('resource')
app.add_url_rule('/resource', view_func=resource_view, methods=['GET', 'POST'])
app.add_url_rule('/resource/<int:id>', view_func=resource_view, methods=['GET', 'PUT', 'DELETE'])

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

アプリケーションレベルでのミドルウェア

アプリケーションレベルでのミドルウェアは@app.before_request, @app.after_request, @app.teardown_request デコレータを用いて実装します。 定義されたミドルウェアは、対象アプリケーションのすべてのルーティングに適応されます。以下の例では、リクエストを受けたら before request, レスポンスを 返す直前に after request とログを出すサンプルです

# __init__.py
from flask import Flask

app = Flask(__name__)

@app.before_request
def logging_before_request():
    print('before request')

@app.after_request
def logging_after_request(response):
    """ reponse を返す必要がある
    """
    print('after request')
    return response

@app.teardown_request
def logging_teardown_request(exc):
    """ excは例外オブジェクト
    """
    print('teardown request')

@app.route('/')
def index():
    print('request')
    return 'response'

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

WSGI レベルでのミドルウェア

最後に WSGI レベルでのミドルウェアを設定します。今までは Flask アプリケーションの領域に入ってから処理を開始していましたが、 WSGI レベルのミドルウェアは Flask アプリケーションに入る前、すなわち、リバースプロキシがリクエストを受けた直後、または、リバースプロキシにレスポンスデータを通す直前に何らかの処理を挟み込むことができます。 flask から切り離されているので、例えば、この領域に django で作ったミドルウェアを挟み込む、 sinatra で作られたロガーを挟み込むなどの処理が可能です。 できることがほとんど同じなので、コードは割愛します。 https://flask-docs-ja.readthedocs.org/en/latest/patterns/appdispatch/#app-dispatch

アプリケーションコンテキスト と リクエストコンテキスト

Flask には上記のように、リクエストが来てからレスポンスを返すまでに様々な処理が入っていることがわかります。この時の処理最中にある変数の集合(公式ドキュメントで言っている状態)の事をしばしば「コンテキスト」と呼びます。 Flask でのコンテキストは、リクエストの単位のものとアプリケーション単位のものに分けられます。リクエストのコンテキストとは、クエリストリング、ボディ、ドメイン、ヘッダ etc.. のことを指します。対して、アプリケーションのコンテキストとは、そのアプリケーションを起動した時の設定ファイル、後述する Blueprint オブジェクトの情報、 DBのコネクションプールなどを指します。(DBの接続をコネクションプーリングを用いないで、毎回リクエスト単位で行っている場合は、DBの接続はリクエストのコンテキストになります。) コンテキストがどのように作成され、どうアクセスするかを知ることは良いアプリケーションの構築にとても重要です。

リクエストコンテキストについては上記で説明しているので、アプリケーションコンテキストの作成方法と、アクセス方法についてベスト・プラクティスを紹介します。 アプリケーションコンテキストは Flask クラスを継承して、プロパティをカスタマイズすることで作成する方法、 _app_ctx_stackを使う方法があります。

# __init__.py
# クラスを継承して作ってしまう方法
from flask import Flask
from redis import Redis

class MyApp(Flask):
    def __init__(self, name):
        self.my_param = "param"  # 後に flask.current_app から取得できる
        self.redis = Redis()     # redisのコネクションを貼り付けてみる
        super().__init__(name)

# __init__.py
# app付属のデコレータと変数を用いる方法
from flask import Flask

app = Flask(__name__)

# アプリケーションコンテキストの削除時に呼ばれる
@app.teardown_appcontext
def log():
    print('this is log')

アプリケーションコンテキストは current_appから取得出来ます。設定ファイルなどもここから参照しましょう。

# __init__.py
from flask import current_app

print(current_app.redis)
print(current_app.my_param)