ASGI Interface
ASGI는 단일 비동기 호출 가능한 객체로 구조화된다.
이 객체는 scope
, send
, receive
를 받는다.
scope
: 특정 연결에 대한 세부 정보를 담고 있음. (dict형식)send
: 클라이언트에게 이벤트 메시지를 보내는 비동기 호출 가능 객체 (콜백)receive
: 클라이언트로부터 이벤트 메시지를 받는 비동기 호출 가능 객체 (콜백)
ASGI에서 주고받는 이벤트는 딕셔너리 형식이며, 미리 정의된 구조를 따른다. 각 이벤트는 type 키를 가지며, 이를 통해 이벤트의 성격을 파악할 수 있다 (예: HTTP, WebSocket 등).
이러한 형식으로 ASGI는 다양한 이벤트들이 들락날락하는걸 처리할 수 있을 뿐만 아니라, 다른 작업을 처리하는 백그라운드 코루틴(예: Redis 큐)도 동시에 수행할 수 있다.
가장 간단한 비동기 함수 애플리케이션은 다음과 같다.
# function-based
async def application(scope, receive, send):
event = await recevie()
...
await send({'type': 'websocket.send', ...})
# instance-based
class App:
async def __call__(self, scope, receive, send):
assert scope['type'] == 'http'
app = App()
보내거나 받는 이벤트는 dict
이고, 미리 정의된 형식을 가진다.
이벤트들은 각각 정의된 type
키를 가지며, 이것을 가지고 이벤트의 구조를 유추할 수 있다. (e.g: http, websocket …)
recevie
에서 받을 수 있는 HTTP 요청의 본문을 포함하는 이벤트는 다음과 같으며, type
키를 볼 수 있다.
{
"type": "http.request",
"body": b"Hello, World",
"more_body": False
}
{
"type": "websocket.send",
"text": "Hello, World!"
}
들어오는 HTTP 요청은 연결 scope 를 다음과 같이 가지고있다.
{
'type': 'http',
'scheme': 'http',
'root_path': '',
'server': ('127.0.0.1', 8000),
'http_version': '1.1',
'method': 'GET',
'path': '/',
'headers': [
(b'host', b'127.0.0.1:8000'),
(b'user-agent', b'curl/7.51.0'),
(b'accept', b'*/*')
]
}
이 때, send
코루틴을 사용하여 서버에 메시지를 보내는데 HTTP 응답을 두 단계로 나누어 처리한다.
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
]
})
await send({
'type': 'http.response.body',
'body': b'Hello, world!',
})
이렇게 두 단계로 나누어 처리하면 응답 본문이 크거나 스트리밍이 필요한 경우에 유용하다.
예를 들어, 큰 파일을 전송하거나 실시간으로 데이터를 생성하는 경우에는 http.response.body
메시지를 여러 번 보내서 데이터를 분할 전송할 수 있다.