gpt4 book ai didi

Django3基于WebSocket实现WebShell的详细过程

转载 作者:qq735679552 更新时间:2022-09-27 22:32:09 25 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Django3基于WebSocket实现WebShell的详细过程由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

前言

最近工作中需要开发前端操作远程虚拟机的功能,简称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 就是一个很好的实例 , 但过于简单........

思路 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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!'
                 })

实现

上面的代码提供了思路,比较完整的可以参考这里 websockets-in-django-3-1 基本可以复用了 其中最核心的实现部分我放下面

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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结合起来实现从前端接受字符传递给远程主机并同时接受返回呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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 完全满足,搜索下找个看着简单的就行. 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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 。

到此这篇关于Django3使用WebSocket实现WebShell的文章就介绍到这了,更多相关Django3实现WebShell内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。

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

最后此篇关于Django3基于WebSocket实现WebShell的详细过程的文章就讲到这里了,如果你想了解更多关于Django3基于WebSocket实现WebShell的详细过程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com