서론
Django를 통해 WAS 서버를 구성하다 보면 당연시하며 사용하는 것이 있다.
바로 'request(요청)' 객체가 이에 해당된다.
너무나도 당연시하며 사용하곤 있었지만 어떻게 이 'request' 객체가 생성되는지 흐름을 알고 있진 못하였다.
따라서 본 글에서는 WSGI 서버와 Django(장고) 사이에서 어떻게 'request' 객체가 생성되고,
비즈니스 로직이 담겨있는 뷰(View)를 거쳐 'response(응답)' 객체로 반환되는지를 알아보고자 한다.
본론
중국의 딥시크(R1) 모델의 발표로 인해 그 위상이 약간은 추락하신 Chat GPT 님께 자문을 구하였다.
Django에서 클라이언트의 요청이 들어와 실제 뷰(View)에 전달되기까지의 흐름을 간단히 요약하면 아래와 같습니다.
1. 클라이언트 (브라우저, 모바일 앱, etc.)
2. 웹 서버 (Nginx, Apache)
3. WSGI 서버 (uWSGI, Gunicorn 등)
4. Django WSGIHandler
5. Request 객체 생성
6. 미들웨어 체인 (전처리)
7. URLconf(라우팅)
8. 미들웨어 체인 (뷰 처리 훅, process_view)
9. View 함수/클래스(CBV)에서 로직 수행
10. Response 객체 생성
11. 미들웨어 체인 (후처리, process_response)
12. WSGI 서버를 통해 응답 전송
13. 웹 서버(Nginx/Apache) → 클라이언트로 최종 응답
출처 | OpenAI. (2025). ChatGPT (o1 Version) [Large language model]. https://chat.openai.com
상기 과정 중 1,2번 항목의 경우는 Nginx와 리버스 프록시(Reverse Proxy)를 주제로 한 글에서 다루도록 하겠다.
전체적인 Django request의 lifecycle은 아래와 같은 구조를 띈다.
각 단계별로 보다 자세히 살펴보자.
WSGI 서버와 WSGIHandler
WSGI(Web Server Gateway Interface)는 CGI(Common Gateway Interface)의 파이썬 구현체이다.
Nginx나 Apache 같은 웹 서버(Web Server)는 직접 파이썬 코드를 실행시킬 수 없으므로,
이를 중간에서 중계하여 직접적으로 파이썬 코드를 실행시킬 수 있는 WSGI 서버를 필요로 한다.
일반적으론 Gunicorn, uWSGI를 사용한다.
Django로의 진입점
Django 프로젝트를 생성하면 기본적으로 생성되는 코드 중 아래와 같은 코드를 확인할 수 있다.
# <프로젝트 이름>/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_wsgi_application()
get_wsgi_application() 함수가 본 글의 알파이자 오메가(자칭)인 WSGIHandler 인스턴스를 반환한다.
# django/core/wsgi.py
import django
from django.core.handlers.wsgi import WSGIHandler
def get_wsgi_application():
"""
The public interface to Django's WSGI support. Return a WSGI callable.
Avoids making django.core.handlers.WSGIHandler a public API, in case the
internal WSGI implementation changes or moves in the future.
"""
django.setup(set_prefix=False)
return WSGIHandler()
이후 아래 코드를 통해 application, 즉 WSGIHandler 인스턴스를 호출하게 된다.
( 호출한다는 것은 WSGIHandler.__call__() 매직메서드를 실행한다는 것을 의미한다. )
gunicorn config.wsgi:application --bind 0.0.0.0:8000
HttpRequest 생성 과정
그렇다면 도대체 WSGIHandler가 수행하는 역할이 뭐길래 알파이자 오메가(자칭)로 칭하는 걸까?
WSGIHandler는 request 객체의 생성부터 response 응답까지 전반적인 역할을 담당한다.
보다 자세히 살펴보면 WSGIHandler는 아래와 같은 구조로 이루어져 있다.
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
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)
response._handler_class = self.__class__
...
return response
이 구조에서 우리가 자세히 살펴보아야 할 부분은 두 부분이다.
request = self.request_class(environ)
response = self.get_response(request)
한 줄로 요약해보자면 request(요청) 인스턴스를 만들고 그 인스턴스를 통해 response(응답)을 얻는 것이다.
즉, 우리가 통상적으로 말하는 HTTP 요청과 그에 대한 응답이 위 코드 2줄로 이루어지는 것이다.
이 정도면 시작과 끝을 담당하니 Django의 알파이자 오메가라고 칭할만하지 않은가.
WSGIRequest
좀 더 자세히 살펴보자.
위 코드에서 말하는 self.request_class(environ)은 WSGIRequest 클래스의 인스턴스를 의미한다.
그럼 WSGIRequest 클래스는 뭘까? WSGIRequest는 아래와 같이 정의되어 있다.
# django/core/handlers/wsgi.py
class WSGIRequest(HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
# trailing slash), operate as if '/' was requested.
path_info = get_path_info(environ) or "/"
self.environ = environ
self.path_info = path_info
# be careful to only replace the first slash in the path because of
# http://test/something and http://test//something being different as
# stated in RFC 3986.
self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1))
self.META = environ
self.META["PATH_INFO"] = path_info
self.META["SCRIPT_NAME"] = script_name
self.method = environ["REQUEST_METHOD"].upper()
...
해당 클래스는 HttpRequest 클래스를 상속받는 것으로, 우리가 통상적으로 사용하는 request 객체를 의미한다.
위 클래스 구조를 살펴보면 method, path 등 일반적으로 사용하는 속성들이 정의되어 있음을 확인할 수 있다.
즉 상기 과정을 통해 우리가 사용하던 request 인스턴스, 보다 정확힌 WSGIRequest 인스턴스가 생성되는 것이다.
미들웨어(Middleware)와 URLconf(라우팅)
WSGIHandler.__call__() 매직메서드로 의해 생성된 request(WSGIRequest) 객체는
이어서 get_response(request) 함수를 통해 본격적으로 Django의 처리 파이프라인을 타게 된다.
해당 self.get_response()는 WSGIHandler의 조상인 클래스인 BaseHandler 클래스에 정의되어 있다.
# django/core/handleres/base.py
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request)
response._resource_closers.append(request.close)
if response.status_code >= 400:
log_response(
"%s: %s",
response.reason_phrase,
request.path,
response=response,
request=request,
)
return response
그렇다면 여기서 한 가지 더 짚고 넘어가야 할 부분이 있다.
바로 WSGIHandler의 __init__()에서 1차적으로 미들웨어를 로드한다는 점이다.
WSGIHandler의 __init__()와 미들웨어
앞서 살펴본 코드에서 WSGIHandler는 BaseHandler를 상속받고 있으며,
생성자(__init__) 내부에서 self.load_middleware()를 통해 미들웨어들을 불러오고 있다.
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
이 부분이 꽤 중요한데, Django는 프로젝트 설정에 정의된 미들웨어들을 여기서 1차적으로 모두 로드한다.
그리고 이 로드된 미들웨어들을 연결(체인)해서 실제 요청이 들어왔을 때 차례대로 실행하게끔 준비해놓는 것이다.
즉, WSGIHandler 인스턴스를 생성하는 순간, “어떤 미들웨어들이 있고, 어떤 순서로 처리할지”를 이미 알고 있게 된다. 이후 요청이 들어오면, 이 미들웨어 체인을 통해 전처리(process_request), 뷰 선택 직전 처리(process_view), 후처리(process_response) 등을 수행하게 된다.
이 말은 즉슨, 서버가 최초 실행될 때(WSGIHandler가 생성될 때) 미들웨어를 모두 로드하므로,
서버가 실행되는 중간에 추가되거나 변경된 미들웨어는 재시작하기 전까지 반영되지 않는다는 것을 의미한다.
get_response()와 URLconf, 그리고 미들웨어 체인
이어서 앞서 본 get_response(request) 메서드를 자세히 보면,
내부적으로 self._middleware_chain(request)를 호출하는 구조였다.
바로 이 _middleware_chain이 위에서 로드된 미들웨어들이 모여 구성된 체인이라고 이해하면 된다.
미들웨어 체인은 아래처럼 구성되어 있다.
실제 코드는 아래와 같다.
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request)
...
return response
상시 코드의 흐름을 간략히 설명하자면 아래와 같다.
- set_urlconf(settings.ROOT_URLCONF)
: Django는 URLconf(라우팅) 설정을 통해, “요청이 어떤 뷰(View)에 매칭될지를 찾아야겠다”라는 준비를 한다. - response = self._middleware_chain(request)
: 그리고 이어서 미들웨어 체인을 실행한다. 체인은 내부적으로 다음 과정을 거친다.
1) 모든 미들웨어의 process_request()를 순서대로 호출 (전처리)
2) URLconf로 “어떤 뷰 함수를 호출할지” 결정
3) 모든 미들웨어의 process_view() 호출 (뷰 직전 훅)
4) 실제 뷰(View 함수/클래스)를 실행해 HttpResponse 생성
5) 역순 미들웨어 process_response() 호출 (후처리)
결국 get_response(request)가 하는 일은 URLconf를 세팅하고,
미들웨어 체인에 request를 통과시켜서 최종 response를 받아오는 과정 이라 할 수 있다.
드디어 우리가 앞서 언급하였던 request를 통해 response를 얻게 된 것이다.
Django Middleware 공식 문서에는 아래와 같은 문장이 존재한다.
If a middleware (or the view) returns an HttpResponse object, Django won’t bother calling any other middleware (process_request or process_view) or the appropriate view...
이 말은 즉슨 미들웨어 체인에서 전처리(process_request) 단계에서 HttpResponse를 반환하면,
그 시점에서 체인이 중단되고 바로 응답이 되어버린다는 점을 유의해야 한다.
예를 들어 우리가 자주 본 'CSRF verification failed. Request aborted.' 에러를 생각해보자.
해당 에러는 CsrfViewMiddleware의 전처리 도중 만약 CSRF 토큰 검증에서 실패하면,
Django는 즉시 HttpResponseForbidden(403)을 반환하여 요청 처리를 멈춘다.
즉, 뒤에 올 다른 미들웨어나 URLconf, 뷰를 전혀 거치지 않고, 바로 403 응답을 돌려주는 것이다.
결론
정리하자면
- WSGIHandler가 처음 생성될 때, load_middleware()를 통해 모든 미들웨어를 미리 로드한다.
- 요청이 들어오면, WSGIHandler.__call__()에서 request(WSGIRequest)를 생성하고,
get_response(request)로 처리 과정을 진행한다. - get_response() 내부에서는 URLconf를 지정한 뒤, 미들웨어 체인(_middleware_chain)을 통해
전처리 → URL 라우팅 → 뷰 호출 → 후처리를 모두 진행한다.
결론적으로 Django의 요청 흐름은
WSGIHandler → 미들웨어 체인(전처리) → URLconf → 미들웨어 체인(뷰 직전 / 후처리) → 뷰
의 구조라고 볼 수 있다.
참고자료
Middleware | Django documentation
The web framework for perfectionists with deadlines.
docs.djangoproject.com
django/django/core/handlers/base.py at main · django/django
The Web framework for perfectionists with deadlines. - django/django
github.com
Django: Request/Response Cycle
A web application or a website revolves around the request-response cycle and Django applications are no exception to this. But it is not…
medium.com
Django Request-Response Cycle | Technoarch Softwares
Request and Response objects are transmitted over the web. Most of the contents that we have seen on the web like images, HTML, CSS, JavaScript are all Response objects. Technoarch works with Django and python to build smooth API's
www.technoarchsoftwares.com
Django Request Life Cycle Explained
In the world of web development, understanding the request life cycle is crucial for optimizing...
dev.to
Django Middleware (2) - 동식이 블로그
Django middleware (2)
dongsik93.github.io
[Django] 미들웨어(Middleware)
Middleware란? Django의 요청/응답 처리를 위한 프레임워크임 Django의 입출력을 전역으로 변경하기 위한 가볍고, 낮은 수준의 플러그인 시스템 각 구성 요소는 특정 기능을 담당함.(ex. 사용자가 세션
pupbani.tistory.com
요청(request, 브라우저 주소창)은 어떻게 이루어질까?
요청(reqeust)은 어떻게 이루어지는가?API를 사용할때 파이썬의 경우 request를 사용해서 요청하고 심지어 주소창에 url을 입력해서 특정 사이트를 접속하는 경우도 리퀘스트, API 요청이다.그런데 이
velog.io
'BE > Django' 카테고리의 다른 글
DRF Serializer Validation 로직 (0) | 2025.04.12 |
---|---|
쿠키(Cookie)의 종류 구분 [Session vs Persistent] (2) | 2025.01.07 |
Django JWT 인증 자동화하기 [DRF Simple JWT] (3) | 2024.10.28 |