Djangoのmiddleware_chain


今回やる事

Djangoのmiddleware_chainの挙動に興奮したので、簡単なコードに書き換えて挙動を分かりやすく整理してみた。

どこの部分の話か

Djangoで立てたサーバーに、リクエストが来た時にミドルウェアを通して動くように実装されている部分。

middleware_chainとは

おおまかにまとめると、settings.pyで指定したミドルウェアリストの上から順にリクエストを渡し、一連のミドルウェアの処理が終わったら最終的にレスポンスが返されるやつ。

デコレータの仕組みにより、ミドルウェアリストを逆順でループさせて、後続のミドルウェアを自身のメソッドとして登録していく事で、この一連の流れを作っている。

まさにその名の通りの動きをする。

簡単に実装してみた

※色々省きます。


まずはミドルウェア関連

インスタンス化されると、引数で渡されたget_responseを自身のインスタンスにセットする。

コールで呼ばれたら
  1. process_requestが実装されていたら実行する。
  2. self.get_responseにより、次のチェーンへとつなぐ。次がミドルウェアだったら1 ~ 2を繰り返す。
  3. get_responseメソッドにたどり着いたら、今度はミドルウェアリストの逆順からprocess_responseを処理していく。
middlewares.py
class MiddlewareMixin:

    def __init__(self, get_response):
        print('【', self.__class__.__name__, '】の 【 get_response 】に', res,'を指定\n')
        self.get_response = get_response

    def __call__(self, request):
        print('【call】', self.__class__)
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request) # 1

        response = response or self.get_response(request) # 2

        print('========================================')

        if hasattr(self, 'process_response'):
            response = self.process_response(request, response) # 3
        return response


class MiddlewareA(MiddlewareMixin):

    def process_request(self, request):
        print('   1. MiddlewareAのprocess_request')

    def process_response(self, request, response):
        print('   5. MiddlewareAのprocess_response')
        return response

class MiddlewareB(MiddlewareMixin):

    def process_request(self, request):
        print('   2. MiddlewareBのprocess_request')

    def process_response(self, request, response):
        print('   4. MiddlewareBのprocess_response')
        return response

リクエスト、レスポンスクラス

http.py
class HTTPRequest:
    pass

class HTTPResponse:
    pass

ハンドラークラス

・インスタンス化されたら、ミドルウェアチェーンを作成する。
・コールで呼ばれたらリクエストをミドルウェアチェーンに渡す。

handler.py
class Handler:

    def __init__(self):
        self.load_middleware()

    def __call__(self, request):
        self._middleware_chain(request)

    def load_middleware(self):
        handler = convert_exception_to_response(get_response)
        for mw in reversed(middleware_list):
            mw_instance = mw(handler)
            handler = convert_exception_to_response(mw_instance)
        print('ミドルウェアチェーンに直前のhandlerを指定: ', res, '\n')
        self._middleware_chain = handler

メソッドなど

・get_responseメソッドがリクエストを処理するメソッド。
・convert_exception_to_responseメソッドは、受け取った関数 or クラスを返すクロージャを返す。

utils.py
def get_response(request):
    print('3. get_responseメソッド')
    print('get_response終わり')
    return HTTPResponse

def convert_exception_to_response(get_response):
    global res
    if isinstance(get_response, Middleware):
        res = str(get_response.__class__.__name__) + ' を記憶したinner関数'
    elif hasattr(get_response, '__name__'):
        res = str(get_response.__name__) + ' を記憶したinner関数'
    print(res)
    print(' ↓ ')

    def inner(request):
        response = get_response(request)
        return response
    return inner

ミドルウェアリスト

settings.py
middleware_list = [
   MiddlewareA,
   MiddlewareB 
]

ハンドラーを動かしてみる

main.py
# インスタンス化されミドルウェアチェーンをセット
handler = Handler()

# callが呼ばれミドルウェアチェーンにリクエストを渡す
handler(HTTPRequest)

結果

output.sh

# ★ load_middlewareが動く。


# 最初はget_responseを記憶したinner関数を作る
get_response を記憶したinner関数

 ↓

# ミドルウェアリストを逆順にループ
# 上で作ったinner関数を一番下のミドルウェアBにセット
【 MiddlewareB 】の 【 get_response 】に get_response を記憶したinner関数 を指定

# ミドルウェアBを記憶したinner関数を作る
MiddlewareB を記憶したinner関数

 ↓ 

# ミドルウェアBを記憶したinner関数をミドルウェアAにセット
【 MiddlewareA 】の 【 get_response 】に MiddlewareB を記憶したinner関数 を指定

# ミドルウェアAを記憶したinner関数を作る
MiddlewareA を記憶したinner関数

 ↓ 

# ミドルウェアAを記憶したinner関数を、ミドルウェアチェーンとしてセットする。
ミドルウェアチェーンに直前のhandlerを指定:  MiddlewareA を記憶したinner関数 


# ★ ミドルウェアチェーンにリクエストを渡すと、ミドルウェアAがinner関数内でコールで呼ばれる。
【call】 <class '__main__.MiddlewareA'>
   1. MiddlewareAのprocess_request
【call】 <class '__main__.MiddlewareB'>
   2. MiddlewareBのprocess_request
   3. get_responseメソッド
      get_response終わり

# get_responseメソッドまで行ったら、今度は逆順にミドルウェアのprocess_responseが動く。
========================================
   4. MiddlewareBのprocess_response
========================================
   5. MiddlewareAのprocess_response

このように、ミドルウェアチェーンにリクエストを渡したら、

  1. MiddlewareAのprocess_request
  2. MiddlewareBのprocess_request
  3. get_responseメソッド
  4. MiddlewareBのprocess_response
  5. MiddlewareAのprocess_response

という風に動いた。

まとめ

・予めミドルウェアチェーンを作成
 ↓
・やってきたリクエストをミドルウェアチェーンに渡す
 ↓
・ポンポンポンとミドルウェアを通った後get_responseメソッドが動く
 ↓
・興奮した


これを踏まえてDjangoのソースを深く追ってみようと思う。