개요

Django의 미들웨어는 요청과 응답을 전역적으로 가로채어 공통 로직을 삽입하는 경량 플러그인 시스템임. 입력 또는 출력의 전역 수정이 필요할 때 사용함

미들웨어 시스템과 순서

settings.py의 MIDDLEWARE 리스트에 등록된 항목을 기준으로 동작함. 요청 단계는 위에서 아래 순서로 통과, 응답 단계는 아래에서 위 순서로 역순 통과. 순서가 기능적 의존성과 직결되므로 중요함. 예시로 AuthenticationMiddleware는 세션을 읽기 때문에 SessionMiddleware 이후 배치 필요

커스텀 미들웨어 만들기

미들웨어는 함수 기반 또는 클래스 기반 중 하나로 작성. get_response를 호출하면 다음 미들웨어 혹은 최종 view로 제어가 넘어가며, 반환 이후 구간이 응답 후 처리 지점이 됨

함수 기반 패턴

팩토리 형태로 정의. 바깥 함수가 get_response를 받아 내부 미들웨어 함수를 반환

def my_middleware(get_response):
    def middleware(request):
        # before view
        response = get_response(request)
        # after view
        return response
    return middleware

클래스 기반 패턴과 훅

구조화와 확장성 측면에서 권장됨. __init__에서 get_response를 보관하고 __call__에서 before/after 처리 가능

class MyMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    def __call__(self, request):
        # before view
        response = self.get_response(request)
        # after view
        return response
    # 선택적 훅
    def process_view(self, request, view_func, view_args, view_kwargs):
        return None

신규 스타일 미들웨어에서 공식 훅은 process_view, process_exception, process_template_response임. 응답 단계 훅은 역순으로 실행됨. 구버전 스타일의 process_request, process_response는 MiddlewareMixin을 사용할 때만 호환됨. 신규 스타일에서는 call 내부 before/after 구간으로 대체하는 형태 권장

예시 1 뷰 처리 시간 헤더 추가

요청과 응답 사이 경과 시간 측정을 헤더로 노출. CPU 사용 시간 측정 함수가 아닌 경과 시간 측정 함수 사용이 정확함. time.process_time_ns는 CPU 시간만 측정하므로 경과 시간에는 time.perf_counter_ns 권장

from time import perf_counter_ns

def view_process_time_middleware(get_response):
    def middleware(request):
        start = perf_counter_ns()
        response = get_response(request)
        end = perf_counter_ns()
        # 헤더 값은 문자열이어야 함
        response["View-Process-Run-Time-Ns"] = str(end - start)
        return response
    return middleware

단위는 나노초. 필요 시 10^9로 나누어 초 단위 파생 가능

예시 2 DRF 응답 포맷팅 스케치

API 응답을 일관된 형태로 변환. 대상 URL과 HTTP 메서드를 선별한 뒤 DRF Response 객체의 data를 래핑. 상태코드 기반 성공 여부와 페이로드 분리

class ResponseFormattingMiddleware:
    METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE"}
    def __init__(self, get_response):
        self.get_response = get_response
    def __call__(self, request):
        response = self.get_response(request)
        if request.method in self.METHODS and hasattr(response, "data"):
            ok = 200 <= response.status_code < 300
            data = response.data
            body = {
                "success": ok,
                "result": data if ok else None,
                "message": None if ok else data,
            }
            response.data = body
        return response

유효한 API 경로만 대상으로 삼고 싶은 경우 정규식이나 path prefix로 필터링 추가. 필요 시 메시지 키만 분리 추출하도록 확장 가능

훅 동작 요약

  • process_view(request, view_func, view_args, view_kwargs)
    • view 호출 직전에 실행
    • None 반환 시 다음 미들웨어 혹은 view로 진행, HttpResponse 반환 시 view를 호출하지 않고 응답 단계로 진입
  • process_exception(request, exception)
    • view에서 예외 발생 시 실행
    • HttpResponse를 반환하면 상위의 동일 훅은 호출되지 않음. None이면 기본 예외 처리로 위임
  • process_template_response(request, response)
    • response가 TemplateResponse 유사 객체일 때 실행. render 메서드를 가진 경우 대상이 됨
    • response.template_name, response.context_data 등을 조정 가능. 모든 템플릿 응답 훅 이후 자동 렌더링 수행

주의와 베스트 프랙티스

  • 순서 관리 중요. 세션 이후 인증, CSRF 이전 공통 헤더 삽입 등 의존 관계 고려
  • 빠르고 순수한 로직 유지. 데이터베이스 접근이나 외부 I/O 최소화
  • 단일 책임 지향. 인증, 로깅, 규격화 등 관심사를 분리
  • 헤더 값은 문자열로 설정. 바이너리 또는 비직렬화 객체 금지
  • 스트리밍 응답이나 파일 응답은 본문 변형에 유의. data 속성이 없는 HttpResponse는 그대로 통과
  • DRF 포맷팅은 Response 객체에 한정. 일반 HttpResponse와 구분 처리 필요
  • 신규 스타일 기준으로 훅 선택. 구버전 훅이 필요하면 MiddlewareMixin 사용 여부를 명시적으로 결정
  • 단위 테스트로 요청 경로, 메서드, 상태 코드별 분기 검증. 헤더 존재 여부와 값 범위 검증. 예외 발생 시 훅 체인 동작도 확인

마무리

Django 미들웨어는 전역 횡단 관심사를 다루는 표준 수단임. 순서와 훅의 특성을 이해하고 함수 또는 클래스 기반 중 요구사항에 맞는 패턴을 선택. 경과 시간 측정, 응답 포맷팅처럼 반복 코드를 한 곳에 모아 일관성과 유지보수성을 확보하는 것이 핵심임

참고자료