什么是webssh?
泛指一種技術可以在網頁上實現一個 終端。從而無需 之類的模擬終端工具進行 連接,將 這一比較低層的操作也從 架構扭成了 架構 這樣的架構常用在運維制作開發一些堡壘機等系統中,或是目前比較新型的在線教育方式,通過向學生提供一個可以直接使用瀏覽器進行相關 操作或代碼編寫的學習方式 主要是建立客戶端與服務端的即時通信
模型
此種 實現方式,將通過結合 以及后端的 來進行實現,所需要的技術 棧如下
# 前端 vue websocket xterm.js
# 后端 django dwebsocket (channels) paramiko threading
技術介紹
xterm
前端通過xterm插件進行shell黑窗口環境的搭建,這個插件會自動解析由后台paramiko返回的帶有標記樣式的命令結果,並渲染到瀏覽器中,非常酷炫
websocket
這里通過websocket進行瀏覽器與django的數據交通橋梁
paramiko
paramiko此時的角色用來承擔django與linux環境的交互,將前端發來的命令發送給后台,將 后台發來的命令結果返回到前端的xterm組件中
前端實現
vue發送websocket請求

<template> <div> <input type="text" v-model="message"> <p><input type="button" @click="send" value="發送"></p> <p><input type="button" @click="close_socket" value="關閉"></p> </div> </template> <script> export default { name:'ws', data() { return { message:'', testsocket:'' } }, methods:{ send(){ // send 發送信息 // close 關閉連接 this.testsocket.send(this.message) this.testsocket.onmessage = (res) => { console.log("WS的返回結果",res.data); } }, close_socket(){ this.testsocket.close() } }, mounted(){ this.testsocket = new WebSocket("ws://127.0.0.1:8000/ws/") // onopen 定義打開時的函數 // onclose 定義關閉時的函數 // onmessage 定義接收數據時候的函數 this.testsocket.onopen = function(){ console.log("開始連接socket") }, this.testsocket.onclose = function(){ console.log("socket連接已經關閉") } } } </script>
安裝xterm
cnpm install xterm@3.1.0 --save //指定版本安裝
在vue框架中引入xterm的樣式文件

// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import 'xterm/dist/xterm.css' // 看這里,添加xterm css文件樣式 Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
使用xterm和websocket來實時發送命令

<template> <div class="console" id="terminal"></div> </template> <script> import { Terminal } from 'xterm' import * as attach from 'xterm/lib/addons/attach/attach' import * as fit from 'xterm/lib/addons/fit/fit' export default { name: 'webssh', data () { return { term: null, terminalSocket: null, order:'' } }, methods: { }, mounted () { //實例化一個websocket,用於和django江湖 this.terminalSocket = new WebSocket("ws://127.0.0.1:8000/web/"); //獲取到后端傳回的信息 this.terminalSocket.onmessage = (res) => { console.log(res.data); // var message = JSON.parse(res.data); //將傳回來的數據顯示在xterm里 this.term.writeln("\r\n"+res.data); //重置要發送的信息 this.order = "" //換行,顯示下一個開頭 this.term.write("\r\n$ "); } //ws連接的時候 // this.terminalSocket.onopen = function(){ // console.log('websocket is Connected...') // } //ws關閉的時候 // this.terminalSocket.onclose = function(){ // console.log('websocket is Closed...') // } //ws錯誤的時候 // this.terminalSocket.onerror = function(){ // console.log('damn Websocket is broken!') // } // this.term.attach(this.terminalSocket) // 綁定xterm到ws流中 }, let terminalContainer = document.getElementById('terminal') //創建xterm實例 this.term = new Terminal({ cursorBlink: true, // 顯示光標 cursorStyle: "underline" // 光標樣式 }) // 創建一個新的Terminal對象 this.term.open(terminalContainer) // 將term掛載到dom節點上 //在xterm上顯示命令行提示 this.term.write('$ ') //監聽xterm的鍵盤事件 this.term.on('key', (key, ev)=>{ // key是輸入的字符 ev是鍵盤按鍵事件 console.log("key==========", ev.keyCode); this.term.write(key) // 將輸入的字符打印到黑板中 if (ev.keyCode == 13) { // 輸入回車 // console.log("輸入回車") // this.term.write('$ ') // console.log(this.order) //使用webscoket將數據發送到django this.terminalSocket.send(this.order) // this.order='' console.log("里面的order",this.order) }else if(ev.keyCode == 8){//刪除按鈕 //截取字符串[0,lenth-1] this.order = this.order.substr(0,this.order.length-1) //清空當前一條的命令 this.term.write("\x1b[2K\r") //簡化當前的新的命令顯示上 this.term.write("$ "+this.order) console.log("截取的字符串"+this.order) typeof this.order }else{// 將每次輸入的字符拼湊起來 this.order += key console.log("外面的order",this.order)} }) }, } </script>
后端實現
基於channels實現websocket
安裝channels
pip install channels
在setting的同級目錄下創建routing.py
#routing.py from channels.routing import ProtocolTypeRouter application = ProtocolTypeRouter({ # 暫時為空 })
配置setting
INSTALLED_APPS = [ 'channels' ] ASGI_APPLICATION = "項目名.routing.application"
啟動帶有ASGI的django項目
帶有ASGI的項目
平常項目
在app-chats中創建一個wsserver.py文件夾來保存關於websocket的處理視圖

from channels.generic.websocket import WebsocketConsumer class ChatService(WebsocketConsumer): # 當Websocket創建連接時 def connect(self): #websocket保持連接 self.accept() pass # 當Websocket接收到消息時 def receive(self, text_data=None, bytes_data=None): pass # 當Websocket發生斷開連接時 def disconnect(self, code): pass
配置對應的路由

from django.urls import path from chats.chatService import ChatService websocket_url = [ path("ws/",ChatService) ]
在routing.py里增加關於websocket的非http請求的url

from channels.routing import ProtocolTypeRouter,URLRouter from chats.urls import websocket_url application = ProtocolTypeRouter({ "websocket":URLRouter( websocket_url ) })
Paramiko的使用
安裝paramiko
pip install paramiko
使用paramiko
from django.test import TestCase # Create your tests here. import paramiko class WebSsh(object): def client_ssh(self): sh = paramiko.SSHClient() # 1 創建SSH對象 sh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 2 允許連接不在know_hosts文件中的主機 sh.connect("10.211.55.17", username="parallels", password="beijing") # 3 連接服務器 stdin, stdout, stderr = sh.exec_command('ls') right_info = stdout.read() err_info = stderr.read() if right_info: print(right_info.decode("utf-8")) elif err_info: print(err_info.decode("utf-8")) else: print("命令執行成功") if __name__ == '__main__': a = WebSsh() a.client_ssh()
webssh的后端實現

INSTALLED_APPS=[ 'channels', 'chats', ] ASGI_APPLICATION = "shiyanloupro.routing.application"

from channels.routing import ProtocolTypeRouter,URLRouter from chats.urls import websocket_url application = ProtocolTypeRouter({ "websocket":URLRouter( websocket_url ) })

from django.urls import path from chats.chatservice import ChatService,WebSSHService websocket_url = [ path("ws/",ChatService), path("web/",WebSSHService), ]

from channels.generic.websocket import WebsocketConsumer import paramiko socket_list = [] class ChatService(WebsocketConsumer): # 當Websocket創建連接時 def connect(self): self.accept() socket_list.append(self) # 當Websocket接收到消息時 def receive(self, text_data=None, bytes_data=None): print(text_data) # 打印收到的數據 for ws in socket_list: # 遍歷所有的WebsocketConsumer對象 ws.send(text_data) # 對每一個WebsocketConsumer對象發送數據 # 當Websocket發生斷開連接時 def disconnect(self, code): print(f'sorry{self},你被女朋友拋棄了') socket_list.remove(self) class WebSSHService(WebsocketConsumer): def connect(self): self.accept() self.sh = paramiko.SSHClient() # 1 創建SSH對象 self.sh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 2 允許連接不在know_hosts文件中的主機 self.sh.connect("10.211.55.17", username="parallels", password="beijing") # 3 連接服務器 print("連接成功") def receive(self, text_data=None, bytes_data=None): print(str(text_data)) # 打印收到的數據 print(type(text_data)) stdin, stdout, stderr = self.sh.exec_command(text_data) right_info = stdout.read() err_info = stderr.read() print(right_info) if right_info: new_data = right_info.decode("utf-8").replace("\n","\r\n") print(new_data) self.send(new_data) elif err_info: new_data = err_info.decode("utf-8").replace("\n", "\r\n") print(new_data) self.send(new_data) else: print(self.send("命令執行成功")) def disconnect(self, code): print(f'sorry{self},你被女朋友拋棄了')