首页
你的位置:999拍拍拍视频完整版-免费免费毛播放片dddd-拍拍拍拍完 > 首页 > Django3 行使 WebSocket 实现 WebShell
Django3 行使 WebSocket 实现 WebShell
2021-11-29 13:01    点击次数:174

 引言

比来做事中必要开发前端操作长途虚拟机的功能,简称 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就是一个很益的实例,但过于浅易……

思路 
# asgi.py   import os  from django.core.asgi import get_asgi_application  from websocket_app.websocket import websocket_application  os.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.py  async def websocket_application(scope, receive, send):      pass  
# websocket.py  async 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':              break          if event['type'] == 'websocket.receive':              if event['text'] == 'ping':                  await send({                      'type': 'websocket.send',                      'text': 'pong!'                  }) 
实现

上面的代码挑供了思路

其中最中央的实现片面吾放下面:

class WebSocket:      def __init__(self, scope, receive, send):          self._scope = scope          self._receive = receive          self._send = send          self._client_state = State.CONNECTING          self._app_state = State.CONNECTING      @property      def headers(self):          return Headers(self._scope)      @property      def scheme(self):        return self._scope["scheme"]      @property      def path(self):        return self._scope["path"]      @property      def query_params(self):          return QueryParams(self._scope["query_string"].decode())      @property      def query_string(self) -> str:          return self._scope["query_string"]      @property      def scope(self):          return self._scope      async 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.DISCONNECTED              else:                  self._app_state = State.CONNECTED          elif 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.DISCONNECTED          await 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.CONNECTED          elif 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.DISCONNECTED          return message 
缝相符怪

做为相符格的代码搬运工,为了挑高搬运效果照样要造点轮子填点坑的,如何将上面的 WebSocket 类与 paramiko 结相符首来,实现以前端批准字符传递给长途主机,并同时批准返回呢?

import asyncio  import traceback  import paramiko  from webshell.ssh import Base, RemoteSSH  from webshell.connection import WebSocket   class WebShell:      """清理 WebSocket 和 paramiko.Channel,实现两者的数据互通"""      def __init__(self, ws_session: WebSocket,                   ssh_session: paramiko.SSHClient = None,                   chanel_session: paramiko.Channel = None                   ):          self.ws_session = ws_session          self.ssh_session = ssh_session          self.chanel_session = chanel_session      def init_ssh(self, host=None, port=22, user="admin", passwd="admin@123"):          self.ssh_session, self.chanel_session = RemoteSSH(host, port, user, passwd).session()      def set_ssh(self, ssh_session, chanel_session):          self.ssh_session = ssh_session          self.chanel_session = chanel_session      async def ready(self):          await self.ws_session.accept()      async def welcome(self):          # 展现Linux迎接有关内容          for i in range(2):              if self.chanel_session.send_ready():                  message = self.chanel_session.recv(2048).decode('utf-8')                  if not message:                      return                  await self.ws_session.send_text(message)      async def web_to_ssh(self):          # print('--------web_to_ssh------->')          while True:              # print('--------------->')              if not self.chanel_session.active or not self.ws_session.status:                  return              await asyncio.sleep(0.01)              shell = await self.ws_session.receive_text()              # print('-------shell-------->', shell)              if self.chanel_session.active and self.chanel_session.send_ready():                  self.chanel_session.send(bytes(shell, 'utf-8'))              # print('--------------->', "end")      async def ssh_to_web(self):          # print('<--------ssh_to_web-----------')          while True:              # print('<-------------------')              if not self.chanel_session.active:                  await self.ws_session.send_text('ssh closed')                  return              if not self.ws_session.status:                  return              await asyncio.sleep(0.01)              if self.chanel_session.recv_ready():                  message = self.chanel_session.recv(2048).decode('utf-8')                  # print('<---------message----------', message)                  if not len(message):                      continue                  await self.ws_session.send_text(message)              # print('<-------------------', "end")      async def run(self):          if not self.ssh_session:              raise Exception("ssh not init!")          await self.ready()          await asyncio.gather(              self.web_to_ssh(),              self.ssh_to_web()          )     def clear(self):          try:              self.ws_session.close()          except Exception:              traceback.print_stack()          try:              self.ssh_session.close()          except Exception:              traceback.print_stack() 
前端

xterm.js 十足已足,搜索下找个望着浅易的就走。

export class Term extends React.Component {      private terminal!: HTMLDivElement;      private fitAddon = new FitAddon();      componentDidMount() {          const xterm = new Terminal();          xterm.loadAddon(this.fitAddon);          xterm.loadAddon(new WebLinksAddon());         // using wss for https          //         const socket = new WebSocket("ws://" + window.location.host + "/api/v1/ws");          const socket = new WebSocket("ws://localhost:8000/webshell/");          // socket.onclose = (event) => {          //     this.props.onClose();          // }          socket.onopen = (event) => {              xterm.loadAddon(new AttachAddon(socket));              this.fitAddon.fit();              xterm.focus();          }          xterm.open(this.terminal);          xterm.onResize(({ cols, rows }) => {             socket.send("<RESIZE>" + cols + "," + rows)          });          window.addEventListener('resize', this.onResize);      }      componentWillUnmount() {          window.removeEventListener('resize', this.onResize);      }      onResize = () => {          this.fitAddon.fit();      }      render() {          return <div className="Terminal" ref={(ref) => this.terminal = ref as HTMLDivElement}></div>;      }  }  

【编辑选举】

鸿蒙官方战略相符作共建——HarmonyOS技术社区 从零实现基于Linux socket座谈室-增补数据库Sqlite功能-5 Linux体系编程第06期:从零实现一个shell注释器 Linux体系构成、启动和交互过程 微柔 Windows 11/10 Linux 子体系(WSL)0.50.2 更新,获得崭新企鹅图标 Linux 驱动实践:你清新字符设备驱动程序的两栽写法吗?