AberSheeran
Aber Sheeran
I know nothing except the fact of my ignorance.

Responder挂载WSGI程序

起笔自
所属文集: 杂记
共计 2595 个字符
落笔于

首先需要感谢Django-Channels团队提出的ASGI协议,但Django-Channels实在是不好用,相对来说,它的副产品(ASGI)更加让人心动。而responder则是众多ASGI框架中的佼佼者。

使用responder去驱动支持WSGI的项目,将得到的效果——组合任意的Django、Flask、Battle……项目;为WSGI项目提供更优雅的Websocket支持;更好的性能(或许)。

挂载其他Application

responder文档可以看到一个简单的例子

import responder
from flask import Flask

api = responder.API()
flask = Flask(__name__)

@flask.route('/')
def hello():
    return 'hello'

api.mount('/flask', flask)

flask = Flask(__name__)创建了一个支持WSGI的app,而api.mount类似于flask的蓝图,django的子路由,它可以将指定前缀的URI分配到对应的app中,这个app只需要是支持WSGI或者ASGI的应用程序即可。

实现原理

先看看挂载其他应用程序所调用的mount方法:

self.apps.update({route: app})

只有一行代码。其中self.apps是一个dict

由于API() 实现了一个标准的ASGI应用程序,所以可以通过API.__call__的代码来查看responder项目被启动时,如何调用的。

async def __call__(self, scope, receive, send):
    if scope["type"] == "lifespan":
        await self.lifespan_handler(scope, receive, send)
        return

    path = scope["path"]
    root_path = scope.get("root_path", "")

    # Call into a submounted app, if one exists.
    for path_prefix, app in self.apps.items():
        if path.startswith(path_prefix):
            scope["path"] = path[len(path_prefix) :]
            scope["root_path"] = root_path + path_prefix
            try:
                await app(scope, receive, send)
                return
            except TypeError:
                app = WSGIMiddleware(app)
                await app(scope, receive, send)
                return

    await self.app(scope, receive, send)

可以看出,responder在启动时,先匹配了所有挂载的应用程序,最后匹配自己。

所以要注意,挂载的时候如果使用了api.mount("", application)这种方式,那么它会匹配到所有路由的请求。

在WSGI程序里使用Websocket

以Django为例,传统的使用方式是使用Django-Channels,你需要以一种侵入式的方式配置各种东西。但如果使用上述的挂载方式,则能够更加优雅的使用Websocket。

Django中使用Websocket

在Django项目的根目录下(与manage.py同级)创建名为application.py的文件,写入以下内容

from responder import API

from djangoresponder.wsgi import application

app = API()


@app.route(f"/sayhello", websocket=True)
async def home(websocket):
    await websocket.accept()
    while True:
        name = await websocket.receive_text()
        await websocket.send_text(f"Hello {name}!")
    await websocket.close()

app.mount("/api", application)


if __name__ == "__main__":
    app.run(port=8000)

本地启动时不再需要使用python manage.py runserver,只需要直接在命令行里运行application.py文件即可。

然后试着用一些能连Websocket的工具去连接ws://127.0.0.1:8000/sayhello,你会发现一切正常。那么再在浏览器里试试http://127.0.0.1/api/,你将能看到自己的Django项目提供的服务!

由于Uvicorn的支持,当你在uvloop支持的系统上直接运行application.py时,它仍然拥有良好的性能足以支撑中小企业的业务。但更好的部署方式是gunicorn、docker之类的更现代化的工具,在此不多赘述,可自行查看官方文档

如果你觉得本文值得,不妨赏杯茶
Python的元类
没有下一篇