장고 애플리케이션을 개발하고 나면 이를 배포해야 한다. 자바 스프링의 경우 Tomcat과 같은 WAS 환경으로 배포된다. 하지만 파이썬은 자바 애플리케이션과 달리 컴파일이 필요없고 인터프리터를 통해서 실행된다. 그렇다면 파이썬 애플리케이션이 어떤 웹서버 환경에서 동작하는지 장고 애플리케이션을 기준으로 정리한다.
1. CGI vs WSGI vs ASGI
파이썬 애플리케이션을 웹서버에서 실행하기 위해서는 웹서버와 애플리케이션이 통신할 수 있어야 한다. 웹서버로 전달된 요청을 애플리케이션으로 전달하고 애플리케이션에서 처리한 응답을 다시 외부로 반환할 수 있어야 한다. 이러한 동작을 위해서 파이썬 애플리케이션의 통신 방식을 인터페이스로 미리 정의해놓았고, 통신 방식에 따라서 CGI, WSGI, ASGI 등 다양하게 구분된다.
2. CGI
CGI는 Common Gateway Interface의 줄임말이다. 서버에 입력된 요청에 대해 CGI 스크립트를 실행하는 방식으로 요청을 처리하고 결과를 반환한다. CGI 스크립트에서는 파이썬 코드를 실행시키는데, 요청 정보는 환경변수와 표준 입력 등의 방법으로 처리하여 파이썬으로 전달한다. 이 방식은 매 요청마다 새로운 프로세스를 실행시켜 요청을 처리한다. 이때문에 매번 파이썬 인터프리터를 실행시켜서 발생하는 성능 이슈가 있다.
3. WSGI
WSGI는 Web Server Gateway Interface의 줄임말이다. 웹서버와 파이썬 애플리케이션간의 동기적 통신의 표준이다. CGI와 달리 WSGI 서버는 하나의 파이썬 애플리케이션 으로 여러 요청을 처리한다. 이때문에 CGI 보다 나은 성능을 보여준다.
WSGI 서버는 파이썬 애플리케이션의 callable object를 호출하여 입력된 요청을 처리한다. 비동기는 지원하지 않으며 WSGI를 지원하는 서버로는 gunicorn과 uwsgi 등이 있다.
- gunicorn
gunicorn 은 wsgi를 지원하는 HTTP 서버로 아래와 같은 방식으로 설치 및 실행한다. 아래 예시는 mysite 라는 장고 애플리케이션을 실행하는 명령어이다.
python -m pip install gunicorn // gunicorn 설치
gunicorn mysite.wsgi // Django project mysite의 wsgi 애플리케이션 실행
- Django WSGI
장고 프로젝트 내부에는 wsgi 애플리케이션이 미리 정의되어 있다. wsgi 서버에서는 이 애플리케이션을 호출하여 장고 프로젝트를 실행한다. wsgi 서버들은 파이썬으로 구현되어 있어서 장고 프로젝트의 wsgi 모듈에 정의된 application 객체에 직접 접근할 수 있다. 해당 객체를 통해서 서버로 입력된 요청을 처리한다.
# wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_wsgi_application() # wsgi.py에 정의된 application 객체
wsgi.py에 정의된 application 객체는 WSGIHandler 인스턴스이다. WSGIHandler 객체는 wsgi 서버로부터 요청 정보 (환경변수)와 결과를 반환할 콜백함수 등을 입력받는다. 입력된 요청 정보를 장고 애플리케이션에 구현된 로직에 따라 처리한 후에 콜백함수를 통해 결과를 반환한다.
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()
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware() # 내부적으로 request를 처리할 미들웨어들을 미리 로드한다.
# environ은 request 정보를 가지고 있다.
# start_response는 wsgi 서버에서 응답을 준비하기 위한 콜백함수이다.
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) # request가 입력되었을때 미리 로드한 미들웨어 체인을 통해 처리하고 결과를 반환한다.
` # ...
# 결과를 콜백함수로 전달
start_response(status, response_headers)
# ...
return response
4. ASGI
ASGI는 Asynchronous Server Gateway Interface의 줄임말이다. 이름에서 알 수 있듯이 비동기 기능을 지원하는 파이썬 표준 통신이다. 웹소켓 통신과 같이 비동기 기능이 필요한 애플리케이션을 동작하는데 적합하다.
내부에서는 wsgi 와 동일하게 callble object를 제공하여 asgi 서버가 입력된 요청을 처리할 수 있도록 한다. asgi를 지원하는 서버로는 uvicorn 등이 있다.
- uvicorn
uvicorn 서버의 설치와 장고 프로젝트의 asgi 애플리케이션 실행은 아래의 예제와 같다.
python -m pip install uvicorn // uvicorn 설치
python -m uvicorn myproject.asgi:application // Django project myproject의 asgi 애플리케이션 실행
- Django ASGI
WSGI와 동일하게 ASGI 애플리케이션도 장고 프로젝트 내부에 미리 정의되어 있다. wsgi 애플리케이션과 동일하게 application 이라는 객체를 제공한다.
# asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_asgi_application()
asgi.py 에 정의된 application 객체는 ASGIHandler 객체이다. ASGIHandler 객체는 asgi 서버로부터 입력받은 요청을 비동기 형식으로 처리한다. 아래의 코드에서 볼 수 있듯이 handle() 이라는 함수에서 요청을 처리하도록 하는데, 내부에서 asyncio를 사용하여 비동기 task를 생성하여 요청을 처리한다.
비동기 방식으로 요청을 처리해야하기 때문에 내부적으로 좀 더 복잡하다. 쓰레드풀에 대한 처리나 asyncio를 이용하여 작업을 실행하고 그 결과를 처리하는 부분이 wsgi와 차이가 있다.
class ASGIHandler(base.BaseHandler):
"""Handler for ASGI requests."""
request_class = ASGIRequest
# Size to chunk response bodies into for multiple response messages.
chunk_size = 2**16
def __init__(self):
super().__init__()
self.load_middleware(is_async=True)
async def __call__(self, scope, receive, send):
"""
Async entrypoint - parses the request and hands off to get_response.
"""
# Serve only HTTP connections.
# FIXME: Allow to override this.
if scope["type"] != "http":
raise ValueError(
"Django can only handle ASGI/HTTP connections, not %s." % scope["type"]
)
async with ThreadSensitiveContext():
await self.handle(scope, receive, send)
async def handle(self, scope, receive, send):
"""
Handles the ASGI request. Called via the __call__ method.
"""
# ...
async def process_request(request, send):
response = await self.run_get_response(request)
try:
await self.send_response(response, send)
except asyncio.CancelledError:
# Client disconnected during send_response (ignore exception).
pass
return response
# Try to catch a disconnect while getting response.
tasks = [
# Check the status of these tasks and (optionally) terminate them
# in this order. The listen_for_disconnect() task goes first
# because it should not raise unexpected errors that would prevent
# us from cancelling process_request().
asyncio.create_task(self.listen_for_disconnect(receive)),
asyncio.create_task(process_request(request, send)),
]
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
# Now wait on both tasks (they may have both finished by now).
for task in tasks:
# ...
try:
response = tasks[1].result()
except asyncio.CancelledError:
await signals.request_finished.asend(sender=self.__class__)
else:
await sync_to_async(response.close)()
body_file.close()
[Reference]
- https://kangbk0120.github.io/articles/2022-02/cgi-wcgi-asgi
- https://wsgi.readthedocs.io/en/latest
- https://asgi.readthedocs.io/en/latest
'프로그래밍언어 > Python' 카테고리의 다른 글
[Python] Default Argument Value - mutable object (2) | 2023.12.02 |
---|---|
[Python] GIL (Global Interpreter Lock) (0) | 2022.07.24 |
[Python] Awaitable (0) | 2022.04.19 |
[Python] Decorator (0) | 2022.04.01 |
[Python] module, package, library (0) | 2022.02.11 |