异步服务器网关接口

This chapter contains information about using ASGI with Pyramid. 阅读有关 ASGI specification .

下面的示例应用程序使用来自 asgiref library 将普通的WSGI请求转换为ASGI响应。这允许应用程序与ASGI服务器一起运行,例如 uvicorndaphne .

wsgi->asgi应用程序

此示例使用由提供的包装器 asgiref 将wsgi应用程序转换为asgi,允许由asgi服务器运行。

请注意,可能不支持wsgi的所有扩展功能,例如传入post主体的文件句柄。

 1# app.py
 2
 3from asgiref.wsgi import WsgiToAsgi
 4from pyramid.config import Configurator
 5from pyramid.response import Response
 6
 7def hello_world(request):
 8    return Response("Hello")
 9
10# Configure a normal WSGI app then wrap it with WSGI -> ASGI class
11
12with Configurator() as config:
13    config.add_route("hello", "/")
14    config.add_view(hello_world, route_name="hello")
15    wsgi_app = config.make_wsgi_app()
16
17app = WsgiToAsgi(wsgi_app)

扩展的wsgi->asgi websocket应用程序

此示例扩展了 asgiref 支持将ASGi使用者与转换后的WSGi应用程序一起路由的包装器。这只是路由ASGi消费者的一个潜在解决方案。

  1# app.py
  2
  3from asgiref.wsgi import WsgiToAsgi
  4
  5from pyramid.config import Configurator
  6from pyramid.response import Response
  7
  8
  9class ExtendedWsgiToAsgi(WsgiToAsgi):
 10
 11    """Extends the WsgiToAsgi wrapper to include an ASGI consumer protocol router"""
 12
 13    def __init__(self, *args, **kwargs):
 14        super().__init__(*args, **kwargs)
 15        self.protocol_router = {"http": {}, "websocket": {}}
 16
 17    async def __call__(self, scope, *args, **kwargs):
 18        protocol = scope["type"]
 19        path = scope["path"]
 20        try:
 21            consumer = self.protocol_router[protocol][path]
 22        except KeyError:
 23            consumer = None
 24        if consumer is not None:
 25            await consumer(scope, *args, **kwargs)
 26        await super().__call__(scope, *args, **kwargs)
 27
 28        if consumer is not None:
 29            await consumer(scope, *args, **kwargs)
 30        try:
 31            await super().__call__(scope, *args, **kwargs)
 32        except ValueError as e:
 33            # The developer may wish to improve handling of this exception.
 34            # See https://github.com/Pylons/pyramid_cookbook/issues/225 and
 35            # https://asgi.readthedocs.io/en/latest/specs/www.html#websocket
 36            pass
 37        except Exception as e:
 38            raise e
 39
 40
 41    def route(self, rule, *args, **kwargs):
 42        try:
 43            protocol = kwargs["protocol"]
 44        except KeyError:
 45            raise Exception("You must define a protocol type for an ASGI handler")
 46
 47        def _route(func):
 48            self.protocol_router[protocol][rule] = func
 49
 50        return _route
 51
 52
 53HTML_BODY = """<!DOCTYPE html>
 54<html>
 55    <head>
 56        <title>ASGI WebSocket</title>
 57    </head>
 58    <body>
 59        <h1>ASGI WebSocket Demo</h1>
 60        <form action="" onsubmit="sendMessage(event)">
 61            <input type="text" id="messageText" autocomplete="off"/>
 62            <button>Send</button>
 63        </form>
 64        <ul id='messages'>
 65        </ul>
 66        <script>
 67            var ws = new WebSocket("ws://127.0.0.1:8000/ws");
 68            ws.onmessage = function(event) {
 69                var messages = document.getElementById('messages')
 70                var message = document.createElement('li')
 71                var content = document.createTextNode(event.data)
 72                message.appendChild(content)
 73                messages.appendChild(message)
 74            };
 75            function sendMessage(event) {
 76                var input = document.getElementById("messageText")
 77                ws.send(input.value)
 78                input.value = ''
 79                event.preventDefault()
 80            }
 81        </script>
 82    </body>
 83</html>
 84"""
 85
 86# Define normal WSGI views
 87def hello_world(request):
 88    return Response(HTML_BODY)
 89
 90# Configure a normal WSGI app then wrap it with WSGI -> ASGI class
 91with Configurator() as config:
 92    config.add_route("hello", "/")
 93    config.add_view(hello_world, route_name="hello")
 94    wsgi_app = config.make_wsgi_app()
 95
 96app = ExtendedWsgiToAsgi(wsgi_app)
 97
 98# Define ASGI consumers
 99@app.route("/ws", protocol="websocket")
100async def hello_websocket(scope, receive, send):
101    while True:
102        message = await receive()
103        if message["type"] == "websocket.connect":
104            await send({"type": "websocket.accept"})
105        elif message["type"] == "websocket.receive":
106            text = message.get("text")
107            if text:
108                await send({"type": "websocket.send", "text": text})
109            else:
110                await send({"type": "websocket.send", "bytes": message.get("bytes")})
111        elif message["type"] == "websocket.disconnect":
112            break

运行与部署

可以使用asgi服务器运行应用程序:

$ uvicorn app:app

$ daphne app:app

有几个潜在的部署选项,一个例子是使用 nginxsupervisor . 下面是运行应用程序的示例配置文件 uvicorn 然而 daphne 也可以使用。

nginx配置示例

 1upstream app {
 2    server unix:/tmp/uvicorn.sock;
 3}
 4
 5server {
 6
 7    listen 80;
 8    server_name <server-name>;
 9
10    location / {
11        proxy_pass http://app;
12        proxy_set_header Host $host;
13        proxy_set_header X-Real-IP $remote_addr;
14        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
15        proxy_set_header X-Forwarded-Proto $scheme;
16        proxy_buffering off;
17        proxy_http_version 1.1;
18        proxy_set_header Upgrade $http_upgrade;
19        proxy_set_header Connection "Upgrade";
20        proxy_redirect off;
21    }
22
23    location /static {
24      root </path-to-static>;
25    }
26}

Example Supervisor configuration

1[program:asgiapp]
2directory=/path/to/app/
3command=</path-to-virtualenv>/bin/uvicorn app:app --uds /tmp/uvicorn.sock --workers 2 --access-log --log-level error
4user=<app-user>
5autostart=true
6autorestart=true
7redirect_stderr=True
8
9[supervisord]