동식이 블로그 / dongsik93.github.io
TIL 4 MIN READ 673 WORDS

Django Middleware (2)

django middleware

#django #middleware

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',
]

참고사이트