1. 什么是WSGI
1.1 CGI
解释 WSGI
之前应该先说一下什么是 CGI
(通用网关接口,Common Gateway Interface,CGI),是Web 服务器运行时外部程序的规范 , 是外部扩展应用程序与 Web 服务器交互的一个标准接口。 CGI规范定义了Web服务器如何向扩展应用程序发送消息,在收到扩展应用程序的信息后又如何进行处理等内容。对于许多静态的HTML网页无法实现的功能,通过 CGI可以实现,比如表单的处理、对数据库的访问、搜索引擎、基于Web的数据库访问等等。使用CGI实现客户端与服务器的交互有以下几个标准步骤 :
(1)Web 客户端的浏览器将URL的第一部分解码与Web服务器相连。
(2)Web 浏览器将URL的其余部分提供给服务器。
(3)Web 服务器将URL转换成路径和文件名。
(4)Web 服务器发送 HTML 和别的组成请求页面的文件给客户。一旦页面内容传送完,这个连接自动断开。
(5)在客户端,HTML脚本提示用户做动作或输入。当用户响应后,客户请求Web服务器建立一个新的连接。
(6)Web 服务器把这些信息和别的进程变量传送给由HTML以URL的形式指定CGI程序。
(7)CGI 根据输入作出响应,把响应结果传送给 Web 服务器。
(8)Web 服务器把响应的数据传给客户,完成后关闭连接。
1.2 WSGI
WSGI
(Web服务网关接口,Python Web Server Gateway Interface,缩写为WSGI) 是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口 。 WSGI 没有官方的实现, 因为WSGI更像一个协议。只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行 。 它是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的 。
实现了WSGI 协议的 服务器有:uWSGI、uvicorn、gunicorn。像Django框架生产环境一般就不会使用runserver来运行,而是采用上面实现了WSGI协议的服务器来运行。
Django 中运行 runserver 命令时,其实内部就启动了wsgiref模块作为Web服务器运行的,它的性能比较低下。
我的博客:nginx+uWSGI + django部署项目一篇中也有介绍。
1.3 Web服务器
Web服务器(Web Server)是一种运行于网站后台(物理服务器)的软件。Web服务器主要用于提供网页浏览或文件下载服务,它可以向浏览器等Web客户端提供html网页文档,也可以提供其他类型的可展示文档,让客户端用户浏览;还可以提供数据下载等,
目前业内主流的Web服务器有Nginx、Apache、IIS、Tomcat。
1.4 Web应用程序
上图写作Python程序
Web应用程序是一种能完成Web业务逻辑,能让用户基于Web浏览器访问的应用程序,它可以是一个实现http请求和响应功能的函数或者类,也可以是Django、Flask、tornado等这样的web框架,当然,也可以是其他语言的Web程序或Web框架。
Web服务器和Web应用程序的区别:
- Web应用程序主要是完成Web应用业务逻辑的处理;
- Web服务器则主要应对外部请求的接收、响应、和转发。
需要使用Web服务器启动运行,Web应用程序才能倍用户访问到。
而Django框架中我们之所以只有一个Web应用程序就跑起来,是因为我们在终端执行了一个命令,python manage.py runserver 。这个命令启动了Django框架中内置提供的测试Web服务器(这个内置服务器功能较差)。
2. 什么是ASGI
ASGI(异步服务器网关接口)是 WSGI 的继承者,旨在**提供具有异步能力的 Python Web 服务器、框架和应用程序之间的标准接口。 **
ASGI 被构造为一个单一的、异步的可调用对象。它需要一个scope
,它dict
包含有关特定连接的详细信息 send
,一个异步可调用对象,它允许应用程序向客户端发送事件消息,以及receive
一个异步可调用对象,它允许应用程序从客户端接收事件消息。
这不仅允许每个应用程序有多个传入事件和传出事件,而且还允许后台协程,以便应用程序可以做其他事情(例如侦听外部触发器上的事件,如 Redis 队列)。
以最简单的形式,应用程序可以编写为异步函数,如下所示:
async def application(scope, receive, send):event = await receive()...await send({"type": "websocket.send", ...})
您发送或接收的每个事件都是一个 Python dict
,具有预定义的格式。正是这些事件格式构成了标准的基础,并允许应用程序在服务器之间进行交换。
这些事件每个都有一个定义的type
键,可用于推断事件的结构。以下是您可能receive
从 HTTP 请求的正文中接收到的示例事件 :
{"type": "http.request","body": b"Hello World","more_body": False,
}
这是您可能传递send
给发送传出 WebSocket 消息的事件示例:
{"type": "websocket.send","text": "Hello world!",
}
2.1 ASGI 规范
2.1.1 抽象的
网络协议服务器(尤其是 Web 服务器)和 Python 应用程序之间的标准接口,旨在允许处理多种常见的协议样式(包括 HTTP、HTTP/2 和 WebSocket)。
这个基本规范旨在修复这些服务器交互和运行应用程序代码的 API 集;每个支持的协议(例如 HTTP)都有一个子规范,概述了如何将该协议编码和解码为消息。
2.1.2 基本原理
WSGI 规范自推出以来一直运行良好,并为 Python 框架和 Web 服务器选择提供了极大的灵活性。然而,它的设计不可撤销地与 HTTP 风格的请求/响应周期相关联,越来越多的不遵循这种模式的协议正在成为 Web 编程的标准部分(最显着的是 WebSocket)。
ASGI 试图保留一个简单的应用程序接口,同时提供一个抽象,允许随时从不同的应用程序线程或进程发送和接收数据。
它还采用将协议转换为 Python 兼容、异步友好的消息集的原则,并将其概括为两部分;用于构建服务器的标准化通信接口,以及用于每个协议的一组标准消息格式。
然而,它的主要目标是提供一种方法来编写 HTTP/2 和 WebSocket 代码以及正常的 HTTP 处理代码;这个设计的一部分意味着确保有一个简单的路径来使用现有的 WSGI 服务器和应用程序,因为绝大多数 Python web 使用依赖于 WSGI,并且提供一个简单的前进路径对于采用至关重要。有关该互操作性的详细信息包含在 ASGI-HTTP 规范中。
2.1.3 概述
ASGI 由两个不同的组件组成:
- 一个协议服务器,它终止套接字并将它们转换为连接和每个连接的事件消息。
- 位于协议服务器 内的应用程序,每个连接调用一次,并在事件消息发生时处理它们,并在必要时发送它自己的事件消息。
与 WSGI 一样,服务器在其中托管应用程序,并以标准化格式将传入请求分派给它。然而,与 WSGI 不同,应用程序是异步可调用对象而不是简单的可调用对象,它们通过接收和发送异步事件消息而不是接收单个输入流并返回单个可迭代对象与服务器进行通信。ASGI 应用程序必须作为async
/await
兼容的协程运行 (即asyncio
-compatible)(在主线程上;如果需要同步代码,它们可以自由使用线程或其他进程)。
与 WSGI 不同,ASGI 连接有两个独立的部分:
- 一个***连接范围***,它代表与用户的协议连接,并在连接关闭之前一直存在。
- 事件,即连接上发生的事情时发送到应用程序的消息,以及应用程序发回以供服务器接收的消息,包括要传输到客户端的数据。
应用程序通过一个连接scope
和两个可等待的可调用对象来调用和等待receive
事件消息和send
事件消息返回。所有这些都发生在一个异步事件循环中。
应用程序 callable 的每次调用都映射到单个传入的“套接字”或连接,并且如果需要清理,预计会持续该连接的生命周期加上更长的时间。某些协议可能不使用传统套接字;预计这些协议的 ASGI 规范将定义范围生命周期是什么以及何时关闭。
2.2 规格详情
2.2.1 连接范围
用户与 ASGI 应用程序的每个连接都会导致调用可调用的应用程序来完全处理该连接。这个存在多久,以及描述每个特定连接的信息,称为 连接范围。
密切相关的是,传递给可调用应用程序的第一个参数是一个 scope
字典,其中包含描述该特定连接的所有信息。
例如,在 HTTP 下,连接范围只持续一个请求,但scope
传递的包含大部分请求数据(除了 HTTP 请求正文,因为这是通过事件流式传输的)。
但是,在 WebSocket 下,只要套接字已连接,连接范围就会持续。而scope
通过包含类似的WebSocket的路径信息, 但是诸如传入消息之类的细节以事件的形式传递 。
某些协议可能会预先为您scope
提供非常有限的信息,因为它们封装了诸如握手之类的内容。每个协议定义必须包含有关其连接范围持续多长时间的信息,以及您将在scope
参数中获得哪些信息。
根据协议规范,应用程序在与客户端通信之前可能必须等待初始打开消息。
2.2.2 事件
ASGI 将协议分解为应用程序必须 接收和响应的一系列事件,以及应用程序可能发送的响应事件。对于 HTTP,这就像按顺序接收两个事件一样简单-http.request
和 http.disconnect
,然后 发送 回相应的事件消息。对于像 WebSocket 这样的东西,它可能更像是接收 websocket.connect
、 发送一个 websocket.send
、接收一个 websocket.receive
、最后 接收一个 websocket.disconnect
。
每个事件dict
都有一个顶级type
键,其中包含消息类型的 Unicode 字符串。用户可以自由创造他们自己的消息类型,并在应用程序实例之间为高级事件发送它们 - 例如,聊天应用程序可能会发送用户类型为 mychat.message
. 应用程序应该能够处理一组混合的事件,一些来自传入的客户端连接,一些来自应用程序的其他部分。
因为这些消息可以通过网络发送,所以它们需要可序列化,因此它们只允许包含以下类型:
- 字节串
- Unicode 字符串
- 整数(在有符号的 64 位范围内)
- 浮点数(在 IEEE 754 双精度范围内;无
Nan
或无穷大) - 列表(元组应编码为列表)
- 字典(键必须是 Unicode 字符串)
- 布尔值
None
2.2.3 应用
ASGI 应用程序应该是单个异步可调用的:
coroutine application(scope, receive, send)
scope
: 连接范围信息,一个字典,至少包含一个type
指定传入协议的 键receive
: 一个可等待的可调用对象,当一个可用的事件字典可用时将产生一个新的事件字典send
: 一个可等待的可调用对象,将单个事件字典作为位置参数,一旦发送完成或连接关闭,它将返回
每个“连接”都会调用一次应用程序。连接的定义及其寿命由相关协议规范决定。例如,对于 HTTP,它是一个请求,而对于 WebSocket,它是单个 WebSocket 连接。
scope
您发送和接收的事件消息的类型和格式均由应用程序协议之一定义。scope
必须是 dict
. 密钥scope["type"]
将始终存在,并可用于确定传入的协议。密钥 scope["asgi"]
也将作为包含scope["asgi"]["version"]
对应于服务器实现的 ASGI 版本的密钥的字典出现 。如果缺少,版本应默认为"2.0"
.
也可能有一个特定于规范的版本作为 scope["asgi"]["spec_version"]
. 这允许单独的协议规范在不影响整个 ASGI 版本的情况下进行增强。
特定于协议的子规范涵盖了这些范围和事件消息格式。它们等同environ
于 WSGI 字典中的键规范。
3. WSGI和ASGI的区别
WSGI succeeded in allowing much more freedom and innovation in the Python web space, and ASGI’s goal is to continue this onward into the land of asynchronous Python.
You may ask “why not just upgrade WSGI”? This has been asked many times over the years, and the problem usually ends up being that WSGI’s single-callable interface just isn’t suitable for more involved Web protocols like WebSocket.
WSGI applications are a single, synchronous callable that takes a request and returns a response; this doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections.
Even if we made this callable asynchronous, it still only has a single path to provide a request, so protocols that have multiple incoming events (like receiving WebSocket frames) can’t trigger this.
WSGI 成功地在 Python 网络空间中提供了更多的自由和创新,而 ASGI 的目标是将这一点继续推进到异步 Python 的领域。
你可能会问“为什么不升级 WSGI”?多年来,这个问题已经被问过很多次了,问题通常最终是 WSGI 的单一可调用接口不适合更多涉及的 Web 协议,如 WebSocket。
WSGI 应用程序是一个单一的、同步的可调用对象,它接受一个请求并返回一个响应;这不允许长期连接,就像使用长轮询 HTTP 或 WebSocket 连接一样。
即使我们使这个可调用的异步,它仍然只有一个路径来提供请求,因此具有多个传入事件(如接收 WebSocket 帧)的协议无法触发它.