Django Middleware (2)

django middleware

Posted by 동식이 블로그 on February 21, 2020

Django middleware (2)

middleware를 직접 작성해보자

이번엔 직접 middleware를 구현하고, 테스트

참고 사이트 예시인 Rest Framework를 위한 HTTP Response Formatting을 따라서 해보자

Rest Framework를 위한 HTTP Response Formatting

  • HTTP 응답 형식을 정해주는 미들웨어
  • 많은 상용 API 처럼 http 응답 형태를 일관성있게 해주기 위해서
  • Django에서 API 응답을 보내줄 때 형식
1
2
3
4
5
6
7
# vieww.py 예시
class SampleView(APIView):
    def get(self, request, *args, **kwargs):
        return Response({
            'success': True,
            'data': 'sample data'
        }
  • 위처럼 모든 view 함수에서 일일이 http 응답 형태를 정할 수있지만 중복되는 코드가 많이 생기게 된다
  • 미들웨어를 활용하면 http 응답이 미들웨어를 거쳐 자동으로 formatting 되도록 해보자

1. 미들웨어 클래스 만들기

  • 미들웨어 클래스 이름을 ResponseFormattingMiddleware이라고 하고, 클래스 변수를 만들어 준다
1
2
3
4
5
6
# middlware.py
import re
from rest_framework.status import is_client_error, is_success

class ResponseFormattingMiddleware:
    METHOD = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE')
  • http의 요청중 METHOD에 포함되어 있는 형식에 대해서만 미들웨어가 작동하도록 하기 위해서 설정해준다
  • 그 다음 initializer를 만들어 준다
1
2
3
4
5
6
def __init__(self, get_response):
    self.get_response = get_response
    self.API_URLS = [
        re.compile(r'^(.*)/api'),
        re.compile(r'^api'),
    ]
  • re는 정규표현식을 위한 파이썬 모듈이다
  • URL에 api가 포함되어 있는 요청에 대해서만 미들웨어가 작동하도록 하기 위해서 API_URLS라는 인스턴스 변수를 생성

2. __call__ 메소드 만들기

  • 클래스의 인스턴스가 함수처럼 호출될 때 불리는 메소드이다
1
2
3
4
5
def __call__(self, request):
    response = self.get_response(request)
    if hasattr(self, 'process_response'):
        response = self.process_response(request, response)
    return response
  • process_responseget_response에 정의되어 있다면 이를 처리해주는 형식

3. process_response 만들기

  • http 응답을 formatting 해주는 로직을 구현한다
  • 미들웨어가 작동할 조건은 유효한 URL인지, 유효한 http 요청 인지를 검사해야 한다
1
2
3
4
5
6
7
8
9
10
# 검사를 위한 코드
path = request.path_info.lstrip('/')
valid_urls = (url.match(path) for url in self.API_URLS)

if request.method in self.METHOD and any(valid_urls):
    response_format = {
        'success': is_success(response.status_code),
        'result': {},
        'message': None
    }
  • 검사 후 view에서 넘어온 response에 데이터가 있는지를 확인해야 한다.
  • 데이터가 있다면 정해둔 응답 format에 맞춰서 수정해주고, 없다면 response format 그대로 http 응답 데이터를 구성한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if hasattr(response, 'data') and getattr(response, 'data') is not None:
                data = response.data
                try:
                    response_format['message'] = data.pop('message')
                except (KeyError, TypeError):
                    response_format.update({
                        'result': data
                    })
                finally:
                    if is_client_error(response.status_code):
                        response_format['result'] = None
                        response_format['message'] = data
                    else:
                        response_format['result'] = data

                    response.data = response_format
                    response.content = response.render().rendered_content
            else:
                response.data = response_format
  • 마지막으로 response를 return 해주면 된다

완성된 middleware.py 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# middleware.py
import re
from rest_framework.status import is_client_error, is_success


class ResponseFormattingMiddleware:
    """
    Rest Framework 을 위한 전용 커스텀 미들웨어에 대해 response format 을 자동으로 세팅
    """
    METHOD = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE')

    def __init__(self, get_response):
        self.get_response = get_response
        self.API_URLS = [
            re.compile(r'^(.*)/api'),
            re.compile(r'^api'),
        ]

    def __call__(self, request):
        response = None
        if not response:
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response

    def process_response(self, request, response):
        """
        API_URLS 와 method 가 확인이 되면
        response 로 들어온 data 형식에 맞추어
        response_format 에 넣어준 후 response 반환
        """
        path = request.path_info.lstrip('/')
        valid_urls = (url.match(path) for url in self.API_URLS)

        if request.method in self.METHOD and any(valid_urls):
            response_format = {
                'success': is_success(response.status_code),
                'result': {},
                'message': None
            }

            if hasattr(response, 'data') and getattr(response, 'data') is not None:
                data = response.data
                try:
                    response_format['message'] = data.pop('message')
                except (KeyError, TypeError):
                    response_format.update({
                        'result': data
                    })
                finally:
                    if is_client_error(response.status_code):
                        response_format['result'] = None
                        response_format['message'] = data
                    else:
                        response_format['result'] = data

                    response.data = response_format
                    response.content = response.render().rendered_content
            else:
                response.data = response_format

        return response

4. 미들웨어 등록

  • setting.py에 만들어준 미들웨어를 추가해준다
  • 추가할 위치는 맨 밑, 그 이유는 이 전 포스팅에서 미들웨어의 순서가 중요하기 때문이다
1
2
3
4
5
6
7
8
9
10
11
# setting.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',
    '앱이름.middleware.ResponseFormattingMiddleware',
]

참고사이트