Django Middleware - custom middleware 만들기

정현우·2022년 10월 13일
3

Django Basic to Advanced

목록 보기
27/38
post-thumbnail

Django Middleware

Django의 요청 / 응답 처리에 대한 후크 프레임 워크로써 Django 상의 모든 API 입출력을 전역적으로 변경하기 위한 플러그인 시스템이다.
Official Docs 에서 "Django의 입력 또는 출력을 전역적으로 변경하기 위한 가볍고 낮은 수준의 "플러그인" 시스템입니다." 라고 언급힌다.

Django middleware system

  • django project을 startup 해보면 기본적으로 settings.py 에 있는 많은 middleware를 볼 수 있다.
MIDDLEWARE = [
    # default
    '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라는 list의 0번째 index 부터 HttpRequest, HttpResponse가 loop 돌 듯이 거쳐간다. "순차적" 이라는 것이다.

  • HttpRequest Object 관점에서 보면, SecurityMiddleware를 시작으로, 각 미들웨어에서 process_request(), process_view() 메서드를 실행하면서 우리가 정의한 view 까지 도달하는 것이다.

  • 그리고 사실 middleware끼리 디펜던시가 있는 것도 있어서 순서가 중요하다. 예를 들어 AuthenticationMiddleware는 인증된 사용자를 세션에 저장하기 때문에 SessionMiddleware 이후에 실행되어야 한다. 이런 기본 미들웨어의 순서에 대한 고찰은 여기서 확인할 수 있다.

middleware custom

  • 원리는 아주 간단하다. class또는 function based로, django에서 미리 만들어둔 "미들 웨어 훅" 을 정의하고 사용하면 된다.

  • HttpRequest -> HttpResponse 이 처리 구간에서 time library의 process_time_ns 함수를 활용해서 응답 헤더에 추가하는 미들웨어를 만들어 보자.

Function based middleware

  • 우선 함수로 정의하는 미들웨어는 "팩토리 메소드 패턴" 형태를 가지게 된다.

Factory method patternTemplate Method의 생성 패턴 버전 이라고 도 한다. 특정 역할을 가진 객체를 생산하는 형태로 짜여진 디자인 패턴이다.
부모(상위) 클래스가 알지 못하는 구체 클래스 생성 패턴이며, 자식(하위) 클래스가 스스로 어떤 객체를 생성할지 결정한다.

  • django project root 경로에 custom_middleware.py 를 아래와 같이 만들고, settings.py에 추가하자.
# custom middlewawre - factory method pattern
def view_process_time_middleware(get_response):
    
    def middleware(request):
        print("before response", type(get_response), type(request))
        response = get_response(request)
        print("After response", type(response))
        return response

    return middleware
    
...

# settings.py (or config > settings > local.py)
MIDDLEWARE = [
	...
    'config.custom_middleware.view_process_time_middleware',
]
  • 그러면 아래와 같은 응답을 볼 수 있다.
before response <class 'function'> <class 'django.core.handlers.wsgi.WSGIRequest'>
After response <class 'rest_framework.response.Response'>
  • 시발점 부터 get_response 함수를 계속해서 주면서 다음 middleware를 타고 들어간다. 즉 response = get_response(request) 를 호출하여 get_response를 사용하는 순간 다음 middleware를 가고 (리커시브 한 형태), response가 정의 된 순간이 view단 로직이 끝나고 우리 차례까지 넘오는 것이다.
from time import process_time_ns
from django.core.handlers.wsgi import WSGIRequest
from rest_framework.response import Response

def view_process_time_middleware(get_response):
    
    def middleware(request: WSGIRequest):
        view_process_start_time = process_time_ns()
        response: Response = get_response(request)
        
        ############ After response ############

        view_process_end_time = process_time_ns()
        if not response.has_header("process_time"):
            response["View-Process-Run-Time"] = view_process_end_time - view_process_start_time

        return response

    return middleware

  • 아주 간단하게 response header에 View-Process-Run-Time 값을 넣는데 성공했다!
  • 나노초는 나누기 1000000000 (10^9) 하면 초로 변환할 수 있다.

Class based middleware

  • 사실 class based 는 더 확장성 있고 기능이 많다. 그리고 구조화되어 있기 때문에 많은 django user들이 class based를 선호하는것 같다. 기본적인 function base와 비슷하게 하려면 아래와 같은 템플릿을 보면 된다.
class ViewProcessTimeMiddleware:

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

    def __call__(self, request):
        view_process_start_time = process_time_ns()
        response = self.get_response(request)
        
        ############ After response ############

        view_process_end_time = process_time_ns()
        if not response.has_header("process_time"):
            response["View-Process-Run-Time"] = view_process_end_time - view_process_start_time
        return response
  • 클래스 형식으로 미들웨어를 정의하게 되면, http 요청/응답에 대한 처리를 하게되는 메소드를 추가로 정의할 수 있는데 이를 미들웨어 훅(Middleware Hook)이라고 한다.

  • 공식 문서에 아래와 같이 정의 되어 있다.

- Calls self.process_request(request) (if defined).
- Calls self.get_response(request) to get the response from later - - middleware and the view.
- Calls self.process_response(request, response) (if defined).
  • hook 종류가 request는 process_request, process_view, response는 process_exception, process_template_response, process_response 인 줄 알았는데,, 공식문서에서는 process_view, process_exception, process_template_response 가 official한 hook 종류였다.

middleware hooks

1. process_view(request, view_func, view_args, view_kwargs)

  • request 는, 위에서 확인 했 듯, HttpRequest object 이다.

  • view_func 는 django가 사용할 view function이다. (It’s the actual function object, not the name of the function as a string.)

  • view_args 는 view에서 넘어오는 positional arguments 것 이며, view_kwargs 는 view에서 넘어오는 dictionary of keyword arguments이다. view_args, view_kwargs 모두 첫 번째 인수인 request를 포함하지 않는다.

  • process_view() 는 Django가 view를 호출하기 "직전"에 trigger 된다.

  • 이 hook 함수는 무조건 None아니면 HttpResponse object를 return 해야 한다.

    • If it returns None, Django will continue processing this request, executing any other process_view() middleware and, then, the appropriate view.

    • If it returns an HttpResponse object, Django won’t bother calling the appropriate view; it’ll apply response middleware to that HttpResponse and return the result.

2. process_exception(request, exception)

  • requestHttpRequest object , exception은 view에서 넘어오는 Exception object. 만약 custom exception이라면 해당 부분 제대로 체크할 필요가 있다.

  • django는 view에서 exception있을 때 만 process_exception를 호출한다.

  • 이 hook 함수는 무조건 None아니면 HttpResponse object를 return 해야 한다.

    • If it returns an HttpResponse object, the template response and response middleware will be applied and the resulting response returned to the browser.
    • Otherwise, default exception handling kicks in.
  • 여기서 응답을 반환하면, 상위에 있는 process_exception 메서드는 호출되지 않는다. -> None일 때 만 타고 들어가니까.

3. process_template_response(request, response)

  • requestHttpRequest object , response 는 Django view or by a middleware에 의해 넘어오는 TemplateResponse object (or equivalent)이다.

  • process_template_response 메서드는 view가 실행을 다 끝내자 마자 호출한다. 하지만 response instance에 render() 메서드나 그와 비슷한 역할을 하는 함수가 있어야만 호출이 된다.

  • 이 hook 함수는 무조건 render method에 의한 response object를 return 해야 한다.

  • It could alter the given response by changing response.template_name and response.context_data, or it could create and return a brand-new TemplateResponse or equivalent.

  • You don’t need to explicitly render responses – responses will be automatically rendered once all template response middleware has been called.

  • Middleware are run in reverse order during the response phase, which includes process_template_response().


출처

profile
도메인 중심의 개발, 깊이의 가치를 이해하고 “문제 해결” 에 몰두하는 개발자가 되고싶습니다. 그러기 위해 항상 새로운 것에 도전하고 노력하는 개발자가 되고 싶습니다!

0개의 댓글