0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Django3如何使用WebSocket实现WebShell

马哥Linux运维 来源:从零开始的程序员生活 作者:从零开始的程序员 2021-11-17 09:58 次阅读

前言最近工作中需要开发前端操作远程虚拟机的功能,简称 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
importos

fromdjango.core.asgiimportget_asgi_application
fromwebsocket_app.websocketimportwebsocket_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE','websocket_app.settings')

django_application=get_asgi_application()


asyncdefapplication(scope,receive,send):
ifscope['type']=='http':
awaitdjango_application(scope,receive,send)
elifscope['type']=='websocket':
awaitwebsocket_application(scope,receive,send)
else:
raiseNotImplementedError(f"Unknownscopetype{scope['type']}")


#websocket.py
asyncdefwebsocket_application(scope,receive,send):
pass

#websocket.py
asyncdefwebsocket_application(scope,receive,send):
whileTrue:
event=awaitreceive()

ifevent['type']=='websocket.connect':
awaitsend({
'type':'websocket.accept'
})

ifevent['type']=='websocket.disconnect':
break

ifevent['type']=='websocket.receive':
ifevent['text']=='ping':
awaitsend({
'type':'websocket.send',
'text':'pong!'
})

实现

上面的代码提供了思路

其中最核心的实现部分我放下面:

classWebSocket:
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
defheaders(self):
returnHeaders(self._scope)

@property
defscheme(self):
returnself._scope["scheme"]

@property
defpath(self):
returnself._scope["path"]

@property
defquery_params(self):
returnQueryParams(self._scope["query_string"].decode())

@property
defquery_string(self)->str:
returnself._scope["query_string"]

@property
defscope(self):
returnself._scope

asyncdefaccept(self,subprotocol:str=None):
"""Acceptconnection.
:paramsubprotocol:Thesubprotocoltheserverwishestoaccept.
:typesubprotocol:str,optional
"""
ifself._client_state==State.CONNECTING:
awaitself.receive()
awaitself.send({"type":SendEvent.ACCEPT,"subprotocol":subprotocol})

asyncdefclose(self,code:int=1000):
awaitself.send({"type":SendEvent.CLOSE,"code":code})

asyncdefsend(self,message:t.Mapping):
ifself._app_state==State.DISCONNECTED:
raiseRuntimeError("WebSocketisdisconnected.")

ifself._app_state==State.CONNECTING:
assertmessage["type"]in{SendEvent.ACCEPT,SendEvent.CLOSE},(
'Couldnotwriteevent"%s"intosocketinconnectingstate.'
%message["type"]
)
ifmessage["type"]==SendEvent.CLOSE:
self._app_state=State.DISCONNECTED
else:
self._app_state=State.CONNECTED

elifself._app_state==State.CONNECTED:
assertmessage["type"]in{SendEvent.SEND,SendEvent.CLOSE},(
'Connectedsocketcansend"%s"and"%s"events,not"%s"'
%(SendEvent.SEND,SendEvent.CLOSE,message["type"])
)
ifmessage["type"]==SendEvent.CLOSE:
self._app_state=State.DISCONNECTED

awaitself._send(message)

asyncdefreceive(self):
ifself._client_state==State.DISCONNECTED:
raiseRuntimeError("WebSocketisdisconnected.")

message=awaitself._receive()

ifself._client_state==State.CONNECTING:
assertmessage["type"]==ReceiveEvent.CONNECT,(
'WebSocketisinconnectingstatebutreceived"%s"event'
%message["type"]
)
self._client_state=State.CONNECTED

elifself._client_state==State.CONNECTED:
assertmessage["type"]in{ReceiveEvent.RECEIVE,ReceiveEvent.DISCONNECT},(
'WebSocketisconnectedbutreceivedinvalidevent"%s".'
%message["type"]
)
ifmessage["type"]==ReceiveEvent.DISCONNECT:
self._client_state=State.DISCONNECTED

returnmessage

缝合怪

做为合格的代码搬运工,为了提高搬运效率还是要造点轮子填点坑的,如何将上面的 WebSocket 类与 paramiko 结合起来,实现从前端接受字符传递给远程主机,并同时接受返回呢?

importasyncio
importtraceback
importparamiko
fromwebshell.sshimportBase,RemoteSSH
fromwebshell.connectionimportWebSocket


