模型
- 此时webSSH实现方式,将通过结合websocket以及Paramiko来进行实现,所需要的技术栈如下
# 前端
vue
websocket
xterm.js ["xterm": "^3.1.0"]
# 选择xterm插件的版本是3.1.0,在package.json里标记
"dependencies": {
"axios": "^0.19.0",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
-----------------
"xterm": "^3.1.0"
-----------------
},
#后端
django
dwebsocket
paramiko
threading
技术介绍
- xterm
前端样式通过xterm插件进行shell黑窗口环境的搭建,这个插件会自动解析由后台paramiko返回的带有标记样式的命令结果,并渲染到浏览器中,非常炫酷。
- websocket
这里通过websocket进行浏览器与django的数据交通桥梁
- paramiko
paramiko此时的角色用来承担django与linux环境的交互,将前端发来的命令发给后台,将后台发来的命令结果返回到前端的xterm组件中
前端实现
前端xterm组件使用:简单
- 安装xterm
cnpm install xterm --save
#使用命令默认安装的是最新版本,在这里我们在vue根目录下,package.json下的"dependencies":{"xterm": "^3.1.0"}指定xterm版本
- vue框架中的引入xterm的样式文件
# 在vue main.js中引入xterm样式 *重*
import Vue from 'vue'
import App from './App'
import router from './router'
import 'xterm/dist/xterm.css' //导入所需要的样式
Vue.config.productionTip = false;
import axios from 'axios'
Vue.prototype.axios = axios;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
# 如果导入样式服务报错,解决方法如下
# 在vue项目的根目录下创建一个postcss.config.js文件夹
module.exports = { plugins: {
'autoprefixer': {browsers: 'last 5 version'}
}};
- 初始化xterm组件并添加俩个插件:attach可以将终端附加到websocket流中,fit可以调整终端的大小以及和列适配父级元素
import {Terminal} from 'xterm' //初始化id="terminal"
import * as attach from 'xterm/lib/addons/attach/attach' //黑窗口绑定
import * as fit from 'xterm/lib/addons/fit/fit' //自动化调节
Terminal.applyAddon(attach); //Addon插件
Terminal.applyAddon(fit);
- 构建websocket并绑定到终端,websocket地址为ws协议前缀,此时使用的是即将在django中配置websocket后台视图的路由,这一系列行为挂载到钩子函数下进行
# 需要创建一个vue组件
# 例:console.vue *重*
<template>
<div id="terminal">
</div>
</template>
<script>
import {Terminal} from 'xterm' //初始化id="terminal"
import * as attach from 'xterm/lib/addons/attach/attach' //黑窗口绑定
import * as fit from 'xterm/lib/addons/fit/fit' //自动化调节
Terminal.applyAddon(attach); //Addon插件
Terminal.applyAddon(fit);
export default {
name: "console",
mounted() {
let terminalContainer = document.getElementById('terminal');
this.term = new Terminal(this.terminal); //初始化终端
this.term.open(terminalContainer) //把div为terminal的标签绑定到初始化号的黑窗口
this.terminalSocket = new WebSocket('ws://127.0.0.1:8000/webssh/')
this.term.attach(this.terminalSocket) //绑定黑窗口到websocket上
this.terminalSocket //连接对象
},
beforeDestroy() {
this.terminalSocket.close() //先关闭websocket
this.term.destroy() //关闭黑窗口
}
}
</script>
--------------------------------------------------------------------------------------------------------------------------------------------
# 提示语发送成功,失败,等信息语。
this.terminalSocket.onopen = function () {
console.log('websocket is Connected...')
};
this.terminalSocket.onclose = function () {
console.log('websocket is Closed...')
};
this.terminalSocket.onerror = function () {
console.log('damn websocket is broken!')
}
- 当浏览器关闭时,也代表着客户端关闭,此时主动断开连接,交给vue的钩子函数来处理这个问题
//钩子函数
beforeDestroy() {
this.terminalSocket.close() //先关闭websocket
this.term.destroy() //关闭黑窗口
}
后端实现
django 这里使用dwebsocket模块进行ws的服务端编写通信
- 首先确定路由,也是前端的ws连接地址
# urls.py 路由
from django.contrib import admin
from django.urls import path,include
from deam import views
urlpatterns = [
path('webssh/',views.Webssh)
]
- 定义函数,初始化SSH连接对象(连接linux)
# views.py *重*
from dwebsocket import accept_websocket
from threading import Thread
import paramiko
# pip install paramiko
# pip install dwebsocket
def ssh_channle():
host = '120.27.23.63'
username = 'root'
password = 'xxxxxxxxxxxx'
sh = paramiko.SSHClient() # 利用paramiko建立连接
sh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#保存校验密钥
sh.connect(host,username=username,password=password)
channle = sh.invoke_shell(term='xterm')
return channle
- 由于在SSH通道中,服务端可能返回结果的方式并不是与客户端发起的命令一唱一和,可能是一唱多和,比如类似top这样的命令,一次命令输入之后,服务端会返回N次结果,此时在django视图中采用多线程,专门处理命令结果的返回:以下是这个任务线程函数的定义:
# views.py *重*
FLAG = [True]
def cmd_callback(ws,channle):
# 接收CMD命令的返回,并且给到websocket
while True:
if FLAG[0]:
try:
result = channle.recv(1024) # 管道的返回值
ws.send(result) # 把结果返回给前端用户
except:
break
return #线程结束
# views.py *重*
@accept_websocket #确保当前视图只接收websocket
def Webssh(request):
if request.is_websocket:
# 用户构建好了链接
# 接受命令发给Linux
ws = request.websocket
channle = ssh_channle() # 链接paramiko管道,与linux进行交互
cmd_callback_thread = Thread(target=cmd_callback,args=(ws,channle))
cmd_callback_thread.start()
while True:
try:
cmd = ws.wait() # 阻塞接受呃前端发来的命令
print(cmd,'*******************************************************')
if not cmd:
break
channle.send(cmd) # 向linux发送cmd
except:
break
FLAG[0] = False
ws.close()
channle.close()
return Response({'code':1})
重点
# websocket ws.wait()接受vue发送的数据
# ws.send()向vue发送数据
# paramiko channle.recv(1024) 接受linux接受的数据
# channle.send() 向linux发送数据