佳木斯湛栽影视文化发展公司

主頁(yè) > 知識(shí)庫(kù) > Django3基于WebSocket實(shí)現(xiàn)WebShell的詳細(xì)過程

Django3基于WebSocket實(shí)現(xiàn)WebShell的詳細(xì)過程

熱門標(biāo)簽:鐵路電話系統(tǒng) 網(wǎng)站文章發(fā)布 檢查注冊(cè)表項(xiàng) 銀行業(yè)務(wù) 呼叫中心市場(chǎng)需求 智能手機(jī) 服務(wù)器配置 美圖手機(jī)

前言

最近工作中需要開發(fā)前端操作遠(yuǎn)程虛擬機(jī)的功能,簡(jiǎn)稱WebShell. 基于當(dāng)前的技術(shù)棧為react+django,調(diào)研了一會(huì)發(fā)現(xiàn)大部分的后端實(shí)現(xiàn)都是django+channels來實(shí)現(xiàn)websocket服務(wù).
大致看了下覺得這不夠有趣,翻了翻django的官方文檔發(fā)現(xiàn)django原生是不支持websocket的,但django3之后支持了asgi協(xié)議可以自己實(shí)現(xiàn)websocket服務(wù). 于是選定
gunicorn+uvicorn+asgi+websocket+django3.2+paramiko來實(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 就是一個(gè)很好的實(shí)例
, 但過于簡(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 基本可以復(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é)合起來實(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 歡迎star/fork!

參考資料

webshell

django文檔

graphene-django文檔

django 異步視圖

websockets-in-django-3-1

How to Add Websockets to a Django App without Extra Dependencies

到此這篇關(guān)于Django3使用WebSocket實(shí)現(xiàn)WebShell的文章就介紹到這了,更多相關(guān)Django3實(shí)現(xiàn)WebShell內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • Django websocket原理及功能實(shí)現(xiàn)代碼
  • 詳解Django3中直接添加Websockets方式
  • Django通過dwebsocket實(shí)現(xiàn)websocket的例子
  • 詳解Django-channels 實(shí)現(xiàn)WebSocket實(shí)例
  • Django使用Channels實(shí)現(xiàn)WebSocket的方法

標(biāo)簽:滄州 新疆 樂山 沈陽(yáng) 紅河 長(zhǎng)治 河南 上海

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Django3基于WebSocket實(shí)現(xiàn)WebShell的詳細(xì)過程》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    赣榆县| 巴彦淖尔市| 高平市| 多伦县| 怀来县| 保德县| 连南| 徐水县| 宿迁市| 紫云| 汶上县| 靖宇县| 临沧市| 垣曲县| 益阳市| 酉阳| 西乌珠穆沁旗| 青州市| 会同县| 顺平县| 分宜县| 和平县| 合作市| 井陉县| 盐边县| 东源县| 石门县| 汉寿县| 周宁县| 中阳县| 从江县| 九龙坡区| 鸡泽县| 长宁县| 灵璧县| 咸丰县| 华宁县| 印江| 洪泽县| 安乡县| 莒南县|