什麼是webssh?
泛指一種技術能夠在網頁上實現一個 終端。從而無需 之類的模擬終端工具進行 鏈接,將 這一比較低層的操做也從 架構扭成了 架構 這樣的架構經常使用在運維製做開發一些堡壘機等系統中,或是目前比較新型的在線教育方式,經過向學生提供一個能夠直接使用瀏覽器進行相關 操做或代碼編寫的學習方式 主要是創建客戶端與服務端的即時通訊css
模型
此種 實現方式,將經過結合 以及後端的 來進行實現,所須要的技術 棧以下前端
# 前端 vue websocket xterm.js
# 後端 django dwebsocket (channels) paramiko threading
技術介紹
xtermvue
前端經過xterm插件進行shell黑窗口環境的搭建,這個插件會自動解析由後臺paramiko返回的帶有標記樣式的命令結果,並渲染到瀏覽器中,很是酷炫linux
websocketwebpack
這裏經過websocket進行瀏覽器與django的數據交通橋樑web
paramiko shell
paramiko此時的角色用來承擔django與linux環境的交互,將前端發來的命令發送給後臺,將 後臺發來的命令結果返回到前端的xterm組件中npm
前端實現
安裝xtermdjango
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 # 這裏除了 WebsocketConsumer 以外還有 # JsonWebsocketConsumer # AsyncWebsocketConsumer # AsyncJsonWebsocketConsumer # WebsocketConsumer 與 JsonWebsocketConsumer 就是多了一個能夠自動處理JSON的方法 # AsyncWebsocketConsumer 與 AsyncJsonWebsocketConsumer 也是多了一個JSON的方法 # AsyncWebsocketConsumer 與 WebsocketConsumer 纔是重點 # 看名稱彷佛理解並不難 Async 無非就是異步帶有 async / await # 是的理解並無錯,但對與咱們來講他們惟一不同的地方,可能就是名字的長短了,用法是如出一轍的 # 最誇張的是,基類是同一個,並且這個基類的方法也是Async異步的 # 因爲咱們對Async編程還未熟練,所在這邊使用相對簡單的WebsocketConsumer進行演示 class ChatService(WebsocketConsumer): # 當Websocket建立鏈接時 def connect(self): 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},你被女友拋棄了')