Date

ASGI (Asynchronous Server Gateway Interface) Specification

需要注意,我阅读的是 Version: 2.0 (2017-11-28)。ASGI 在先前版本是有一个 channel 层的,现在被拿到外面去了。上一个版本有 中文翻译

目的

ASGI 的目的是提出一种规范,用于处理 Web 服务器和 Python 应用程序间的多种常见协议格式(HTTP,HTTP/2,WebSocket)

依据

WSGI 为 Python 框架和 Web Server 的选择提供了极大的灵活性。然而它的设计与 HTTP 的请求和响应绑在了一起。目前一些非此格式的协议也逐渐占领重要的地位,比如 WebSocket。ASGI 尝试去保留一个极简的应用接口。但提供一个能够从不同应用线程/进程随时发送/接收数据的抽象。

它通过将协议转划分为两部分。一部分是用于通信的标准化接口,另一部分是每种协议的标准消息格式。另外此设计的另一部分是确保有一个简单有效的途径来使用现有的 WSGI Server 和应用程序。

概览

ASGI 有两个不同的组件:

  • 协议服务器(protocol server):将 socket 转换为连接和事件消息(event message)
  • 应用(application):位于协议服务器内部,每个连接实例化一次,会对事件消息进行处理

与 WSGI 不同,ASGI 的连接有两个独立部分:

  • 连接作用域(connection scope)
  • 事件(events)

实例化的应用仅存活在连接的生命周期内。

实现细节

连接作用域(Connection Scope)

从用户到 ASGI 应用的每个连接都将导致为该连接创建一个应用实例。此应用实例能存活多久,创建时会得到什么信息,这些便被称为连接作用域(Connection Scope)。比如在 HTTP 协议下,连接作用域就是一个请求。但在 WebSocket 下,连接作用域从 socket 连接持续到 socket 关闭。作用域中包含了 WebSocket 路径等信息,传入的消息则会以事件的形式出现。

scope 和 WSGI 中的 environ 有点类似

需要注意

Applications cannot communicate with the client when they are initialized and given their connection scope; they must wait until their event loop is entered, and depending on the protocol spec, may have to wait for an initial opening message.

事件(Events)

ASGI 将协议分解为应用必须响应的多个事件。对于 HTTP 来说便是 http.requesthttp.disconnect。而对于 WebSocket 来说则是 websocket.connectwebsocket.receivewebsocket.receivewebsocket.disconnect

事件是 dict 类型。它在最外层级包含一个 type 与对应消息类型(str类型)的键值对。用户可以自定义新的消息类型。

由于消息可能通过网络发送,所以需要支持序列化。因此仅允许包含以下类型:

  • Byte strings
  • Unicode strings
  • Integers (within the signed 64 bit range)
  • Floating point numbers (within the IEEE 754 double precision range, no Nan or infinities)
  • Lists (tuples should be encoded as lists)
  • Dicts (keys must be unicode strings)
  • Booleans
  • None
应用(Applications)

用类来表示最简单,不过也可以 callable 返回 callable 的形式

class Application:

    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        ...

每当有新的连接到达协议服务器(protocol server)时,就会创建一个新的应用序实例

  • scope 参数为连接作用域。也是一个包含类型键值对的 dict
  • receive, an awaitable callable that will yield a new Event when one is available
  • send, an awaitable callable that will return once the send has been completed
协议规范(Protocol Specfications)

HTTP and WebSocket

扩展(Extensions)

通过在 scope 中添加 extensions 项实现扩展

Taking Django Asyc

PyCon 2018 的一个视频,演讲者是 Channel 的核心开发者 Andrew Godwin

Channel 1.0 的架构

Channel-1.0

Channel 2.0 的架构

  • Asyncio-navtive
  • Python 3.5 and up
  • Supports async coroutines and sync threads

Channel-2.0

因为 Python 中的 async 和 sync api 的割裂,所以 Channel 的开发面临了两个问题

  • sync to async
  • async to sync

sync to async

解决方法是 run in threads

可以使用 concurrent.futures.ThreadPoolExecutor

loop = asyncio.get_event_loop()
future = loop.run_in_executor(
    self.threadpool,
    func,
)
return await future

这种方式用于

  • Calling the ORM
  • Rendering templates
  • Handling off to Django views

async to sync

通过利用 main thread 中的 loop 去运行,所以这里实际上饶了一圈

sub thread -> main thread -> sub thread

Python 允许你在每个线程中去创建一个 loop,但这样做效率不好

此部分在视频的 17:46

How to make WSGI async?

Channel 使用了ASGI 而不是 WSGI

Do we want to have everyone writing async?

Channel 的设计理念是你可以写 sync 也可以写 async 的代码

所以 Channel 的设计是这样的

Channel-arch

Both async and sync code are useful!