ASGI翻译系列(一): 你好,ASGI

说说为什么要写这个系列。在新项目Python Web框架的选项中,我选择了我之前没有使用过的Fastapi,这是一次新的尝试,当然在这次用之前老早的时候我就看过这个框架,并被其吸引住了,感觉很有意思。至目前为止,框架也用了一两个月了,愈发觉得其typing+asynico的特性吊爆了,正如某些人所说,Fastapi绝对是Python Web框架上历程上的一个里程碑!当然这次的翻译系列中,不说Fastapi,先来说说与其息息相关的ASGI

原文地址

译者注:原文代码的使用的ASGI2协议,当时接口采用的是双重调用,在2019年3月20日,ASGI3.0发布,改进了调用风格,虽然向下兼容,但是原文的例子有些还是没法用了,所以我这边做了更新统一采用ASGI3写法并适当进行增删改。

具体可以看ASGI3.0

1. 前言

本文介绍了新兴的 ASGI 标准,以及它为 Python Web 框架带来的好处。

当前 Python 环境的最新变化之一是关注异步编程模型,并在语言中引入了 async/await 语法。

异步模型使用轻量级的基于任务的切换,在runtime时管理显式的切换点和流控制。这与基于线程的并发相反,后者资源更加密集,依赖于隐式切换,由操作系统管理。

更多的现代runtime(如 Go 和 Node)是基于异步模型的。作为一种相对古老的语言,Python 不得不逐步适应它,这既带来了机遇,也带来了挑战。

其中一个挑战是 Python Web 框架如何适应异步模型的潜在好处。我们已经在最近涌入的高性能 Python Web 框架中看到了这一点。

译者注:runtime 根据使用语境有两种含义,一个是单纯的字面意思,指程序运行的时候。另一个貌似是指支撑程序运行所需的环境,包括比如系统性的变量、其他系统级的辅助程序等。

2. WSGI 综述

长期以来,Python领域的Web框架,包括Django、Flask、Falcon、Pyramid和Bottle都是基于WSGI的框架。

WSGI是一个标准,它在Server实现和Application实现之间提供了一个形式化的接口,处理原始socket处理的琐碎细节。

在这里有一个形式化的接口是非常重要的,因为它在这两个方面之间提供了一个适当的分离,并允许服务器实现独立于框架或应用程序实现而发展。

Python有很多WSGI服务器,包括Gunicorn、uWSGI、Apache/mod_wsgi和Waitress。

我不会在本文中深入研究WSGI接口的具体细节,但我们可以快速看看如何运行一个基本“Hello, World” 的Web服务。

example. py:

1
2
3
4
5
6
def hello_world(environ, start_response):
data = b"Hello, World!\n"
start_response("200 OK", [
("Content-Type", "text/plain"),
])
return [data]

现在我们可以在任何WSGI服务器上运行我们的hello world应用程序。

$ gunicorn example:hello_world
WSGI已经取得了巨大的成功,它几乎被普遍采用,这证明了Server/application接口的重要性,但它有几个限制,如果不进行重大改革,这些限制是不容易克服的。

首先,WSGI必须是一个基于线程或greenlet的接口,无法支持async/await模式。WSGI本质上无法利用异步编程的潜在优势。

还有一点,WSGI严格来说是一个HTTP接口,不能轻易地调整为提供对WebSockets或其他网络协议的支持。

2. 谈谈ASGI

这就是 ASGI 的用武之地。

ASGI规范是一个从WSGI迭代下来的但又基本算是一个重新设计,它提供了一个异步 server/application接口,支持HTTP、HTTP/2和WebSockets。

作为对比,这里有一个ASGI的“Hello, World”的 Web服务例子。

example.py:

原文采用的ASGI2双重调用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class HelloWorld:
def __init__(self, scope):
pass

async def __call__(self, receive, send):
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!',
})

$ uvicorn example:HelloWorld

