ASGI는 WSGI에 async를 얹은 것이 아니라 양방향 메시지 스트림 모델로 재설계된 것이다
·수정 2026.05.04·수정 1회
요약
- ASGI의 본질은 async가 아니라 양방향 메시지 스트림 모델이다. async는 결과일 뿐.
- WSGI는
(environ, start_response) → iterable의 1회성 request-response 모델, ASGI는(scope, receive, send)의 N개 메시지 스트림 모델이다. - 이 차이 때문에 WebSocket/SSE/HTTP/2 streaming/lifespan 같은 개념을 WSGI는 표현 못하고 ASGI는 자연스럽게 다룬다.
본문
인터페이스 비교
WSGI:
def app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello"]
- 함수 1회 호출 → 응답 반환 → 끝
- 입력:
environdict 한 덩어리 - 출력: iterable body
ASGI:
async def app(scope, receive, send):
await send({"type": "http.response.start", "status": 200, "headers": [...]})
await send({"type": "http.response.body", "body": b"Hello"})
- 입력:
scope(메타데이터) +receive/send(메시지 큐) - 메시지를 여러 번 주고받는 스트림
결정적 차이: 메시지 스트림
ASGI는 receive/send를 원하는 만큼 await 할 수 있다.
async def app(scope, receive, send):
while True:
msg = await receive()
if msg["type"] == "websocket.receive":
await send({"type": "websocket.send", "text": "echo: " + msg["text"]})
→ WebSocket, SSE, HTTP/2 streaming, long-polling이 자연스럽게 표현된다. WSGI에는 이 모델 자체가 없다.
scope의 type 필드: 멀티 프로토콜 지원
scope["type"] # "http", "websocket", "lifespan"
http— HTTP 요청websocket— WebSocket 연결lifespan— 앱 startup/shutdown 이벤트
WSGI는 HTTP만, lifespan 개념도 없다.
비유로 정리
| WSGI | ASGI | |
|---|---|---|
| 비유 | 편지 (보내고 답장 받고 끝) | 전화 (양방향 대화) |
| 메시지 수 | 요청 1, 응답 1 | N개 (양쪽 다) |
| 프로토콜 | HTTP만 | HTTP + WebSocket + lifespan |
| 시그니처 | (environ, start_response) |
(scope, receive, send) |
호환성의 비대칭
- WSGI 앱 → ASGI 환경에서 실행: 가능 (
asgiref.wsgi.WsgiToAsgi로 wrap, 단 동기 핸들러는 그대로 동기) - ASGI 앱 → WSGI 환경에서 실행: 불가능. ASGI의 메시지 스트림을 WSGI의 1회성 모델로 표현 못함
이 비대칭이 "ASGI = WSGI 상위호환" 같은 인상을 주지만, 실제로는 다른 모델이다.
핵심 통찰
ASGI = WSGI + async (X) ASGI = 양방향 메시지 스트림 + async (O)
async는 양방향 스트림을 자연스럽게 표현하기 위한 수단이지, ASGI의 정의 자체가 async인 것은 아니다.