<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Django3 使用 WebSocket 實(shí)現(xiàn) WebShell

          共 21448字,需瀏覽 43分鐘

           ·

          2021-09-14 22:49

          △點(diǎn)擊上方“Python貓”關(guān)注 ,回復(fù)“1”領(lǐng)取電子書
          劇照:《眷思量》

          作者:從零開始的程序員生活

          來(lái)源:https://www.cnblogs.com/lgjbky/p/15186188.html

          前言

          最近工作中需要開發(fā)前端操作遠(yuǎn)程虛擬機(jī)的功能,簡(jiǎn)稱 WebShell?;诋?dāng)前的技術(shù)棧為 react+django,調(diào)研了一會(huì)發(fā)現(xiàn)大部分的后端實(shí)現(xiàn)都是 django+channels 來(lái)實(shí)現(xiàn) websocket 服務(wù)。

          大致看了下覺(jué)得這不夠有趣,翻了翻 django 的官方文檔發(fā)現(xiàn) django 原生是不支持 websocket 的,但 django3 之后支持了 asgi 協(xié)議可以自己實(shí)現(xiàn) websocket 服務(wù)。

          于是選定 gunicorn+uvicorn+asgi+websocket+django3.2+paramiko 來(lái)實(shí)現(xiàn) WebShell。

          實(shí)現(xiàn) websocket 服務(wù)

          使用 django 自帶的腳手架生成的項(xiàng)目會(huì)自動(dòng)生成 asgi.py 和 wsgi.py 兩個(gè)文件,普通應(yīng)用大部分用的都是 wsgi.py 配合 nginx 部署線上服務(wù)。

          這次主要使用 asgi.py 實(shí)現(xiàn) websocket 服務(wù)的思路大致網(wǎng)上搜一下就能找到,主要就是實(shí)現(xiàn) connect/send/receive/disconnect 這個(gè)幾個(gè)動(dòng)作的處理方法。

          這里 How to Add Websockets to a Django App without Extra Dependencies(https://jaydenwindle.com/writing/django-websockets-zero-dependencies/) 就是一個(gè)很好的實(shí)例,但過(guò)于簡(jiǎn)單……

          思路

          # 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!'
                          })

          實(shí)現(xiàn)

          上面的代碼提供了思路,比較完整的可以參考這里 websockets-in-django-3-1 (https://aliashkevich.com/websockets-in-django-3-1/) 基本可以復(fù)用了。

          其中最核心的實(shí)現(xiàn)部分我放下面:

          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

          縫合怪

          做為合格的代碼搬運(yùn)工,為了提高搬運(yùn)效率還是要造點(diǎn)輪子填點(diǎn)坑的,如何將上面的 WebSocket 類與 paramiko 結(jié)合起來(lái),實(shí)現(xiàn)從前端接受字符傳遞給遠(yuǎn)程主機(jī),并同時(shí)接受返回呢?

          import asyncio
          import traceback
          import paramiko
          from webshell.ssh import Base, RemoteSSH
          from webshell.connection import WebSocket


          class WebShell:
              """整理 WebSocket 和 paramiko.Channel,實(shí)現(xiàn)兩者的數(shù)據(jù)互通"""

              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歡迎相關(guān)內(nèi)容
                  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 完全滿足,搜索下找個(gè)看著簡(jiǎn)單的就行。

          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>;
              }
          }

          好了,廢話不多少了,代碼我放這里了webshell (https://github.com/aleimu/webshell) 歡迎 star/fork!
          Python貓技術(shù)交流群開放啦!群里既有國(guó)內(nèi)一二線大廠在職員工,也有國(guó)內(nèi)外高校在讀學(xué)生,既有十多年碼齡的編程老鳥,也有中小學(xué)剛剛?cè)腴T的新人,學(xué)習(xí)氛圍良好!想入群的同學(xué),請(qǐng)?jiān)诠?hào)內(nèi)回復(fù)『交流群』,獲取貓哥的微信(謝絕廣告黨,非誠(chéng)勿擾!)~


          還不過(guò)癮?試試它們




          Python 協(xié)程與 JavaScript 協(xié)程的對(duì)比

          把 Redis 當(dāng)作隊(duì)列用,真的合適嗎?

          我在 GitHub 上讀清華

          如何快速?gòu)?JSON 中找到特定的 Key?

          Python進(jìn)階:用websocket構(gòu)建實(shí)時(shí)日志跟蹤器

          吐槽:Python正在從簡(jiǎn)明轉(zhuǎn)向臃腫,從實(shí)用轉(zhuǎn)向媚俗


          如果你覺(jué)得本文有幫助
          請(qǐng)慷慨分享點(diǎn)贊,感謝啦!
          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人黄片网站 | 大长腿啪啪 | 色av导航| 狠狠久久的视频 | 九九九亚洲视频 |