classWebShell:
"""整理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

definit_ssh(self,host=None,port=22,user="admin",passwd="admin@123"):
self.ssh_session,self.chanel_session=RemoteSSH(host,port,user,passwd).session()

defset_ssh(self,ssh_session,chanel_session):
self.ssh_session=ssh_session
self.chanel_session=chanel_session

asyncdefready(self):
awaitself.ws_session.accept()

asyncdefwelcome(self):
#展示Linux欢迎相关内容
foriinrange(2):
ifself.chanel_session.send_ready():
message=self.chanel_session.recv(2048).decode('utf-8')
ifnotmessage:
return
awaitself.ws_session.send_text(message)

asyncdefweb_to_ssh(self):
#print('--------web_to_ssh------->')
whileTrue:
#print('--------------->')
ifnotself.chanel_session.activeornotself.ws_session.status:
return
awaitasyncio.sleep(0.01)
shell=awaitself.ws_session.receive_text()
#print('-------shell-------->',shell)
ifself.chanel_session.activeandself.chanel_session.send_ready():
self.chanel_session.send(bytes(shell,'utf-8'))
#print('--------------->',"end")

asyncdefssh_to_web(self):
#print('<--------ssh_to_web-----------')
whileTrue:
#print('<-------------------')
ifnotself.chanel_session.active:
awaitself.ws_session.send_text('sshclosed')
return
ifnotself.ws_session.status:
return
awaitasyncio.sleep(0.01)
ifself.chanel_session.recv_ready():
message=self.chanel_session.recv(2048).decode('utf-8')
#print('<---------message----------', message)
ifnotlen(message):
continue
awaitself.ws_session.send_text(message)
#print('<-------------------', "end")

asyncdefrun(self):
ifnotself.ssh_session:
raiseException("sshnotinit!")
awaitself.ready()
awaitasyncio.gather(
self.web_to_ssh(),
self.ssh_to_web()
)

defclear(self):
try:
self.ws_session.close()
exceptException:
traceback.print_stack()
try:
self.ssh_session.close()
exceptException:
traceback.print_stack()

前端

xterm.js 完全满足,搜索下找个看着简单的就行。

exportclassTermextendsReact.Component{
privateterminal!:HTMLDivElement;
privatefitAddon=newFitAddon();

componentDidMount(){
constxterm=newTerminal();
xterm.loadAddon(this.fitAddon);
xterm.loadAddon(newWebLinksAddon());

//usingwssforhttps
//constsocket=newWebSocket("ws://"+window.location.host+"/api/v1/ws");
constsocket=newWebSocket("ws://localhost:8000/webshell/");
//socket.onclose=(event)=>{
//this.props.onClose();
//}
socket.onopen=(event)=>{
xterm.loadAddon(newAttachAddon(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<divclassName="Terminal"ref={(ref)=>this.terminal=refasHTMLDivElement}>div>;
}
}

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

编辑:jq
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6508

    浏览量

    87552
  • 主机
    +关注

    关注

    0

    文章

    895

    浏览量

    34590
  • 代码
    +关注

    关注

    30

    文章

    4552

    浏览量

    66642
  • WebSocket
    +关注

    关注

    0

    文章

    24

    浏览量

    3654

原文标题:Django3 使用 WebSocket 实现 WebShell

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    webshell的四个特征

    webshell检测预提取特征分析
    发表于 11-05 06:31

    初探实现websocket的心跳重连

    初探和实现websocket心跳重连
    发表于 05-25 15:07

    基于TCP的一种新的网络协议WebSocket

    开启 WebSocket 服务WebSocket 服务是网页程序、安卓 App、微信小程序等获得数据和服务的接口,是基于TCP 的一种新的网络协议,它实现了浏览器与服务器全双工通信。通过
    发表于 12-16 07:38

    Python+Django+Mysql实现在线电影推荐系统

    Python+Django+Mysql实现在线电影推荐系统(基于用户、项目的协同过滤推荐算法)一、项目简介1、开发工具和实现技术pycharm2020professional版本,python3.8
    发表于 01-03 06:35

    Webshell提权登陆服务器

    Webshell提权登陆服务器
    发表于 09-07 14:04 4次下载
    <b class='flag-5'>Webshell</b>提权登陆服务器

    基于Django的XSS防御研究与实现

    跨站脚本攻击(XSS)是当前最流行的Web应用攻击方式之一,而Django作为目前比较火热的Web应用开发框架,并没有为其开发的Web应用提供有效的XSS防御功能。本文针对此问题,采用中间件,黑白
    发表于 04-09 11:33 0次下载
    基于<b class='flag-5'>Django</b>的XSS防御研究与<b class='flag-5'>实现</b>

    什么是WebSocket?进行通信解析 WebSocket 报文及实现

    一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错。
    的头像 发表于 05-15 16:59 9161次阅读
    什么是<b class='flag-5'>WebSocket</b>?进行通信解析 <b class='flag-5'>WebSocket</b> 报文及<b class='flag-5'>实现</b>

    根据WebSocket协议完全使用C++实现函数

    由于需要在项目中增加Websocket协议,与客户端进行通信,不想使用开源的库,比如WebSocketPP,就自己根据WebSocket协议实现一套函数,完全使用C++实现
    的头像 发表于 11-28 14:29 4208次阅读

    WebSocket有什么优点

    WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。HTML5开始提
    的头像 发表于 02-15 15:53 7961次阅读
    <b class='flag-5'>WebSocket</b>有什么优点

    WebSocket工作原理及使用方法

    它有很多名字; WebSocketWebSocket协议和WebSocket API。从首选的消息传递应用程序到流行的在线多人游戏,WebSocket在当今最常用的Web应用程序中是
    的头像 发表于 05-05 22:12 7526次阅读
    <b class='flag-5'>WebSocket</b>工作原理及使用方法

    Django应用程序开发中设计Django模板的方法

    在本文中,我将介绍在Django应用程序开发中设计Django模板的方法。目的是保持Django应用程序的UI部分井井有条,并避免重复编码。Django在模板引擎中提供了各种机制来帮助
    的头像 发表于 07-29 15:44 1524次阅读

    Django Simple Captcha Django验证组件

    ./oschina_soft/django-simple-captcha.zip
    发表于 05-09 10:53 3次下载
    <b class='flag-5'>Django</b> Simple Captcha <b class='flag-5'>Django</b>验证组件

    Django的简单应用示例

    Django是python的Web应用框架,并于2008年发布了第一个版本,下面我们先来学习Django的简单应用示例。
    的头像 发表于 02-14 14:13 742次阅读
    <b class='flag-5'>Django</b>的简单应用示例

    WebSocket的6种集成方式介绍

    由于前段时间我实现了一个库【Spring Cloud】一个配置注解实现 WebSocket 集群方案
    的头像 发表于 09-02 16:52 846次阅读

    websocket协议的原理

    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。 WebSocket通信协议于2011年被IETF
    的头像 发表于 11-09 15:13 414次阅读
    <b class='flag-5'>websocket</b>协议的原理