使用node.js开发webSocket开发代理,可以解决webSocket的cookies跨域问题。
这时有人会说,如果openstack的虚拟桌面流量太大,把代理冲内存溢出了,如何处理?
实际上,不是什么人都特别需要用WEB虚拟桌面操控虚拟机或物理机的,除非是windows系统,linux系统完全可以用流量更小的虚拟终端登陆。实际上进入linux虚拟桌面之后,好多操作不是还要用终端的吗?
业务层面,推荐使用linux系虚拟桌面的用户使用终端,甚至完全不提供虚拟桌面,这才是解决流量拥塞的方法。
然而想要在web上操纵linux终端,就需要通过 SSH 代理的方式调用并返回一个 shell 的虚拟终端(pty)的开源的 Web Terminal 项目。
这里为了防止SSH代理与项目耦合,导致代码难以查找,用node.js中间件或者Java的Springboot实现。
node.js的服务端实现(node.js对于websocket服务端的解决方法有二:原生websocket包和socket.io,后者可以在浏览器不支持的情况下转换为sockJS链接):
var http = require('http'); var io = require('socket.io'); var utf8 = require('utf8'); var SSHClient = require('ssh2').Client; var server2 = http.createServer(function(request, response) { console.log((new Date()) + ' Server is reseiveing on port 4041'); response.writeHead(204); response.end(); }); server2.listen(4041, function() { console.log((new Date()) + ' Server is listening on port 4041'); }); io = io.listen(server2,{origins: '*:*'}); function createNewServer(machineConfig, socket) { var ssh = new SSHClient(); let {msgId, ip, username, password, port, rows, cols} = machineConfig; ssh.on('ready', function () { socket.emit(msgId, '\r\n***' + ip + ' SSH CONNECTION ESTABLISHED ***\r\n'); ssh.shell(function(err, stream) { if(rows != null && cols != null) stream.setWindow(rows, cols); if(err) { return socket.emit(msgId, '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n'); } socket.on(msgId, function (data) { var mydata = data.data; if(mydata != null){ console.log(">>>" + data.data + "<<<"); stream.write(mydata); } var size = data.rows; if(size != null){ stream.setWindow(data.rows, data.cols); } }); stream.on('data', function (d) { try{ var mydata = utf8.decode(d.toString('binary')); mydata = mydata.replace(/ \r(?!\n)/g,''); console.log("<<<" + mydata + ">>>"); socket.emit(msgId, mydata); }catch(err){ socket.emit(msgId, '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n'); } }).on('close', function () { ssh.end(); }); }) }).on('close', function () { socket.emit(msgId, '\r\n*** SSH CONNECTION CLOSED ***\r\n'); ssh.end(); }).on('error', function (err) { console.log(err); socket.emit(msgId, '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n'); ssh.end(); }).connect({ host: ip, port: port, username: username, password: password }); } io.on('connection', function(socket) { socket.on('createNewServer', function(machineConfig) {//新建一个ssh连接 console.log("createNewServer"); createNewServer(machineConfig, socket); }) socket.on('disconnect', function(){ console.log('user disconnected'); }); })
Vue.js代码这样写(需导入xterm):
<template> </template> <script> import 'xterm/dist/xterm.css' import { Terminal } from 'xterm'; import * as fit from 'xterm/dist/addons/fit/fit'; import * as fullscreen from 'xterm/dist/addons/fullscreen/fullscreen' import openSocket from 'socket.io-client'; export default { name: 'sshweb', props:['ip','port'], data () { return { wsServer:null, localip:'', localport:'', env: "", podName: "", contaName: "", logtxt: "", term:[0,0], colsLen:9, rowsLen:19, colRemain:21, msgId:0, col:80, row:24, terminal: { pid: 1, name: 'terminal', cols: 80, rows: 24 }, } }, watch:{ port(val){ this.localport=port; }, ip(val){ this.localip=ip; } }, methods: { S4() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); }, guid() { return (this.S4()+this.S4()+"-"+this.S4()+"-"+this.S4()+"-"+this.S4()+"-"+this.S4()+this.S4()+this.S4()); }, createServer1(){ this.msgId = this.guid(); var msgId = this.msgId; var myserver = this.wsServer; var selfy = this; var ipport = this.$route.params.ipport.split(':'); var myport = ipport[0]; var myip = ipport[1]; myserver.emit("createNewServer", {msgId: msgId, ip: myport, username: "root", password: "xunfang", port: myip, rows: this.term[0].rows, cols: this.term[0].cols}); let term = this.term[0]; term.on("data", function(data) { myserver.emit(msgId, {'data':data}); }); myserver.on(msgId, function (data) { term.write(data); }); term.attachCustomKeyEventHandler(function(ev) { if (ev.keyCode == 86 && ev.ctrlKey) { myserver.emit(msgId, new TextEncoder().encode("\x00" + this.copy)); } }); myserver.on('connect_error', function(data){ console.log(data + ' - connect_error'); }); myserver.on('connect_timeout', function(data){ console.log(data + ' - connect_timeout'); }); myserver.on('error', function(data){ console.log(data + ' - error'); }); myserver.on('disconnect', function(data){ console.log(data + ' - disconnect'); }); myserver.on('reconnect', function(data){ console.log(data + ' - reconnect'); }); myserver.on('reconnect_attempt', function(data){ console.log(data + ' - reconnect_attempt'); }); myserver.on('reconnecting', function(data){ console.log(data + ' - reconnecting'); }); myserver.on('reconnect_error', function(data){ console.log(data + ' - reconnect_error'); }); myserver.on('reconnect_failed', function(data){ console.log(data + ' - reconnect_failed'); }); myserver.on('ping', function(data){ console.log(data + ' - ping'); }); myserver.on('pong', function(data){ console.log(data + ' - pong'); }); }, resize(row,col){ row = Math.floor(row/this.rowsLen); col = Math.floor((col-this.colRemain)/this.colsLen); if(row<24)row=24; if(col<80)col=80; if(this.row != row || this.col != col){ this.row=row; this.col=col; this.term[0].fit(); //this.term[0].resize(col,row); this.wsServer.emit(this.msgId, {'rows':this.term[0].rows.toString(),'cols':this.term[0].cols.toString()}); //this.wsServer.emit(this.msgId, {'rows':row.toString(),'cols':col.toString()}); } } }, mounted(){ this.wsServer = new openSocket('ws://127.0.0.1:4041'); var selfy = this; window.onload = function(){ for(var i = 0;i < 1;i++){ var idname = 'net0'; Terminal.applyAddon(fit); Terminal.applyAddon(fullscreen); var terminalContainer = document.getElementById('app'); //terminalContainer.style.height = (selfy.rowsLen * selfy.terminal.rows).toString() + 'px' ; //terminalContainer.style.width = (selfy.colsLen * selfy.terminal.cols + selfy.colRemain).toString() + 'px' ; selfy.term[i] = new Terminal({ cursorBlink: true }); selfy.term[i].open(terminalContainer, true); if(window.innerWidth > 0 && window.innerHeight > 0) selfy.term[i].fit(); selfy.createServer1(); } } $(window).resize(() =>{ if(window.innerWidth > 0 && window.innerHeight > 0){ selfy.resize(window.innerHeight, window.innerWidth); } }); }, components: { } } </script> <style scoped> #app{height:100%;width:100%;} </style>
打开服务器和Vue系统,登陆这个客户端上SSH,大功告成!
Java部分:
这里有个完整的解决方案,在我改正过的github仓库里。
扫描二维码关注公众号,回复:
7041295 查看本文章
下载项目,开启Springboot,在浏览器上访问http://localhost:10003/,会进入登录页面,目前不支持RSA秘钥登录,只支持账号密码登录。
两个项目的SSH窗口都是全屏的,只要窗口不小于某个大小,窗口的字会随着窗口缩放而调整位置。