在前面的 Flask 示例中,我们用 Gunicorn(WSGI 服务器)来部署应用。没错,这就是当年的“老黄历”:一种标准化的方式,让像 Flask、Django 这样的框架与服务器通信。虽然有很多可用的 WSGI 服务器,但它们都没有为异步工作负载而设计——毕竟,WSGI 规范诞生的年代,asyncio 还不存在呢。随着异步框架变得越来越流行,业界急需一个能解耦框架与服务器的新标准。于是,异步服务器网关接口(ASGI)应运而生。虽然这个概念很新,但已经是各大热门项目争相拥抱的协议,包括 Django。
回想下,当初为啥要发明 WSGI?那时候的开发者面临着应用框架五花八门的局面。不同框架各自为政,导致选择某一框架就意味着要受限于配套的服务器。为了解决这个问题,开发者们推出了简单明了的 WSGI 协议,提供了一个统一的接口,让服务器可以顺利跟任何符合规范的框架通信。这套机制自 2004 年被正式接纳为标准(参考 PEP-333),成为了如今的事实标准。
但麻烦的是,这个“经典”的模式对异步不友好。核心原因在于,它的设计本质上就是一个简单的纯函数。来看一个最简的 WSGI 应用:
def application(env, start_response): start_response('200 OK', [('Content-Type','text/html')]) return [b"WSGI hello!"]
我们可以用 Gunicorn 运行它:gunicorn chapter_09.listing_9_6,再用 curl http://127.0.0.1:8000 测一下。你会发现,里面根本找不到 await 的身影。更严重的是,它只支持一次性请求/响应周期,完全不支持像 WebSocket 这样长连接的协议。
ASGI 正是对这些问题的一次革命性改造,它用协程替换了原有的函数模型。我们来把上面的例子迁移到 ASGI:
async def application(scope, receive, send): await send({ 'type': 'http.response.start', 'status': 200, 'headers': [[b'content-type', b'text/html']] }) await send({'type': 'http.response.body', 'body': b'ASGI hello!'})
注意,ASGI 的应用函数有三个参数:一个 scope 字典(描述请求上下文)、一个 receive 协程(用来接收消息)、一个 send 协程(用来发送消息)。在这个例子中,我们分两步发出了响应:先是头部,再是主体。
那该怎么运行这个应用?目前有很多 ASGI 实现,我们推荐使用 Uvicorn(官网(https://www.uvicorn.org/))。它基于高效的 uvloop 事件循环和 httptools 解析器,性能惊人。安装方法:
pip install -Iv uvicorn==0.14.0
uvicorn chapter_09.listing_9_7:application
访问 http://localhost:8000,你就能看到“hello”了。虽然我们这里是直接用 Uvicorn 测的,但在生产环境中,更好的做法是让 Gunicorn 作为主控,用 UvicornWorker 来工作。这样做,如果某个子进程崩溃,Gunicorn 能自动重启它。我们稍后在介绍 Django 时会详谈这一点。
最后提醒一点:虽然 WSGI 已经被正式接纳为标准(PEP-333),但 ASGI 目前还没有,它仍然是一个相对新生的事物。大家也都知道,随着 asyncio 地基不断演变,未来它也可能继续迭代和调整。但我们现在的重点是弄明白它干了什么,以及如何上手。