注意:该文档依然在更新中,在开发过程中可能发生较大变化。
摘要
该文档旨在描述一个介于网络协议服务和 Python 应用之间的标准接口,能够处理多种通用协议类型,包括 HTTP、HTTP2 和 WebSocket。
它的目的是在 WSGI 上进行拓展,并最终取代它。在设计上还是包含了 WSGI 到 ASGI 以及 ASGI 到 WSGI 的转换器,目的是为了使 HTTP 协议的编写更为容易。
依据
WSGI 规范自诞生以来应用广泛,在作为 Python 框架和 web 服务的选择上拥有非常好的灵活性。但,因为是针对 HTTP 风格的请求响应模型做的设计,加上越来越多不遵循这种模式的协议逐渐成为 web 编程的标准之一,比如说,WebSocket。所以需要新的改变。
ASGI 尝试保持在一个简单的应用接口的前提下,提供允许数据能够在任意时候、被任意应用进程发送和接受的抽象。
它同样描绘了一个新的,兼容 HTTP 请求响应以及 WebSocket 数据帧的序列格式。允许这些协议能通过网络或本地 socket 进行传输,以及让不同的协议被分配到不同的进程进行处理。
总览
ASGI 由三个不同的组件构成:协议服务、频道层(channel layer)、应用代码。频道层是这个实现中最重要的部分,它能同时对协议服务和应用提供接口。
一个频道层对协议服务、应用服务提供一个 send
的可调用方法,该方法接受 channel name
、message dict
以及一个 receive_many
方法作为参数。receive_many
方法接受 channel name
的 list
作为参数,返回指定频道的下一条可用的消息。
所以,相较于在 WSGI 上,我们将协议服务直接指向应用,在 ASGI 里,我们将协议服务和应用同时指向一个频道层的实例。它的目的是让应用服务和协议服务总是运行在不同的进程或者线程中,并通过频道层进行通信。
尽管提议的名字中含有『异步』二字,但 ASGI 并不是只针对异步的解决方案,例如 asyncio
、twisted
或者 gevent
。相反,receive_many
方法能在同步和异步之间切换。这种方式使得应用可以选择适合他们当前运行环境的最佳方案。后续的改进可以提供拥有协作版本(cooperative versions)的 receive_many
方法的拓展。
该文档中对协议服务和应用的区分主要是为了明晰他们要扮演的角色,同时也为了更便于描述概念。这两者之间并没有代码层面的区分,而且完全有可能发生一个进程既是协议服务又是应用的情况。最终大多数的实际操作将遵循这种模式也是期望之中的。
其实是可以增加一个 WSGI 类型的抽象,提供一个接受 channel, message, send_func
参数的方法。但这样在许多的场景里是很受限制的,同时也没有覆盖到如何指定监听的频道名称。这一块就交由框架来实现吧。
频道和消息
在 ASGI 栈里面的所有通信都是通过在频道里发送消息进行的。所有的消息必须是一个 dict
,并且为了保证可序列化,只允许包含以下类型数据:
- 字节串
- Unicode
- 整型(非
long
) - 列表
- 字典(键必须是 Unicode)
- 布尔
- None
频道的 ID 只能由 ASCII 字母、数字及.
、-
、_
,以及一个可选的前缀字符构成(见下文),注意,得是 unicode 类型的。
频道是一个先进先出队列,队列里的项最多被传输一次。它允许多位写入者和多位读取者,当仅有一位读取者时,需要读取每一个写入的消息。实现绝对不能将一条消息传输多次或传输给一位以上读取者,为了保证这一限制,必要时必须清空(drop)所有信息。
为了能够支持可拓展的网络架构,多端读取的频道(例如 http.request
频道,监听每一个应用进程)和单一读取的频道(例如一个 http.response!ABCDE
频道,绑定在一个终端 socket 上)会区别处理。
单端读取频道(Single-reader channel) ,命名中需要包含一个感叹号(!),这么做可以让频道层知道它要将数据通过不同的方式路由给这些频道,以保证数据能到达需要它的痛的奥;这些频道几乎总是与外界来的连接进行绑定。感叹号(!
)总是跟在主频道名称(如 `http.response`)后面,后面跟着每个终端/随机的片段。频道层可以通过取感叹号后面的部分进行路由,如果需要的话,也可以选择忽略,如果它们不需要使用不同的路由规则。
信息如果在一个频道里超过设定时间未读会被过期掉。这个设定时间推荐为一分钟,当然最佳的设定还是取决于频道层和它部署的方式。
消息的大小最大限定为 1 MB;如果要传输的数据大于这个上限,则需要分块(参见 HTTP 请求的内容体处理方式)。所有的频道层都必须遵循此限制。
处理协议
ASGI 信息主要是两个东西,内部应用事件(例如,一个频道可能被用作将之前上传的视频的缩略图存储如队列),以及来自或发往已连接的终端的协议事件。
如此,这份规范概括性地描述了 ASGI 消息是如何对 HTTP 和 WebSocket 进行编码的。这允许 ASGI web 服务器能够和其他 ASGI web 应用进行通信,同理,其他基于共同协议规范实现的服务器和应用也在此列。建议其他变得普遍的协议也应该为自己增补一个标准格式。
消息格式是这份规范的关键,没有它们,协议服务(protocol server)和 web 应用虽然可以进行通信,但不能理解通信的内容。从这个意义上说,消息格式等同于 WSGI 中的 environ
字典里的键(keys)。
设计模式是,大多数协议将共享一些频道,用于接收数据(例如,http.request
, websocket.connect
和 websocket.receive
),但将用独立的频道进行发送数据给每一个终端(例如 http.response!kj2daj23
)。这允许发送来的数据被分发到一个应用服务的集群,于此同时,响应消息(responses)被路由给持有终端的 socket 的单独的协议服务。
有一些协议没有唯一 socket 连接的概念,例如,一个短信网关协议服务可能只有 sms.receive
和 sms.send
,协议服务集群将从 sms.send
获取信息,然后根据信息里的属性(例如电话号码)将他们路由给电话网络。
扩展
对于一些基础的应用代码和几乎全部的协议服务代码不必须的功能,放进了拓展里,这让一些不需要全部特性的应用可以有轻型的频道层。
这里定义了三种拓展:groups
拓展,下面会详细说明;flush
拓展,让测试和开发更加容易;statistics
拓展,为频道层提供全局的和单频道的统计功能。
有可能会增加更多拓展,这些可能会用一份单独的规范进行说明,也可能在本规范的更新版本里增加。
如果应用代码需要一个拓展,它应该尽快对它进行检查,如果没有提供就报错。框架应该鼓励对拓展的可选使用,应尝试在进程启动时进行『拓展没找到』错误的处理,而不应该留到消息处理时。