Django3 使用 WebSocket 实现 WebShell

作者:从零开始的程序员生活
来源:
https://www.cnblogs.com/lgjbky/p/15186188.html
前言最近工作中需要开发前端操作远程虚拟机的功能,简称 WebShell 。基于当前的技术栈为 react+django,调研了一会发现大部分的后端实现都是 django+channels 来实现 websocket 服务 。
大致看了下觉得这不够有趣,翻了翻 django 的官方文档发现 django 原生是不支持 websocket 的,但 django3 之后支持了 asgi 协议可以自己实现 websocket 服务 。
于是选定 gunicorn+uvicorn+asgi+websocket+django3.2+paramiko 来实现 WebShell 。
实现 websocket 服务使用 django 自带的脚手架生成的项目会自动生成 asgi.py 和 wsgi.py 两个文件,普通应用大部分用的都是 wsgi.py 配合 Nginx 部署线上服务 。
这次主要使用 asgi.py 实现 websocket 服务的思路大致网上搜一下就能找到,主要就是实现
connect/send/receive/disconnect 这个几个动作的处理方法 。
这里 How to Add Websockets to a Django App without Extra Dependencies(
https://jaydenwindle.com/writing/django-websockets-zero-dependencies/) 就是一个很好的实例,但过于简单……
思路# asgi.pyimport osfrom django.core.asgi import get_asgi_applicationfrom websocket_app.websocket import websocket_applicationos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'websocket_app.settings')django_application = get_asgi_application()async def application(scope, receive, send):if scope['type'] == 'http':await django_application(scope, receive, send)elif scope['type'] == 'websocket':await websocket_application(scope, receive, send)else:raise NotImplementedError(f"Unknown scope type {scope['type']}")# websocket.pyasync def websocket_application(scope, receive, send):pass# websocket.pyasync def websocket_application(scope, receive, send):while True:event = await receive()if event['type'] == 'websocket.connect':await send({'type': 'websocket.accept'})if event['type'] == 'websocket.disconnect':breakif event['type'] == 'websocket.receive':if event['text'] == 'ping':await send({'type': 'websocket.send','text': 'pong!'})实现上面的代码提供了思路,比较完整的可以参考这里 websockets-in-django-3-1 (
https://aliashkevich.com/websockets-in-django-3-1/) 基本可以复用了 。
其中最核心的实现部分我放下面:
class WebSocket:def __init__(self, scope, receive, send):self._scope = scopeself._receive = receiveself._send = sendself._client_state = State.CONNECTINGself._app_state = State.CONNECTING@propertydef headers(self):return Headers(self._scope)@propertydef scheme(self):return self._scope["scheme"]@propertydef path(self):return self._scope["path"]@propertydef query_params(self):return QueryParams(self._scope["query_string"].decode())@propertydef query_string(self) -> str:return self._scope["query_string"]@propertydef scope(self):return self._scopeasync def accept(self, subprotocol: str = None):"""Accept connection.:param subprotocol: The subprotocol the server wishes to accept.:type subprotocol: str, optional"""if self._client_state == State.CONNECTING:await self.receive()await self.send({"type": SendEvent.ACCEPT, "subprotocol": subprotocol})async def close(self, code: int = 1000):await self.send({"type": SendEvent.CLOSE, "code": code})async def send(self, message: t.Mapping):if self._app_state == State.DISCONNECTED:raise RuntimeError("WebSocket is disconnected.")if self._app_state == State.CONNECTING:assert message["type"] in {SendEvent.ACCEPT, SendEvent.CLOSE}, ('Could not write event "%s" into socket in connecting state.'% message["type"])if message["type"] == SendEvent.CLOSE:self._app_state = State.DISCONNECTEDelse:self._app_state = State.CONNECTEDelif self._app_state == State.CONNECTED:assert message["type"] in {SendEvent.SEND, SendEvent.CLOSE}, ('Connected socket can send "%s" and "%s" events, not "%s"'% (SendEvent.SEND, SendEvent.CLOSE, message["type"]))if message["type"] == SendEvent.CLOSE:self._app_state = State.DISCONNECTEDawait self._send(message)async def receive(self):if self._client_state == State.DISCONNECTED:raise RuntimeError("WebSocket is disconnected.")message = await self._receive()if self._client_state == State.CONNECTING:assert message["type"] == ReceiveEvent.CONNECT, ('WebSocket is in connecting state but received "%s" event'% message["type"])self._client_state = State.CONNECTEDelif self._client_state == State.CONNECTED:assert message["type"] in {ReceiveEvent.RECEIVE, ReceiveEvent.DISCONNECT}, ('WebSocket is connected but received invalid event "%s".'% message["type"])if message["type"] == ReceiveEvent.DISCONNECT:self._client_state = State.DISCONNECTEDreturn message


推荐阅读