(十八)用 ASGI 事件表达异步请求
1. scope、receive 与 send
第八章中,协程在 await 时把等待交给事件循环。服务器也可以通过异步接口驱动应用,把请求过程拆成事件;这就是这里要观察的 ASGI 调用。
# 项目文件:demos/asgi_echo.py
# ASGI将请求体按事件交来;应用await接收,再分开发送响应头与响应体。
import json
async def application(scope, receive, send):
chunks = []
while True:
event = await receive()
if event["type"] == "http.disconnect":
return
chunks.append(event.get("body", b""))
if not event.get("more_body", False):
break
value = {
"method": scope["method"],
"path": scope["path"],
"body_bytes": len(b"".join(chunks)),
}
body = json.dumps(value).encode("utf-8")
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [
(b"content-type", b"application/json"),
(b"content-length", str(len(body)).encode("ascii")),
],
}
)
await send({"type": "http.response.body", "body": body})scope 保存方法、路径等连接信息,receive 异步交来请求事件,send 异步发送响应事件。请求体可能分多次到达;more_body 为 True 时继续读取,最后再合并本次正文。收到 disconnect 时停止这次处理。
响应先发 http.response.start,提供状态与头,再发 http.response.body。应用仍然生成相同的 HTTP 响应,变化的是服务器调用 Python 应用的方式。ASGI 的 HTTP 消息
2. 让 ASGI 服务器运行这个入口
# 在web_project中运行,从demos目录导入asgi_echo模块的application。
uv run uvicorn --app-dir ../demos asgi_echo:application --host 127.0.0.1 --port 8771 --lifespan off
模块名:对象名 指定应用,app-dir 指定导入目录。这里的应用专门处理 HTTP 消息,lifespan off 让服务器不向它发送启动、关闭生命周期消息;下一章再把数据库等资源接到生命周期中。
打开 /hello 可以看到方法、路径和请求体字节数。配套还实际发送中文正文“中文”,按 UTF-8 得到 body_bytes 为 6,与 WSGI 示例的字节处理相互对应。