django middlerwareを作ってみた

# 背景
* django rest frameworkで認証用サービスを実装した
* 各APIへリクエストするときに、同じで認証後のユーザ情報をチェックしないといけない
* 別々でビューに書くすると重複のコードになってしまう

# 概要
* 別々でビューに書くすると重複のコードになってしまう
* django middleware紹介
* django middlewareで重複コードを改善する
* 例外処理

# 別々でビューに書くすると重複のコードになってしまう
#### サンプル
```python:api_view.py
from rest_framework.views import APIView
from rest_framework.response import Response

class Contents(APIView):
    def get(self, request):
        try: 
            # トークンチェック
            token_controller.check(request)
            # userチェック
            user = user_controller.check(request)
        except Exception as e:
            return Response(e)

        try:
            # userのコンテンツを取得
            contents = content_controller.get_contents(user)
        except Exception as e:
            return Response(e)
        return Response(contents)
```

* 上記のソースコードに、トークンチェックとユーザチェックが各APIにやっているので、重複な部分になってしまう
* 例外処理も重複になってしまう
* django middlewareで改善していく

# django middleware紹介
* https://docs.djangoproject.com/en/4.1/topics/http/middleware/
* 基本構成

```
class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # ビュー前の処理
        response = self.get_response(request)
        # ビュー後の処理

        return response
```

* 導入

```python:settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    ......
    'mymiddleware.SimpleMiddleware',
]
```

* middleware hooks
    * process_view(request, view_func, view_args, view_kwargs)
    * process_exception(request, exception)


# django middlewareで重複コードを改善する
#### 説明
* トークンチェックとユーザチェック部分をmiddlewareに移す
* 例外処理も一緒に移動して、例外発生する場合ははビューを経由しなくて直接的にエラー情報を返す
* 正常ケースの場合は、middlewareに取得したユーザ情報をkwargsで返す


#### ソースコード
```python:simple_middleware.py
class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def process_view(request, view_func, view_args, view_kwargs):
        try: 
            # トークンチェック
            token_controller.check(request)
            # userチェック
            view_kwargs["user"] = user_controller.check(request)
        except Exception as e:
            return Response(e)
```

```python:api_view.py
from rest_framework.views import APIView
from rest_framework.response import Response

class Contents(APIView):
    def get(self, request, **kwargs):
        user = kwargs["user"]

        try:
            # userのコンテンツを取得
            contents = content_controller.get_contents(user)
        except Exception as e:
            return Response(e)
        return Response(contents)
```



# ビューの例外処理
#### 説明
* process_exceptionでビューに発生した例外を処理する
* ビューの方が正常ケースを処理する

#### ソースコード
```python:simple_middleware.py
class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def process_view(request, view_func, view_args, view_kwargs):
        ......

    def process_exception(request, exception):
        return Response(exception)
```

```python:api_view.py
from rest_framework.views import APIView
from rest_framework.response import Response

class Contents(APIView):
    def get(self, request, **kwargs):
        user = kwargs["user"]

        # userのコンテンツを取得
        contents = content_controller.get_contents(user)
        return Response(contents)
```


# まとめ
* django middlewareを利用して結構ソースコードの量が減っている
* 例外処理も集中になって、管理しやすい
* しかし、例外発生と例外処理が別の場所なので、ソースコード実装の時に例外処理が忘れないようにしてください