从 ASGI 到 Starlette 应用
用户访问一个地址,服务端返回一句话。这件事看起来简单,却已经包含 Web 应用最基本的过程:客户端发起请求,服务器把请求交给应用,应用构造响应,再由服务器送回客户端。本专题先看清这条路径,再把一个小网站运行起来。
学完后,你应能解释 ASGI 服务器和 Starlette 各做什么,读懂 scope、receive、send 的用途,并独立启动一个包含多个访问路径的应用。
ASGI 是什么
ASGI(Asynchronous Server Gateway Interface,异步服务器网关接口)是 Python Web 生态中的一种通信协议规范,用来定义 Web 服务器与 Python Web 应用之间如何交互。简单来说,ASGI 解决的是:
Web 服务器收到 HTTP 请求后,怎样把请求交给 Python 程序处理,并将处理结果返回给客户端?
FastAPI、Starlette 等框架都基于 ASGI。
下面是 ASGI 在 Web 开发中的位置
这里需要区分三个概念:
- Uvicorn:ASGI 服务器,负责网络通信和协议处理。
- ASGI:服务器与应用之间共同遵守的接口规范。
- FastAPI:Web 框架,负责路由、参数校验、依赖注入等应用功能。
因此,ASGI 本身不是服务器,也不是 Web 框架。
ASGI 与 WSGI 的区别
WSGI 是 Python Web 开发中更早出现的接口规范,Flask 等框架传统上使用 WSGI。
| 对比 | WSGI | ASGI |
|---|---|---|
| 全称 | Web Server Gateway Interface | Asynchronous Server Gateway Interface |
| 编程模型 | 同步接口 | 支持异步接口 |
| HTTP | 支持 | 支持 |
| WebSocket | 原生不支持 | 支持 |
| 长连接 | 不适合 | 更适合 |
| 典型框架 | Flask | FastAPI、Starlette |
| 典型服务器 | Gunicorn、Waitress | Uvicorn、Hypercorn |
需要注意,ASGI 不代表一定比 WSGI 快。对于普通同步业务,性能还取决于数据库、业务逻辑、部署方式等因素。
ASGI 的优势主要体现在异步 I/O、WebSocket 和大量并发连接等场景。
ASGI 请求流程
访问 http://127.0.0.1:8000/ 时,客户端可以是浏览器,也可以是 Python 脚本或其他 HTTP 工具。Uvicorn 负责监听网络请求;Python 应用决定返回什么。两者按 ASGI 约定交换消息。
一个 ASGI 应用是接收 scope、receive、send 的异步可调用对象:
scope提供本次连接的信息,例如类型和访问路径。receive用于接收客户端消息。当前示例没有请求体,所以暂时用不到它。send把响应消息交还给服务器。
原生 ASGI 示例只有一个目标:返回文本。
async def app(scope, receive, send):
if scope["type"] != "http":
return
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain; charset=utf-8")],
})
await send({
"type": "http.response.body",
"body": "Hello, ASGI".encode("utf-8"),
})在 _code_starlette/p01c01 中启动:
uvicorn 01_asgi_text_response:app --reload --lifespan off此例只处理 HTTP,因此关闭 Uvicorn 的 Lifespan 探测。访问 / 会得到 Hello, ASGI。http.response.start 指定状态码和响应头;http.response.body 携带响应体。这里只展示通信方式,日常业务代码通常交给框架处理这些细节。
第一个 Starlette 应用
原生写法需要自己准备消息结构。Starlette 提供应用对象、路由和响应对象,让处理函数集中回答“这个请求要返回什么”。最小 Starlette 示例如下:
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def homepage(request):
return PlainTextResponse("欢迎学习 Starlette")
app = Starlette(routes=[Route("/", homepage)])先在课程根目录安装依赖:
python -m pip install -e ./_code_starlette再进入 _code_starlette/p01c01 并启动:
uvicorn 02_first_starlette_app:app --reloadapp 是 Starlette 应用对象。Route("/", homepage) 把根路径交给 homepage;处理函数返回 PlainTextResponse,Starlette 负责转换成 ASGI 响应消息。命令中的 02_first_starlette_app:app 分别表示 Python 模块名和模块中的应用对象名。
访问 http://127.0.0.1:8000/,响应体为:
欢迎学习 Starlette--reload 会在代码变化时重启开发服务器,适合课堂演示。
多页面课程介绍站
现在需要首页、课程介绍和专题列表三个地址。每个地址有自己的处理函数,再交给对应的 Route。完整案例保留了这一层清晰的对应关系:
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def homepage(request):
return PlainTextResponse("欢迎来到 Starlette 小课")
async def about(request):
return PlainTextResponse("这是一门从 ASGI 基础开始的小课")
async def lessons(request):
return PlainTextResponse("课程专题:请求、响应、路由与完整应用")
app = Starlette(routes=[
Route("/", homepage),
Route("/about", about),
Route("/lessons", lessons),
])在 _code_starlette/p01c01 中运行 uvicorn 03_course_intro_site:app --reload,分别访问 /、/about 和 /lessons,就能看到三段不同文本。新增页面的关键是增加处理函数并注册路由。
本专题只使用普通文本,便于把注意力放在请求分发上。后续专题会读取客户端数据,并按需要返回 JSON、HTML 等响应。