1.1 WebSSH介紹
1、什么是WebSSH?
1. webssh 泛指一種技術可以在網頁上實現一個 SSH 終端。
2. 從而無需 Xshell 之類的模擬終端工具進行 SSH 連接,將 SSH 這一比較低層的操作也從 C/S 架構扭成了 B/S 架構
3. 這樣的架構常用在運維制作開發一些堡壘機等系統中,或是目前比較新型的在線教育方式
4. 通過WebSSH 向學生提供一個可以直接使用瀏覽器進行相關 Linux 操作或代碼編寫的學習方式
5. WebSSh 主要是建立客戶端與服務端的即時通信
2、要實現WebSSH技術棧介紹
# 前端
vue websocket xterm.js # 后端
tornado dwebsocket paramiko threading
3、技術介紹
1)xterm
前端通過 xterm 插件進行 shell 黑窗口環境的搭建,
這個插件會自動解析由后台 paramiko 返回的帶有標記樣式的命令結果,並渲染到瀏覽器中,非常酷炫
2)websocket
這里通過 websocket 進行瀏覽器與 tornado 的雙向通信。
3)paramiko
paramiko 此時的角色用來承擔 tornado與 Linux 環境的交互,
將前端發來的命令發送給后台,將后台發來的命令結果返回到前端的 xterm 組件中
1.2 前端實現
參考代碼:https://gitee.com/edushiyanlou/webssh
1、初始化vue項目
vue init webpack webssh npm install --save xterm@3.1.0

// 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' // 引入xterm.css樣式 import 'xterm/dist/xterm.css' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })

<template> <div class="console" id="terminal"></div> </template> <script> import Terminal from './Xterm' export default { name: 'Console', data () { return { terminal: { pid: 1, name: 'terminal', cols: 400, rows: 400 }, term: null, terminalSocket: null } }, methods: { runRealTerminal () { console.log('webSocket is finished') }, errorRealTerminal () { console.log('error') }, closeRealTerminal () { console.log('close') } }, mounted () { console.log('pid : ' + this.terminal.pid + ' is on ready') let terminalContainer = document.getElementById('terminal') this.term = new Terminal() // 創建一個新的Terminal對象 this.term.open(terminalContainer) // 將term掛砸到dom節點上 // open websocket this.terminalSocket = new WebSocket('ws://127.0.0.1:3000/terminals/') // 創建socket連接 this.terminalSocket.onopen = this.runRealTerminal // 當連接成功觸發此函數 this.terminalSocket.onclose = this.closeRealTerminal // 當斷開連接調用此函數 this.terminalSocket.onerror = this.errorRealTerminal // 當發生錯誤調用此函數 this.term.attach(this.terminalSocket) // // 綁定xterm到ws流中 this.term._initialized = true console.log('mounted is going on') }, beforeDestroy () { this.terminalSocket.close() this.term.destroy() } } </script>

import { Terminal } from 'xterm' import * as fit from 'xterm/lib/addons/fit/fit' import * as attach from 'xterm/lib/addons/attach/attach' Terminal.applyAddon(fit) Terminal.applyAddon(attach) export default Terminal

import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import WebSSH from '@/components/WebSSH' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path: '/webssh', name: 'WebSSH', component: WebSSH } ] })
2、對核心代碼解釋
# 1. Xterm.js 初始化 xterm 組件並添加兩個插件 attach 可以將終端附加到 websocket 流中 fit 可以調整終端的大小以及行和列適配父級元素 # 2. WebSSH.vue 構建 websocket 並綁定到終端, websocket 地址為 ws 協議前綴 此時使用的是即將在 tornado 中配置 Websocket 后台視圖的路由,這一系列行為將掛載到鈎子函數下進行 # 當瀏覽器關閉時,也代表着客戶端關閉,此時主動斷開連接,交給 vue 的鈎子函數來處理這個問題 beforeDestroy () { this.terminalSocket.close() this.term.destroy() }
1.3 后端tornado代碼(方法一)
1、安裝相關包
# requirements.txt
paramiko==2.4.1 tornado==4.5.2 requests==2.18.4 PyJWT==1.6.4
2、tornado服務端代碼

