如果您认为 Python 已被锁定在数据科学领域,think again!Python Web 开发以异步方式回归,这令人兴奋。
现在 Python Web 开发生态系统中发生了很多令人兴奋的事情——这项工作的主要驱动力之一是ASGI,异步服务器网关接口。
我已经在这里多次提到 ASGI,特别是在宣布 Bocadillo和tartiflette-starlette 时,但我从来没有真正花时间写一篇关于它的全面介绍。好吧,我们来了!
这篇文章的目标读者是对 Python Web 开发的最新趋势感兴趣的人。我想带您了解什么是 ASGI以及它对现代 Python Web 开发的意义。
在开始之前,我想宣布我最近创建了awesome-asgi,这是一个很棒的列表,可以帮助人们跟踪不断扩展的 ASGI 生态系统。🙌
您可以观看发布以收到列表中新条目的通知。👀
好吧,让我们潜入吧!
一切都始于 async/await
与 JavaScript 或 Go 不同,Python 并不是一种从一开始就内置异步执行的语言。长期以来,在 Python 中并发执行事物只能使用多线程或多处理,或求助于专门的网络库,如 eventlet、gevent 或 Twisted。(早在2008年,扭曲已经有了异步协同程序,例如,在形式的APIinlineCallbacks
和deferredGenerator
。)
但这一切在 Python 3.4+ 中都改变了。Python 3.4添加asyncio
到标准库中,在生成器和yield from
语法之上添加了对协作多任务的支持。
后来,async
/await
语法被添加到 Python 3.5 中。多亏了这一点,我们现在拥有独立于底层实现的原生协程,这开启了 Python 并发的淘金热。
真是太匆忙了!自从 3.5 发布以来,社区一直在异步化所有的东西。如果你很好奇,很多由此产生的项目现在都列在aio-libs和awesome-asyncio 中。
好吧,你猜对了——这也意味着Python 网络服务器和应用程序正在转向 async。事实上,所有nice的应用都在这样做!(即使Django。)
ASGI 概述
现在,ASGI 如何适应所有这些?
从 1000 英尺的角度来看,ASGI 可以被认为是允许 Python 异步服务器和应用程序相互通信的粘合剂。它与WSGI共享许多设计思想,并且经常作为其精神继承者呈现,并内置异步。
下面是这个心智模型在图表中的样子:
但实际上,情况比这要复杂一些。
要了解 ASGI 的真正工作原理,让我们看一下ASGI 规范:
ASGI 由两个不同的组件组成:
- 一个协议服务器,它终止套接字并将它们转换为连接和每个连接的事件消息。
- 位于协议服务器内的应用程序在每个连接中被实例化一次,并在事件消息发生时对其进行处理。
所以根据规范,ASGI 真正指定的是消息格式以及这些消息应该如何在应用程序和运行它的协议服务器之间交换。
我们现在可以将图表修改为更详细的版本:
显然,还有更多有趣的细节需要查看。例如,您可以查看HTTP 和 WebSocket 规范。
此外,尽管规范非常关注服务器到应用程序的通信,但事实证明 ASGI 包含的远不止这些。
我们将在一分钟内解决这个问题,但首先......
ASGI基础知识
现在我们已经了解了 ASGI 如何融入 Python Web 生态系统,让我们仔细看看它在代码中的样子。
ASGI 依赖于以下思维模型:当客户端连接到服务器时,我们实例化一个应用程序。然后,我们将传入的字节输入应用程序,并将输出的字节发回。
“输入应用程序”在这里的真正意思是调用应用程序,就好像它是一个函数,即接受一些输入并返回输出的东西。
事实上,这就是 ASGI 应用程序的全部内容——一个可调用的. 这个可调用对象的形状再次由 ASGI 规范定义。这是它的样子:
async def app(scope, receive, send):
...
这个函数的签名就是 ASGI 中的“I”所代表的:应用程序必须实现的接口,服务器才能调用它。
让我们来看看 3 个参数:
scope
是一个字典,其中包含有关传入请求的信息。它的内容因HTTP和WebSocket连接而异。receive
是一个异步函数,用于接收 ASGI 事件消息。send
是一个异步函数,用于发送 ASGI 事件消息。
从本质上讲,这些参数允许您通过协议服务器维护的通信通道receive()
和send()
数据,以及知道scope
该通道是在什么上下文(或)中创建的。
我不了解你,但这个界面的整体外观和形状非常适合我的大脑。无论如何,代码示例的时间。
Show me the code!!
为了更实际地了解 ASGI 的样子,我创建了一个最小的项目,它展示了一个由uvicorn(一种流行的 ASGI 服务器)提供的原始 ASGI HTTP 应用程序:
在这里,我们使用send()
向客户端发送 HTTP 响应:我们首先发送标头,然后是响应正文。
现在,有了所有这些字典和原始二进制数据,我承认裸机 ASGI 使用起来不是很方便。
幸运的是,还有更高级别的选项——这就是我要谈论Starlette 的时候。
Starlette 确实是一个了不起的项目,而 IMO 是 ASGI 生态系统的基础部分。
简而言之,它提供了一个包含更高级别组件(例如请求和响应)的工具箱,您可以使用它们来抽象出 ASGI 的一些细节。来,看看这个 Starlette “Hello, World!” 应用程序:
# app.py
from starlette.responses import PlainTextResponse
async def app(scope, receive, send):
assert scope["type"] == "http"
response = PlainTextResponse("Hello, world!")
await response(scope, receive, send)
Starlette 确实拥有您对实际 Web 框架所期望的一切——路由、中间件等。但我决定展示这个精简版,以向您暗示 ASGI 的真正力量,即……
Turtles all the way down
ASGI有趣且彻底改变游戏规则的一点是“ Turtles all way down ”的概念。(这个表达最初是(我认为?)由 Andrew Godwin 创造的,他是 Django 迁移、Django 异步改造和一些 ASGI 故事的幕后推手。)但这究竟是什么意思?
嗯,因为ASGI是允许告诉我们的背景下,以及接收和发送数据的抽象在任何时候,有这种想法,ASGI可用于不仅服务器和应用程序之间,但真正在堆栈中的任何一点。
例如,StarletteResponse
对象本身就是一个 ASGI 应用程序。事实上,我们可以从早期剥离下来Starlette示例应用程序仅此:
# app.py
app = PlainTextResponse("Hello, world!")
这有多可笑?!😍
但是等等,还有更多。
“海龟一路向下”的更深层次的后果是,我们可以构建各种应用程序、中间件、库等项目,并且只要它们都实现了ASGI应用程序接口,就可以确保它们是可互操作的。
(此外,根据我自己构建Bocadillo的经验,经常(如果不是总是)拥抱 ASGI 接口会产生更清晰的代码。)
例如,我们可以构建一个 ASGI 中间件(即包装另一个应用程序的应用程序)来显示请求被提供的时间:
# app.py
import time
class TimingMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
start_time = time.time()
await self.app(scope, receive, send)
end_time = time.time()
print(f"Took {end_time - start_time:.2f} seconds")
为了使用它,我们将它包裹在一个应用程序中......
# app.py
import asyncio
from starlette.responses import PlainTextResponse
async def app(scope, receive, send):
await asyncio.sleep(1)
response = PlainTextResponse("Hello, world!")
await response(scope, receive, send)
app = TimingMiddleware(app)
......它会神奇地起作用。
$ uvicorn app:app
INFO: Started server process [59405]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
...
INFO: ('127.0.0.1', 62718) - "GET / HTTP/1.1" 200
Took 1.00 seconds
令人惊奇的是,它TimingMiddleware
可以包装任何ASGI 应用程序。这里的内部应用程序非常基础,但它可能是一个成熟的、真实的项目(想想数百个 API 和 WebSocket 端点)——没关系,只要它与 ASGI 兼容即可。
(这种时间中间件有一个更适合生产的版本:timing-asgi。)
我为什么要在乎?
虽然我认为互操作性是一个强大的卖点,但使用基于 ASGI 的组件来构建 Python Web 应用程序还有更多优势。
- 速度:ASGI 应用程序和服务器的异步特性使它们非常快(至少对于 Python 而言)——我们谈论的是 60k-70k req/s(考虑到 Flask 和 Django 在类似情况下只能达到 10-20k)。
- 特性:ASGI 服务器和框架使您可以访问使用同步/WSGI 无法实现的固有并发特性(WebSocket、服务器发送事件、HTTP/2)。
- 稳定性:ASGI 作为一个规范已经存在了大约 3 年,并且 3.0 版本被认为非常稳定。因此,生态系统的基础部分正在稳定下来。
在库和工具方面,我不认为我们可以说,我们在那里只是还没有。但是感谢一个非常活跃的社区,我非常希望 ASGI 生态系统能尽快与传统的同步/WSGI 生态系统实现功能平等。
我在哪里可以找到与 ASGI 兼容的组件?
事实上,越来越多的人正在构建和改进围绕 ASGI 构建的项目。这显然包括服务器和 Web 框架,但也包括中间件和面向产品的应用程序,例如Datasette。
我最感兴趣的一些非 Web 框架组件是:
- Mangum:AWS Lambda 的 ASGI 支持
- datasette-auth-github:ASGI 应用程序的 GitHub 身份验证
- tartiflette-starlette(我写了这个!):ASGI 支持 Tartiflette,一种异步 GraphQL 引擎。
虽然看到生态系统的蓬勃发展很棒,但我个人一直很难跟上一切。
这就是为什么,正如本文开头所宣布的,我创建了awesome-asgi。我希望它可以帮助每个人跟上 ASGI 空间中发生的所有令人敬畏的事情。(而且看到几天之内几乎达到了100颗星,我感觉确实需要将ASGI资源进行本地化。)
包起来
虽然它看起来像是一个实现细节,但我确实认为 ASGI 为 Python Web 开发的新时代奠定了基础。
如果您想了解有关 ASGI 的更多信息,请查看 中列出的各种出版物(文章和演讲)awesome-asgi
。要弄脏您的手,请尝试以下任何项目:
- uvicorn : ASGI 服务器。
- Starlette:ASGI 框架。
- TypeSystem:数据验证和表单渲染
- 数据库:异步数据库库。
- orm : 异步 ORM。
- HTTPX:异步 HTTP 客户端,支持调用 ASGI 应用程序(用作测试客户端)。
这些项目由 Encode 构建和维护,这主要是指 Tom Christie。有关于建立Encode 维护团队的公开讨论,所以如果您正在寻找机会帮助推进开源利基市场,那么就来吧!
享受你的 ASGI 之旅。❣️