模型
- 此時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發送數據