Skip to content

从 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 开发中的位置

ASGI 在 Python Web 开发中的分层架构与角色边界

这里需要区分三个概念:

  • Uvicorn:ASGI 服务器,负责网络通信和协议处理。
  • ASGI:服务器与应用之间共同遵守的接口规范。
  • FastAPI:Web 框架,负责路由、参数校验、依赖注入等应用功能。

因此,ASGI 本身不是服务器,也不是 Web 框架。

ASGI 与 WSGI 的区别

WSGI 是 Python Web 开发中更早出现的接口规范,Flask 等框架传统上使用 WSGI。

对比WSGIASGI
全称Web Server Gateway InterfaceAsynchronous Server Gateway Interface
编程模型同步接口支持异步接口
HTTP支持支持
WebSocket原生不支持支持
长连接不适合更适合
典型框架FlaskFastAPI、Starlette
典型服务器Gunicorn、WaitressUvicorn、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 请求与两段式响应流转机制

原生 ASGI 示例只有一个目标:返回文本。

py
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 中启动:

bash
uvicorn 01_asgi_text_response:app --reload --lifespan off

此例只处理 HTTP,因此关闭 Uvicorn 的 Lifespan 探测。访问 / 会得到 Hello, ASGI。http.response.start 指定状态码和响应头;http.response.body 携带响应体。这里只展示通信方式,日常业务代码通常交给框架处理这些细节。

第一个 Starlette 应用 ​

原生写法需要自己准备消息结构。Starlette 提供应用对象、路由和响应对象,让处理函数集中回答“这个请求要返回什么”。最小 Starlette 示例如下:

py
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)])

先在课程根目录安装依赖:

bash
python -m pip install -e ./_code_starlette

再进入 _code_starlette/p01c01 并启动:

bash
uvicorn 02_first_starlette_app:app --reload

app 是 Starlette 应用对象。Route("/", homepage) 把根路径交给 homepage;处理函数返回 PlainTextResponse,Starlette 负责转换成 ASGI 响应消息。命令中的 02_first_starlette_app:app 分别表示 Python 模块名和模块中的应用对象名。

Starlette 对 ASGI 底层的封装架构

访问 http://127.0.0.1:8000/,响应体为:

text
欢迎学习 Starlette

--reload 会在代码变化时重启开发服务器,适合课堂演示。

多页面课程介绍站 ​

现在需要首页、课程介绍和专题列表三个地址。每个地址有自己的处理函数,再交给对应的 Route。完整案例保留了这一层清晰的对应关系:

py
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,就能看到三段不同文本。新增页面的关键是增加处理函数并注册路由。

Starlette 多页面路由匹配与派发流水线

本专题只使用普通文本,便于把注意力放在请求分发上。后续专题会读取客户端数据,并按需要返回 JSON、HTML 等响应。