# -*- coding: utf-8 -*- import tornado import tornado.websocket import paramiko import threading import time # 配置服務器信息 HOSTS = '1.1.1.3' PORT = 22 USERNAME = 'root' PASSWORD = 'chnsys@2016' class MyThread(threading.Thread): def __init__(self, id, chan): ''' :param id: 線程id這里沒有用 :param chan: webSSHServer對象 ''' threading.Thread.__init__(self) self.chan = chan def run(self): thread_num = len(threading.enumerate()) print("線程數量:", thread_num) while not self.chan.chan.exit_status_ready(): time.sleep(0.1) try: data = self.chan.chan.recv(1024) self.chan.write_message(data) except Exception as ex: print(str(ex)) self.chan.sshclient.close() return False class webSSHServer(tornado.websocket.WebSocketHandler): # 連接websocket服務器時進行的event def open(self): self.sshclient = paramiko.SSHClient() # 1 創建SSH對象 # 通過known_hosts 方式進行認證可以用這個,如果known_hosts 文件未定義還需要定義 known_hosts self.sshclient.load_system_host_keys() self.sshclient.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 2 允許連接不在know_hosts文件中的主機 self.sshclient.connect(HOSTS, PORT, USERNAME, PASSWORD) # 3 連接服務器 self.chan = self.sshclient.invoke_shell(term='xterm') # 4 建立交互式shell連接 self.chan.settimeout(0) t1 = MyThread(999, self) # 傳入webSSHServer對象 t1.setDaemon(True) # 守護線程,主線程退出時,需要子線程隨主線程退出 t1.start() # 收到信息的時候進行的動作(每次從前端收到執行的命令就會執行此函數) def on_message(self, message): try: self.chan.send(message) except Exception as ex: print(str(ex)) # 主動調用close()函數可以關閉這個連接(關閉瀏覽器時會觸發函數,關閉這個線程) def on_close(self): self.sshclient.close() # 每次有瀏覽器觸發新連接調用此函數 def check_origin(self, origin): # 允許跨域訪問 return True if __name__ == '__main__': # 定義路由 app = tornado.web.Application([ (r"/terminals/", webSSHServer), ], debug=True ) # 啟動服務器 http_server = tornado.httpserver.HTTPServer(app) http_server.listen(3000) tornado.ioloop.IOLoop.current().start()
1.4 后端django代碼(方法二)
基於django的WebSSH: https://blog.51cto.com/hequan/2145007
1、安裝相關包
# requirements.txt
dwebsocket==0.5.10 Django==2.0.4 paramiko==2.4.1
2、django端代碼

from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('terminals/', views.webssh), # 前端的 ws 連接地址 ]

from django.shortcuts import render import paramiko import threading from dwebsocket.decorators import accept_websocket,require_websocket def _ssh(host,username,password,port=22): sh = paramiko.SSHClient() # 1 創建SSH對象 sh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 2 允許連接不在know_hosts文件中的主機 sh.connect(host, username=username, password=password) # 3 連接服務器 channle = sh.invoke_shell(term='xterm') # 4 建立交互式shell連接 return channle def recv_ssh_msg(channle,ws): ''' channle: 建立好的SSH連接通道 這個函數會不停的接收ssh通道返回的命令 返回到前端的ws套接字里 ''' while not channle.exit_status_ready(): try: buf = channle.recv(1024) # 接收命令的執行結果 ws.send(buf) #發送消息到客戶端 except: break @accept_websocket def webssh(request): ''' 1: 接收前端(ws)的命令,發給后台(ssh) 2: 接收后台的返回結果,給到前端 ''' # request.is_websocket: 如果是個websocket請求返回True,如果是個普通的http請求返回False, 可以用這個方法區分它們 if request.is_websocket: host = '1.1.1.3' username = 'root' password = 'chnsys@2016' channle = _ssh(host, username=username, password=password) # 返回交互式shell連接對象 ws = request.websocket t = threading.Thread(target=recv_ssh_msg,args=(channle,ws)) # t.setDaemon(True) t.start() # 線程開啟 while 1: cmd = ws.wait() # 阻塞接收前端發來的命令 if cmd: channle.send(cmd) # 由SSH通道轉交給Linux環境 else: # 連接斷開 跳出循環 break ws.close() # 釋放對應套接字資源 channle.close()
3、效果圖