同样,在本文中我不会去讨论规范的具体内容,但在接口中需要注意的两个关键点:

  • __call__方法中的async语法明确地将其标记为一个可能的任务切换点 ,并允许在该执行上下文中使用其他异步代码。
  • receivesend参数,server和application之间的消息传递是通过这些参数进行的

而最新的ASGI3例子,更简单明了:

1
2
3
4
5
6
7
8
9
10
11
12
async def app(scope, receive, send):
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!',
})

$ uvicorn example:app

这种基于async/await的接口与WSGI相比有很多优势。

3. 性能

在代码主要是网络绑定而非计算绑定的情况下,异步代码能够获得比基于线程的并发更高的吞吐量。基于任务的并发的资源高效切换意味着成千上万的轻量级任务可以同时执行,而不需要基于线程切换的高开销。这意味着每个单一的服务器或实例能够支持更多的流量。

Python框架对于异步方面某些类别的用例来说是一个糟糕的候选者,而Node或Go等的异步能力则表现出色。

ASGI规范为Python提供了一个机会,使其能够在广泛的用例中达到生产力/性能的最佳状态,从编写大容量的代理服务器到将大规模的Web应用程序快速推向市场。

例如,这里是TechEmpower基准测试的最新结果,过滤到一些动态语言(Python、JavaScript、Ruby、PHP、Perl),全部使用Postgres后端。

随着 Starlette 和 Uvicorn 位居榜首,Python 在这些基准测试中的性能与 Node 相当。

4. 支持WebSockets和长效HTTP连接

ASGI直接支持WebSockets,允许开发人员构建响应速度更快的Web应用程序,以及其他不适合HTTP的高度交互式服务。

它还可以用于支持 HTTP 长轮询或 HTTP 服务器发送事件(HTTP Server Sent Events) ,允许服务器到客户端的单向更新。

5. 进程中的后台任务

ASGI提供的消息模型意味着它能够在HTTP响应发送后继续运行代码。这使得框架能够提供轻量级的进程内后台任务,而不需要支持完整的任务队列的基础设施。

6. 可扩展性

WSGI提出的接口是 “用HTTP请求调用这个函数,并等待HTTP响应返回”,而ASGI规范则提出了一个更广泛的可扩展接口,即 “这里有一对通道,服务器和应用程序可以在这两个通道之间进行通信”。

这种更广泛的模式有可能被扩展到HTTP或WebSockets之外的其他协议中

7. 我们现在在哪里?

有许多可用于生产的 ASGI 服务器:

daphne - 最初的ASGI服务器。最初是为Django channels 开发的。
uvicorn - 一个高性能的ASGI服务器,基于uvloop和httptools实现。
hypercorn - 一个功能齐全的ASGI服务器,包括支持HTTP/2服务器推送等功能。

其中每一个都有一个已建立的生产使用的度量标准,所有这三个都有助于改进 ASGI 规范。

还有一些不同的ASGI框架。

channels - Django为了支持WebSocket而生。在基于异步服务器的实现上提供了一个基于线程的应用上下文。
starlette - Starlette被设计成一个功能齐全的Web框架,或者用作与和 ASGI 一起工作的工具包。一个高性能的框架,其功能集的范围类似于 Sanic 或 Werkzeug。
quart - 一个Python ASGI网络微框架,具有与Flask相同的API。

ASGI还为现有的Web框架提供了一个可管理的升级路径,以提供异步支持,因为它很好地映射到WSGI上,同时增加了额外的功能。

Werkzeug/Flask的维护者以及Falcon团队都发出了积极的声音。Django团队也有考虑将ASGI支持引入核心框架。

还有一些基于异步的框架的维护者提供了初步的支持,包括 Sanic。

8. 继续这个系列

我在这里的开发工作主要集中在 ASGI 服务器、 Uvicorn 和 ASGI 框架 Starlette 上。

在接下来的几周里,我将更详细地介绍如何使用 ASGI,包括如何使用它构建基于 websocket 的应用程序、 HTTP 代理服务器、 ASGI 中间件等等。

我们的下一篇文章将开始更详细地介绍在 ASGI 中使用 HTTP。