Django 요청 처리 흐름

Django의 WSGI 진입점부터 뷰 응답까지의 완전한 호출 구조와 뷰 선택 과정을 상세히 분석합니다.

1. WSGI 진입점과 BaseHandler 호출 구조

WSGI 애플리케이션 생성

# django/core/wsgi.py
from django.core.handlers.wsgi import WSGIHandler

def get_wsgi_application():
    django.setup(set_prefix=False)
    return WSGIHandler()

WSGIHandler 클래스 구조

# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        # WSGI 스펙에 따른 호출 가능 객체
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)

        request = self.request_class(environ)
        response = self.get_response(request)  # BaseHandler의 메서드 호출

        # 응답 처리
        response._handler_class = self.__class__
        status = '%d %s' % (response.status_code, response.reason_phrase)
        start_response(status, response.items(), response.get('wsgi.file_wrapper'))
        return response

2. BaseHandler의 핵심 메서드들

get_response 메서드 (주요 진입점)

# django/core/handlers/base.py
class BaseHandler:
    def get_response(self, request):
        """메인 요청 처리 로직"""
        set_urlconf(settings.ROOT_URLCONF)

        response = self._get_response(request)

        # 응답 후처리
        response = self.apply_response_fixes(request, response)
        return response

    def _get_response(self, request):
        """실제 요청 처리 흐름"""
        response = None
        callback = None
        callback_args = None
        callback_kwargs = None

        try:
            # URL 해결 (URLconf 처리) -> 뷰 선택
            resolver = get_resolver(get_urlconf())
            resolver_match = resolver.resolve(request.path_info)
            callback, callback_args, callback_kwargs = resolver_match
            request.resolver_match = resolver_match

            # 미들웨어 체인 실행 (process_request + process_view)
            response = self._middleware_chain(request)

        except Exception as e:
            # 예외 처리 미들웨어 실행
            response = self.process_exception_by_middleware(e, request)
            if response is None:
                raise

        return response

3. 뷰 선택의 정확한 타이밍

요청 처리 단계에서의 위치

# django/core/handlers/base.py
def _get_response(self, request):
    response = None

    try:
        # 뷰 선택
        resolver = get_resolver(get_urlconf())
        resolver_match = resolver.resolve(request.path_info)  # 뷰 결정
        callback, callback_args, callback_kwargs = resolver_match
        request.resolver_match = resolver_match

        # 미들웨어 process_view 실행 (뷰는 이미 선택된 상태)
        response = self.process_view_middleware(
            request, callback, callback_args, callback_kwargs
        )

        # 실제 뷰 실행은 나중에
        if response is None:
            response = callback(request, *callback_args, **callback_kwargs)

중요: 뷰 선택은 미들웨어 실행보다 먼저 일어납니다.

4. URLResolver의 상세한 해결 과정

URLResolver.resolve() 내부 구현

# django/urls/resolvers.py
class URLResolver:
    def resolve(self, path):
        path = str(path)
        tried = []

        # 1단계: 현재 패턴과 매치 확인
        match = self.pattern.match(path)
        if match:
            new_path, args, captured_kwargs = match

            # 2단계: 하위 패턴들을 순회하며 매칭 시도
            for pattern in self.url_patterns:
                try:
                    sub_match = pattern.resolve(new_path)
                except Resolver404:
                    # 매치 실패 시 다음 패턴 시도
                    continue
                else:
                    # 3단계: 매치 성공 시 ResolverMatch 생성
                    return ResolverMatch(
                        sub_match.func,           # 실제 뷰 함수/클래스
                        args + sub_match.args,    # URL 위치 인자
                        {**captured_kwargs, **sub_match.kwargs},  # 키워드 인자
                        sub_match.url_name,       # URL 이름
                        sub_match.app_names,      # 앱 이름들
                        sub_match.namespaces,     # 네임스페이스들
                    )

        # 매치 실패 시 404 예외
        raise Resolver404({'tried': tried, 'path': new_path})

5. URLPattern별 매칭 메커니즘

path() 함수의 RoutePattern