接前面WSGI入门笔记,继续了解一下ASGI。
ASGI:Asynchronous Server Gateway Interface 异步服务器网关接口。ASGI是WSGI在异步方向的延续?为具有异步功能的Python Web服务器、框架 和 应用之间提供一个标准接口。
不同于WSGI由PEP规范;ASGI由Django团队提出,用于开发Django Channels,以便于支持异步应用,并支持websocket协议和HTTP/2等。
ASGI 3.0是2019年3月4日发布的,本文只关注3.0。
简介
根据ASGI协议,ASGI分为两部分:
- Protocol Server
- Application
我们主要关注Application端
例子
一个简单ASGI 3.0的application:
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来执行它
而后通过浏览器访问 127.0.0.1:8000 地址即可
也可以直接写代码,然后运行程序:
| if __name__ == '__main__':
from uvicorn import run
run(app)
|
概念
不同于WSGI,一个ASGI连接有两个部分:
- connection scope:代表一个客户的连接,在连接断开前始终存在
- event:表示当连接(connection)有事情发生时发送到 Application 中的信息,以及由 Application 发送给 Server 的消息(其中包括传送给 Client 的的数据)。
接口参数
看一看例子中出现的三个参数
scope
一个信息字典,由于ASGI可用于包括HTTP在内的多种协议,这个字典中最重要的键是type
。它应该代表连接生命期。
在上面的例子中,我们可以加上一句
| async def app(scope, receive, send):
print(scope)
...
|
使用firefox访问页面127.0.0.1:8000时,控制台输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | {
'type': 'http',
'asgi': {'version': '3.0', 'spec_version': '2.3'},
'http_version': '1.1',
'server': ('127.0.0.1', 8000),
'client': ('127.0.0.1', 62321),
'scheme': 'http',
'method': 'GET',
'root_path': '',
'path': '/',
'raw_path': b'/',
'query_string': b'',
'headers': [
(b'host', b'127.0.0.1:8000'),
(b'user-agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0'),
(b'accept', b'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'),
(b'accept-language', b'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2'),
(b'accept-encoding', b'gzip, deflate, br'),
(b'connection', b'keep-alive'),
(b'upgrade-insecure-requests', b'1'),
(b'sec-fetch-dest', b'document'),
(b'sec-fetch-mode', b'navigate'),
(b'sec-fetch-site', b'none'),
(b'sec-fetch-user', b'?1')
],
'state': {}
}
|
send
接受单个消息参数并返回None的异步可调用对象。在HTTP情况下,用于发送HTTP响应。
如例子所示,有两种类型HTTP响应消息:
- 一种用于初始化发送响应
http.response.start
- 一种用于发送响应正文
http.response.body
这和WSGI的拆成的start_reponse
和返回值一致的:
| #WSGI
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello World</h1>']
|
receive
和send一样,receive也是一个异步的可调用对象,允许运行时被await挂起。
不带参数,返回一条消息。在HTTP情况下,用于返回HTTP请求正文。
| async def app(scope, receive, send):
recv = await receive()
print (recv)
...
|
控制台输出结果:
| {'type': 'http.request', 'body': b'', 'more_body': False}
|
WebSocket
一个包含websocket的例子
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | html = b'''
<!DOCTYPE HTML>
<html>
<head>
<script>
var socket = new WebSocket("ws://127.0.0.1:8000");
socket.onopen = function(event) {
socket.send("Hello from websocket client!");
};
socket.onmessage = function(event) {
sss = document.createElement("p");
sss.innerHTML = event.data;
document.body.appendChild(sss);
};
</script>
</head>
<body>
WebSocket Example
</body>
</html>
'''
async def app(scope, receive, send):
if scope['type'] == 'http':
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/html'],
],
})
await send({
'type': 'http.response.body',
'body': html,
})
elif scope['type'] == 'websocket':
await send({
'type': 'websocket.accept',
})
while True:
event = await receive()
if event['type'] == 'websocket.receive':
await send({
'type': 'websocket.send',
'text': event['text'],
})
elif event['type'] == 'websocket.disconnect':
break
|
运行前,需要安装支持websocket的ASGI服务器,比如 uvicorn[standard],
| pip install 'uvicorn[standard]'
|
Starlette
Starlette是一个轻量的ASGI框架、工具集,是在Python下创建异步web服务的理想选择。
使用它的话,一个ASGI程序就可以变成:
| from starlette.responses import PlainTextResponse
async def app(scope, receive, send):
response = PlainTextResponse('Hello, world!')
await response(scope, receive, send)
|
再进一步
使用路由机制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | from starlette.applications import Starlette
from starlette.responses import JSONResponse, PlainTextResponse
from starlette.routing import Route
async def home(request):
return JSONResponse({'hello': 'world'})
async def debao(request):
return PlainTextResponse('Hello Debao')
app = Starlette(debug=True, routes=[
Route('/', home),
Route('/debao', debao),
])
|
对于json数据,在firefox浏览器看到页面(JSON,原始数据,头):
ASGI服务器
可用于生产的ASGI服务器
- daphne:最初的ASGI服务器,最初为Django channels开发
- uvicorn:高性能的ASGI服务器,基于uvloop和httptools实现
- hypercorn:ASGI服务器,支持HTTP/2推送等
参考
- https://asgi.readthedocs.io/en/latest/introduction.html
- https://www.aeracode.org/2019/03/20/asgi-30/
- https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface
- https://peps.python.org/pep-3333/
- https://github.com/florimondmanca/awesome-asgi
- https://florimond.dev/en/posts/2019/08/introduction-to-asgi-async-python-web
- https://www.starlette.io