미들웨어

미들웨어 (Middleware)

미들웨어는 Django의 요청/응답 처리 흐름에 끼워 넣는 후크(hook)들의 프레임워크예요. 가볍고 저수준인 "플러그인" 시스템으로, Django의 입력이나 출력을 전역적으로 바꿀 수 있게 해 줘요. 예를 들어 세션을 이용해 사용자를 요청에 연결해 주는 AuthenticationMiddleware 같은 내장 컴포넌트가 있어요. 이 문서에서는 미들웨어가 어떻게 동작하는지, 어떻게 활성화하는지, 직접 어떻게 작성하는지를 설명해요.

출처: Django 공식 문서 - Middleware

미들웨어는 어떻게 생겼나

미들웨어 팩토리(factory)는 get_response라는 호출 가능한 객체(callable)를 받아 미들웨어를 돌려주는 callable이에요. 미들웨어 자체는 뷰처럼 요청을 받아 응답을 돌려주는 callable이에요. 함수로 이렇게 작성할 수 있어요.

def simple_middleware(get_response):
    # 서버 시작 시 단 한 번 실행되는 설정·초기화 코드
    def middleware(request):
        # 뷰(와 이후 미들웨어)가 호출되기 전, 매 요청마다 실행되는 코드
        response = get_response(request)
        # 뷰 호출 후, 매 요청/응답마다 실행되는 코드
        return response
    return middleware

인스턴스가 callable인 클래스로도 작성할 수 있어요.

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # 서버 시작 시 단 한 번 실행되는 설정·초기화 코드

    def __call__(self, request):
        # 뷰(와 이후 미들웨어)가 호출되기 전, 매 요청마다 실행되는 코드
        response = self.get_response(request)
        # 뷰 호출 후, 매 요청/응답마다 실행되는 코드
        return response

Django가 넘겨 주는 get_response는 마지막에 나열된 미들웨어라면 실제 뷰가 되고, 아니라면 체인에서 바로 다음 미들웨어가 돼요. 현재 미들웨어는 그것이 정확히 무엇인지 알 필요 없이, "다음에 무엇이 오는지"만 알면 돼요. 다만 마지막 미들웨어의 get_response는 실제 뷰가 아니라, 뷰 미들웨어 적용과 URL 인자 처리를 담당하는 핸들러의 래퍼 메서드라는 점을 기억하세요.

미들웨어는 파이썬 경로 어디에나 둘 수 있어요. 동기 파이썬만 지원하거나, 비동기만, 또는 둘 다 지원하도록 작성할 수도 있어요.

init(get_response)

미들웨어 팩토리는 반드시 get_response 인자를 받아야 해요. 초기화 시 전역 상태를 설정할 수도 있는데, 다음 두 가지를 주의해야 해요. 첫째, Django는 미들웨어를 get_response 인자 하나로만 초기화하므로 __init__()에 다른 인자를 요구하도록 정의할 수 없어요. 둘째, 요청마다 호출되는 __call__()과 달리 __init__()은 웹 서버가 시작될 때 딱 한 번만 호출돼요.

미들웨어를 사용하지 않는 것으로 표시하기

시작 시점에 어떤 미들웨어를 쓸지 결정하고 싶을 때가 있어요. 그럴 때는 미들웨어의 __init__()에서 MiddlewareNotUsed 예외를 일으키면 돼요. 그러면 Django가 그 미들웨어를 처리 과정에서 제거하고, DEBUGTrue일 때 django.request 로거로 디버그 메시지를 남겨요.

미들웨어 활성화하기

미들웨어를 활성화하려면 settings의 MIDDLEWARE 목록에 추가하면 돼요. 목록의 각 항목은 미들웨어 팩토리의 전체 파이썬 경로 문자열이에요. startproject가 만드는 기본값은 다음과 같아요.

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

