<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 實現(xiàn) WebShell

          共 6735字,需瀏覽 14分鐘

           ·

          2021-11-18 13:32

          前言

          最近工作中需要開發(fā)前端操作遠程虛擬機的功能,簡稱 WebShell?;诋斍暗募夹g(shù)棧為 react+django,調(diào)研了一會發(fā)現(xiàn)大部分的后端實現(xiàn)都是 django+channels 來實現(xiàn) websocket 服務(wù)。

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

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

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

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

          這次主要使用 asgi.py 實現(xiàn) websocket 服務(wù)的思路大致網(wǎng)上搜一下就能找到,主要就是實現(xiàn) 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!'
          ????????????????})

          實現(xiàn)

          上面的代碼提供了思路

          其中最核心的實現(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

          縫合怪

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

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


          class?WebShell:
          ????"""整理?WebSocket?和?paramiko.Channel,實現(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 完全滿足,搜索下找個看著簡單的就行。

          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(""?+?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>;
          ????}
          }

          原文鏈接:https://www.cnblogs.com/lgjbky/p/15186188.html

          文章轉(zhuǎn)載:Python編程學(xué)習圈
          (版權(quán)歸原作者所有,侵刪)

          點擊下方“閱讀原文”查看更多

          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  99re在线视频播放 | 人人摸人人干人人射 | 操碰久久| 日韩无码AV一区 | av永久免费 |