미들웨어는 Django에서 요청(request)과 응답(response)을 처리할 때 개입해 특정 작업을 수행할 수 있도록 만들어진 플러그인 시스템이다. 요청이 서버에 도달하거나, 응답이 클라이언트로 전달되기 전에 미들웨어가 이를 가로채서 필요한 작업을 수행한 뒤 다음 단계로 넘긴다고 보면 된다.
이 글에서는 공식문서를 기반으로 Middleware가 무엇인지, 기본 동작 방식은 어떤지, 어떻게 Custom Middleware를 작성하는지에 대해 정리할 예정이다.
1. Middleware란?
Django 공식 문서에서는 미들웨어를 다음과 같이 설명한다.
"미들웨어는 Django의 요청 및 응답 처리 과정에 훅(hook)을 제공하는 가볍고 저수준의 플러그인 시스템이다."
이를 통해 요청(request)이 들어오거나 응답(response)이 나가기 전에 특정 로직을 실행하거나 데이터를 처리할 수 있다. Django에는 기본적으로 여러 내장 미들웨어가 제공되며, 대표적으로는 다음과 같다. (더 많은 미들웨어를 더 자세하게 알고 싶다면 다음 공식문서를 참고하면 된다.)
- AuthenticationMiddleware: 요청과 연결된 사용자를 식별한다.
- SessionMiddleware: 세션 데이터를 요청과 연결한다.
- CsrfViewMiddleware: CSRF 보안 검증을 처리한다.
- CommonMiddleware: 여러 공통적인 기능을 추가로 처리한다.
이처럼 각 미들웨어는 하나의 역할에 집중하도록 설계된다. 예를 들어, AuthenticationMiddleware는 요청이 들어왔을 때 세션을 이용해 사용자를 식별하고, 요청과 연결된 사용자 정보를 처리한다. 이런 방식으로, 미들웨어는 Django의 요청/응답 사이클을 제어하고 수정할 수 있다.
미들웨어는 Django 설정 파일(settings.py)의 MIDDLEWARE 리스트에 등록하여 활성화할 수 있다.
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',
]
미들웨어는 설정된 순서대로 요청을 처리하며, 응답을 반환할 때는 역순으로 처리된다. 요청은 각 미들웨어를 통과한 뒤 뷰(view)에 도달하며, 응답은 같은 미들웨어들을 역순으로 통과해 최종적으로 사용자에게 전달된다.
2. 미들웨어 순서와 계층 구조
요청 단계에서, Django는 MIDDLEWARE에 정의된 순서대로 미들웨어를 적용한다. 이를 "위에서 아래로" 처리한다고 표현한다. 응답 단계에서는 "아래에서 위로" 역순으로 처리된다.
공식문서에서는 미들웨어를 양파에 비유한다. 각 미들웨어는 양파의 한 겹이며, 뷰는 양파의 중심부에 해당한다. 요청이 모든 미들웨어를 통과해 뷰에 도달하면, 응답은 다시 역순으로 각 미들웨어를 통과해 사용자에게 전달된다.
각 미들웨어는 요청을 다음 단계(뷰 또는 다음 미들웨어)로 넘길지, 아니면 요청을 처리하고 바로 응답(response)을 반환할지 결정할 수 있다. 일반적으로 보통 미들웨어는 요청을 처리한 후 get_response를 호출해서 다음 미들웨어(또는 뷰)로 넘긴다. 마지막 미들웨어까지 요청이 도달하면 뷰가 실행되고 응답이 반환되며, 이 응답은 미들웨어를 역순으로 통과하며 사용자에게 전달된다.
만약 어떤 미들웨어가 요청(request)을 처리하고 get_response를 호출하지 않고 바로 응답(response)을 반환한다면, 그 미들웨어 "안쪽"에 있는 다른 미들웨어들과 뷰는 호출되지 않는다.
즉, 요청은 거기에서 멈추고 해당 응답은 다시 바깥쪽 미들웨어만 거쳐 사용자에게 전달된다.
3. 기본 미들웨어 구조
3.1 함수형 미들웨어
가장 간단한 미들웨어는 함수 형태로 작성할 수 있다.
def simple_middleware(get_response):
# 초기 설정 작업 (한 번만 실행)
def middleware(request):
# 요청(request)이 뷰에 도달하기 전에 실행할 코드
response = get_response(request)
# 응답(response)이 사용자에게 반환되기 전에 실행할 코드
return response
return middleware
3.2 클래스형 미들웨어
클래스 형태로도 미들웨어를 작성할 수 있다. 클래스형 미들웨어는 조금 더 구조적이고, 확장하기에 적합하다. 또한, 기본적으로 내장되어 있는 미들웨어들은 클래스형으로 이루어져 있다.
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 초기 설정 작업 (한 번만 실행)
def __call__(self, request):
# 요청(request)이 뷰에 도달하기 전에 실행할 코드
response = self.get_response(request)
# 응답(response)이 사용자에게 반환되기 전에 실행할 코드
return response
- __init__(self, get_response): __init__() 메서드는 get_response를 매개변수로 받아 한 번만 실행되며, 서버가 시작될 때 초기화 작업을 수행한다.
- __call__(self, request): 요청이 들어올 때마다 실행된다.
4. 미들웨어에서 제공하는 추가 메서드
4.1 process_view()
def process_view(request, view_func, view_args, view_kwargs):
process_view는 뷰가 실행되기 전에 요청(request)을 가로채어 특정 작업을 수행하거나 요청 흐름을 제어할 때 사용한다. 이 메서드는 주로 요청 데이터를 검증하거나 뷰 실행을 중단하고 바로 응답을 반환해야 할 때 유용하다.
process_view는 request, 실행될 뷰 함수(view_func), 뷰에 전달될 위치 인수(view_args)와 키워드 인수(view_kwargs)를 매개변수로 받는다. None을 반환하면 요청 처리가 계속 진행되며, HttpResponse 객체를 반환하면 뷰 실행을 중단하고 응답을 즉시 반환한다.
예를 들어, 인증되지 않은 사용자가 특정 뷰에 접근하는 것을 차단하거나, 뷰 실행 전 요청 데이터를 로깅할 때 활용할 수 있다. process_view는 모든 요청에 대해 호출되므로, 불필요한 작업은 최소화하는 것이 좋다.
주의할 점은 뷰가 실행되기 전에 request.POST에 접근하면 업로드 핸들러를 변경할 수 없으므로 이를 피해야 하는 것이다. 단, CsrfViewMiddleware는 예외적으로 이를 처리할 수 있는 csrf_exempt()와 csrf_protect() 데코레이터가 제공된다고 한다.
이 말이 잘 이해되지 않을 수 있다. 용어들이 좀 생소했다.
Django는 파일 업로드를 처리하기 위해 Upload handlers라는 구조를 사용하는데, 업로드 핸들러는 파일 데이터를 메모리에 저장하거나, 디스크에 저장하는 등의 작업을 수행한다.
미들웨어가 뷰(view) 실행 전에 request.POST에 접근하면 업로드 핸들러를 변경할 수 없다. 이는 Django가 request.POST에 처음 접근하는 순간 업로드 핸들러가 이미 초기화되기 때문이다. (즉, 첫 번째로 request.POST에 접근하는 시점에서 클라이언트가 보낸 데이터를 업로드 핸들러를 통해 처리하며, 이후에는 업로드 핸들러를 변경할 수 없으므로, 다른 설정을 적용하려고 해도 반영되지 않는다.)
class ExampleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print(request.POST) # 미들웨어에서 POST 데이터에 접근
response = self.get_response(request)
return response
쉬운 이해를 위해 예시를 들어봤다. 위 코드처럼 미들웨어에서 request.POST에 접근하면, 이 시점에서 업로드 핸들러가 이미 데이터를 처리하기 시작한다. 따라서 이후의 뷰나 다른 미들웨어에서 업로드 핸들러를 바꾸려는 설정은 무효화된다.
4.2 process_exception()
def process_exception(request, exception):
process_exception은 뷰 실행 중에 예외가 발생했을 때 호출되어 해당 예외를 처리하거나 별도의 응답을 반환할 수 있도록 한다. 이 메서드는 주로 예외 상황을 로깅하거나, 사용자 커스텀 에러 메시지를 반환해야 할 때 유용하다.
process_exception은 request와 발생한 예외 객체(exception)를 매개변수로 받는다. 반환값으로는 None 또는 HttpResponse 객체를 반환할 수 있다. None을 반환하면 기본 예외 처리가 계속 진행되며, HttpResponse 객체를 반환하면 이후의 예외 처리를 중단하고 즉시 응답을 반환한다.
예를 들어, 뷰에서 발생한 특정 예외를 감지하여 사용자에게 적절한 오류 페이지를 반환하거나, 예외 상황을 로깅 시스템에 기록할 때 활용할 수 있다. 또한, 모든 예외를 포괄적으로 처리하거나 특정 예외에 대해서만 선택적으로 동작하도록 설계할 수 있다.
process_exception은 응답 단계에서 역순으로 호출되며, 특정 미들웨어가 예외를 처리하고 응답을 반환하면 그 이후의 process_exception 메서드는 호출되지 않는다. 이를 통해 효율적으로 예외 처리를 분산시킬 수 있다.
4.3 process_template_response()
def process_template_response(request, response):
process_template_response는 뷰가 TemplateResponse 객체(또는 이를 구현한 객체)를 반환할 때 호출되어, 응답 데이터를 수정하거나 추가 작업을 수행할 수 있도록 한다. 이 메서드는 주로 템플릿 렌더링 전에 컨텍스트 데이터를 추가하거나 수정할 때 유용하다.
process_template_response는 request와 response 객체를 매개변수로 받는다. 반환값은 반드시 render() 메서드를 구현하는 응답 객체여야 한다. 기본적으로 수정된 response 객체를 반환하거나, 새롭게 생성된 TemplateResponse 객체를 반환할 수도 있다.
예를 들어, 모든 템플릿에 공통적으로 사용될 컨텍스트 데이터를 추가하거나, 특정 조건에 따라 템플릿 이름을 동적으로 변경할 때 활용할 수 있다. 또한, 템플릿 렌더링 전에 마지막으로 응답 데이터를 조작할 기회를 제공한다.
process_template_response는 응답 단계에서 실행되며, 미들웨어가 역순으로 호출되기 때문에 이전 단계에서 추가된 데이터나 수정사항을 바탕으로 후속 작업을 처리할 수 있다. 이를 통해 템플릿 데이터의 일관성을 유지하면서 동적인 처리를 쉽게 구현할 수 있다.
5. Custom Middleware
간단하게 커스텀 미들웨어를 통해 디버그 모드에서 웹사이트 URL을 출력하고, 페이지 렌더링 시간을 측정하려 한다. 해당 예시는 다음 영상을 참고해서 진행했다.
5.1 Django 앱 생성 및 설정
먼저, 미들웨어를 작성할 새로운 Django 앱을 생성한다.(앱 이름은 debugtools로 한다.)
python manage.py startapp debugtools
생성한 앱을 Django 설정 파일의 INSTALLED_APPS에 추가한다.
INSTALLED_APPS = [
..., # 기존 앱들
'debugtools',
]
5.2 middleware.py 파일 생성
debugtools 앱 폴더에 middleware.py 파일을 생성하고, 아래와 같이 커스텀 미들웨어 클래스를 구현한다.
from django.conf import settings
from django.utils import timezone
class DebugMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.start_time = None
def __call__(self, request):
# 요청 처리 시작 시간 기록
self.start_time = timezone.now()
response = self.get_response(request)
# 요청 처리 시간 계산 및 출력
if settings.DEBUG:
duration = timezone.now() - self.start_time
print(f"Page rendered in {duration.total_seconds()} seconds")
return response
def process_template_response(self, request, response):
# 디버그 모드에서 컨텍스트 데이터에 웹사이트 URL 추가
if settings.DEBUG:
response.context_data['website_url'] = 'https://example.com'
return response
여기서는 __call__ 메서드에서 페이지 렌더링 시간을 측정하고, process_template_response 메서드에서 디버그 모드일 때 컨텍스트 데이터에 URL을 추가한다.
5.3 미들웨어 등록
작성한 커스텀 미들웨어를 Django 프로젝트에 등록한다. settings.py 파일의 MIDDLEWARE 리스트에 경로와 클래스명을 작성해 준다.
MIDDLEWARE = [
..., # 기존 미들웨어
'debugtools.middleware.DebugMiddleware',
]
5.4 템플릿에서 데이터 출력
process_template_response 메서드에서 추가한 데이터를 템플릿에서 출력하려면 다음과 같이 코드를 작성한다. (templates/index.html에 작성)
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
{% if website_url %}
<footer>
<p>Website URL: {{ website_url }}</p>
</footer>
{% endif %}
</body>
</html>
마무리
Django를 배우면서 미들웨어를 깊이 다루는 경우는 드물다. 사실 기본적인 CRUD를 구현하는 데에는 미들웨어가 크게 필요하지 않기 때문이다. (그나마 JWT(JSON Web Token) 인증을 구현하거나, 전역적으로 보안 설정을 추가해야 할 때 가장 많이 접하지 않을까 싶다.) 게다가 미들웨어는 처음 접하려고 하면 생소하고 어려운 편이기도 하다. 따라서 미들웨어의 동작 원리를 이해하고 활용법을 익히는 데 시간이 걸릴 수 있지만, 그만큼 충분히 가치 있는 기능이며, Django를 활용하기 위해선 무조건 알아야 한다고 생각한다. (사실 동작 원리만 이해한다면 활용하는 것은 큰 어려움이 있진 않을 것이다.)
오늘 다루진 않았지만, 미들웨어는 단순히 요청과 응답을 가로채는 역할을 넘어 다양한 방식으로 활용할 수 있다. 예를 들어, 사용자 요청을 로깅하거나 API 속도 제한을 적용하고, 보안 헤더를 추가해 애플리케이션의 보안을 강화하는 데 사용할 수 있다. 또한, 글로벌 데이터를 응답(공통 응답)에 포함시켜 템플릿에서 재사용하거나, 다국어 처리를 위해 요청 헤더를 기반으로 로케일을 설정하는 기능도 미들웨어를 통해 구현할 수 있을 것이다. 물론, 이 또한 예시일 뿐이며 모든 프로젝트에서 반드시 미들웨어를 작성해야 하는 것은 아니다. 중요한 점은 각 애플리케이션의 요구사항과 상황에 맞게 미들웨어를 활용할 수 있다는 가능성을 열어두는 것이라 생각한다. 각자 프로젝트의 특성과 목표를 고민하며, 필요할 때 미들웨어를 활용하거나 확장하여 애플리케이션의 구조와 기능을 한층 더 개선하는 방향으로 나아가면 좋을 것 같다.
'Django' 카테고리의 다른 글
[Django] Django 프로세스, Request-Response Lifecycle (Web Server, WSGI/ASGI, Middlewares, Django) (1) | 2025.01.24 |
---|---|
[Django] URL dispatcher 공식 문서 파헤치기 (1) | 2025.01.18 |
[Django] settings.py 완전 정복 (..파일 분리까지) (0) | 2025.01.11 |
[Django] User Model, Custom User (Extending User) (0) | 2025.01.11 |
[Django] Django 마이그레이션과 MySQL 활용 (0) | 2025.01.10 |