Django 설치는 미들웨어를 전혀 요구하지 않아요. MIDDLEWARE를 비워 둬도 되지만, 적어도 CommonMiddleware는 쓰는 걸 강력히 권장해요. MIDDLEWARE의 순서는 중요해요. 미들웨어는 다른 미들웨어에 의존할 수 있기 때문이에요. 예를 들어 AuthenticationMiddleware는 인증된 사용자를 세션에 저장하므로 SessionMiddleware 다음에 실행되어야 해요.

미들웨어 순서와 레이어링

요청 단계에서 Django는 MIDDLEWARE에 정의된 순서대로, 위에서 아래로 미들웨어를 적용해요. 양파에 비유하면 각 미들웨어 클래스가 뷰(양파의 심)를 감싸는 "겹겹이"예요. 요청이 모든 겹을 통과해 뷰까지 도달하면, 응답은 다시 반대 순서로 모든 겹을 통과하며 나와요.

만약 어느 겹이 get_response를 호출하지 않고 응답을 바로 돌려줘서 단락(short-circuit)시키면, 그 안쪽의 모든 겹(뷰 포함)은 요청도 응답도 보지 못해요. 응답은 요청이 통과했던 같은 겹들만 거쳐 나와요.

다른 미들웨어 후크

요청/응답 패턴 외에도 클래스 기반 미들웨어에 세 가지 특별한 메서드를 추가할 수 있어요.

process_view()

process_view(request, view_func, view_args, view_kwargs) 메서드는 Django가 뷰를 호출하기 직전에 호출돼요. view_func는 쓰려는 실제 파이썬 함수 객체이고, view_args/view_kwargs는 뷰에 전달될 인자들이에요(첫 request 인자는 포함되지 않아요). 이 메서드는 None 또는 HttpResponse 객체를 돌려줘야 해요. None을 돌려주면 Django가 계속 처리해서 뷰를 호출하고, HttpResponse를 돌려주면 뷰를 호출하지 않고 응답 미들웨어를 적용해 그 결과를 돌려줘요.

process_exception()

process_exception(request, exception) 메서드는 뷰가 예외를 일으킬 때 호출돼요. None 또는 HttpResponse를 돌려줘야 해요. HttpResponse를 돌려주면 템플릿 응답과 응답 미들웨어가 적용되고 그 결과가 브라우저로 전달돼요. 그렇지 않으면 기본 예외 처리가 동작해요. 응답 단계에서는 미들웨어가 역순으로 실행되는데, process_exception도 예외가 아니에요. 예외 미들웨어가 응답을 돌려주면 그 위(앞)에 있는 미들웨어들의 process_exception 메서드는 호출되지 않아요.

process_template_response()

process_template_response(request, response) 메서드는 뷰가 끝난 직후, 응답 객체에 render() 메서드가 있어 TemplateResponse임을 나타낼 때 호출돼요. 이 메서드는 render 메서드를 구현한 응답 객체를 돌려줘야 해요. response.template_name이나 response.context_data를 바꿔서 기존 응답을 수정하거나, 새로운 TemplateResponse를 만들어 돌려줄 수도 있어요. 응답을 명시적으로 렌더링할 필요는 없어요. 모든 템플릿 응답 미들웨어가 호출된 뒤 자동으로 렌더링되거든요.

스트리밍 응답 다루기

HttpResponse와 달리 StreamingHttpResponsecontent 속성이 없어요. 그래서 미들웨어는 모든 응답에 content가 있다고 가정할 수 없어요. 내용에 접근해야 한다면 스트리밍 응답인지 확인하고 동작을 조정해야 해요.

if response.streaming:
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    response.content = alter_content(response.content)

streaming_content는 메모리에 담기에 너무 클 수 있다고 가정해야 해요. 응답 미들웨어는 그것을 새 제너레이터로 감쌀 수는 있지만 소비해서는 안 돼요. 감싸는 것은 보통 다음과 같이 구현돼요.

def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

더 알아보기 (Learn more)