56
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DjangoのMiddlewareについて

Posted at

Djangoで「APIリクエストの認証処理を自前で作成したいなぁ」と思った時に、Middlewareについて調べたので、その備忘録として記事に残します。

そもそもMiddlewareって何よ

Djangoの文脈で Middleware とは、リクエスト/レスポンス処理にhookを加えるための仕組みです。簡単に言うと、

  • リクエストが送られた時に行う処理
  • レスポンスを返す前に行う処理

などを定義することができるものです。

(今回、私は特定のURLにアクセスがされたら特別に認証処理を行い、認証が通らなければ200以外のステータスでレスポンスをする、という処理をこのMiddlewareを用いて行いました。)

Middlewareクラス

Middlewareクラスの基本形

自前のMiddlewareクラスを作成します。まず、以下のようにベースとなるclass定義を行います。

class SimpleMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        response = self.get_response(request)

        return response

これがMiddlewareクラスの基本形で、ここから必要に応じて処理を加えていきます。

※実際クラスベースではなく関数ベースのMiddlewareも作成することはできますが、クラスベースを知ってさえすれば困ることはないと思ったため、今回はクラスベースのみを紹介することにしました。

Middlewareクラスに自前の処理を加える

Middlewareクラスには原則として3箇所のhookポイントがあります。

class SimpleMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response
        # (1)

    def __call__(self, request):
        # (2)

        response = self.get_response(request)
        
        # (3)

        return response

上記コード内に埋め込んだ(1)〜(3)がhookポイントで、ここに任意の処理を加えていくことになります。

(1): __init__

Middlewareの __init__() はサーバ起動時に1度だけ呼び出されます。そのため、初期化などの初回のみ実行すべき処理を記述することになると思います。

(2): __call__ get_response実行前

Middlewareの __call__() はリクエスト毎に呼び出されるメソッドです。そのため、リクエスト毎に実行すべき処理は __call__() に記述することになります。

self.get_response() は、requestオブジェクトに対してview関数を適用させてresponseオブジェクトを得る、ということを行っています。
それゆえ(2)の self.get_response() 実行前に処理を記述することで、view関数適用前に実行する共通処理を定義することができます。

(3): __call__ get_response実行後

(2)と違い、(3)はview関数の処理が終わった後にレスポンスをサーバから返す前に実行する共通処理ということになります。

Middlewareを利用する設定を追加する

上記のMiddlewareクラスを用意しただけではDjangoサーバはそのMiddlewareクラスを利用しません。settings.pyで設定することで用意したMiddlewareクラスを有効にすることができます。

  • {your project}/settings.py
...

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
    '{your app}.middleware.SampleMiddleware'  # <= 追加
]

...

上の例では {your app}/middleware.py というファイルに SampleMiddleware というクラスを用意した場合です。ここの記載は用意した環境によって記載する内容が変わってきますので、環境に合わせて記述するようにしてください。

また、Middlewareはリクエストが送られたらこのsettings.pyに設定されているものを上から順番に(2)に書かれた処理が実行されます。一方で(3)に書かれた処理はこの逆の順番で実行されます。なので場合によってはこの順番を意識して設定する必要があります。

実際にリクエストを送ってみる

以上を踏まえて、以下のようなview関数を用意・呼び出される形でサーバを起動・リクエストを投げてみます。

class SimpleMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response
        print('(1) init')

    def __call__(self, request):
        print('(2): before get_response')

        response = self.get_response(request)
        
        print('(3): after get_response')

        return response
from django.http import HttpResponse

def sample_view(request):
    print('in view')
    return HttpResponse()

実際にサーバ起動、その後リクエストを2度行った時のログが以下です。(見やすいように改行を入れています)

$ python sampleproj/manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
February 12, 2018 - 10:36:32
Django version 2.0.2, using settings 'sampleproj.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

(1): init
(2): before get_response
in view!
(3): after get_response
[12/Feb/2018 10:39:25] "GET /sample HTTP/1.1" 200 17

(2): before get_response
in view!
(3): after get_response
[12/Feb/2018 10:39:29] "GET /sample HTTP/1.1" 200 17

(1)は一度だけ、(2)・(3)はリクエストの度に呼ばれていることがわかりますね。

特殊メソッド

Middlewareには今まで述べてきた(1)~(3)の他にhookポイントが3つあります。

  1. process_view
  2. process_exception
  3. process_template_response

これらはMiddlewareクラスのインスタンスメソッドとして定義することで利用することができます。

process_view

process_view() はview関数を呼び出す直前にhookされるメソッドで、(2)のhookポイントと違い、

  • 実行されるview関数
  • そのview関数への引数(例えばurlsで定義したパラメータ)

を知っているのが特徴です。

process_view() は返り値として

  • None
  • HttpResponse

のどちらかを返す必要があります。 None を返せばそのままview関数への処理が進みますが、 HttpResponse を返すと処理はここで止まり、サーバからのレスポンスとしてその HttpResponse で定義された内容が返ります。

from django.http import HttpResponse

class HttpResponseBadRequest(HttpResponse):
    status_code = 400

class SimpleMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response
        
    def process_view(self, request, view_func, view_args, view_kwargs):
        if view_func.__name__.startswith('api'):
            return HttpResponseBadRequest()
        else:
            return None

この例では、view関数の関数名が api から始まる関数だった場合は、HTTPステータスコードが400のレスポンスを返し、それ以外はそのまま通常の処理を進めるという処理になります。(例ですので、実用性とか全く考えていないのであしからず。)

process_exception

process_exception() はview関数内で例外(Exception)がraiseされた時にhookされるメソッドです。

  • None
  • HttpResponse

を返すことができます。Noneの場合はそのまま処理が進み、HttpResponseを返した場合はそのHttpResponseで定義されたレスポンスが返ります。

from django.http import HttpResponse

class SimpleMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        return response
        
    def process_exception(self, request, exception):
        same_logger(exception)
        return HttpResponse('error raised')

この例では、なんらかのロガーでexceptionの内容を記録し、error raised という内容のレスポンスを200番で返しています。

process_template_response

process_template_response() は、view関数の処理の結果、 TemplateResponse を返す場合にのみhookされるメソッドです。例えば django.shortcut.render を使って return render(...) などと返しているview関数が該当します。
process_template_response() の特徴は

  • 返される TemplateResponse オブジェクト

を知っているのが特徴です。

これらの特殊メソッドのhook機構を使うことで、処理をうまく共通化できるようになりそうですね。

参考

ミドルウェア (Middleware) | Django documentation | Django

56
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?