zoukankan      html  css  js  c++  java
  • 如何解决Vue.js里面noVNC的截图问题之后篇——用web虚拟终端作为替代功能

      使用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, '
    ***' + ip + ' SSH CONNECTION ESTABLISHED ***
    ');
            ssh.shell(function(err, stream) {
                if(rows != null && cols != null)
                    stream.setWindow(rows, cols);
                if(err) {
                    return socket.emit(msgId, '
    *** SSH SHELL ERROR: ' + err.message + ' ***
    ');
                }
                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(/ 
    (?!
    )/g,'');
                        console.log("<<<" + mydata + ">>>");
                        socket.emit(msgId, mydata);
                    }catch(err){
                        socket.emit(msgId, '
    *** SSH CONNECTION ERROR: ' + err.message + ' ***
    ');
                    }
                }).on('close', function () {
                    ssh.end();
                });
            })
        }).on('close', function () {
            socket.emit(msgId, '
    *** SSH CONNECTION CLOSED ***
    ');
            ssh.end();
        }).on('error', function (err) {
            console.log(err);
            socket.emit(msgId, '
    *** SSH CONNECTION ERROR: ' + err.message + ' ***
    ');
            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');
        });
    })
    node ssh代理

      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.js的ssh客户端

      打开服务器和Vue系统,登陆这个客户端上SSH,大功告成!

      Java部分:

      这里有个完整的解决方案,在我改正过的github仓库里。

      这里说一下websocket连接代码:

      vip.r0n9.ws.WebSshHandler:

    package vip.r0n9.ws;
    
    import com.jcraft.jsch.JSchException;
    import org.springframework.stereotype.Component;
    import vip.r0n9.util.SshSession;
    
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.*;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    @ServerEndpoint(value = "/ssh/{id}", configurator = WebSocketConfigrator.class)
    @Component
    public class WebSshHandler {
    
        private static Map<String, SshSession> map = new ConcurrentHashMap<String, SshSession>();
    
        @OnOpen
        public void onOpen(final Session session, @PathParam("id") String id) throws JSchException, IOException, EncodeException, InterruptedException {
            System.out.println("有新链接 " + session.getUserProperties().get("ClientIP") + " 加入!当前在线人数为" + getOnlineCount());
    
            Map<String,String> parammap = new HashMap<String,String>();
            String[] param =  session.getQueryString().split("&");
            for(String keyvalue:param){
               String[] pair = keyvalue.split("=");
               if(pair.length==2){
                   parammap.put(pair[0], pair[1]);
               }
            }
            
            String hostname = parammap.get("hostname");
            String password = parammap.get("password");
            Integer port,cols,rows;
            try {
                port = Integer.valueOf(parammap.get("port"));
            }catch(Exception e) {
                port = 22;
            }
            String username = parammap.get("username");
            try {
                rows = Integer.valueOf(parammap.get("rows"));
            }catch(Exception e) {
                rows = 24;
            }
            try {
                cols = Integer.valueOf(parammap.get("cols"));
            }catch(Exception e) {
                cols = 80;
            }
            
            SshSession sshSession;
            sshSession = new SshSession(hostname, port, username, password, session, rows, cols);
            map.put(session.getId(), sshSession);
        }
    
        @OnClose
        public void onClose(Session session) {
            SshSession sshsession = map.remove(session.getId());
            sshsession.close();
        }
    
        @OnMessage
        public void onMessage(String message, Session session) throws IOException, JSchException {
            map.get(session.getId()).getMessage(message);
        }
        
        @OnError
        public void onError(Session session, Throwable throwable) {
            throwable.printStackTrace();
            try {
                session.getBasicRemote().sendText(throwable.getMessage());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        public static synchronized int getOnlineCount() {
            return map.size();
        }
    }
    WebSshController.java

      这里就是websocket服务端代码,连接websocket首先要在这里进行处理。

      vip.r0n9.util.SshSession:

    package vip.r0n9.util;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.nio.ByteBuffer;
    import java.util.Iterator;
    
    import javax.websocket.Session;
    
    import com.fasterxml.jackson.databind.JsonNode;
    import com.jcraft.jsch.Channel;
    import com.jcraft.jsch.ChannelShell;
    import com.jcraft.jsch.JSch;
    import com.jcraft.jsch.JSchException;
    
    import vip.r0n9.JsonUtil;
    import vip.r0n9.ws.WebSshHandler;
    
    public class SshSession {
    
        private Session websession;//从客户端发起的websocket连接
    
        private StringBuilder dataToDst = new StringBuilder();
    
        private JSch jsch = new JSch();//ssh客户端
    
        private com.jcraft.jsch.Session jschSession;//ssh服务端返回的单个客户端连接
    
        private ChannelShell channel;
    
        private InputStream inputStream;
        private BufferedReader stdout;
    
        private OutputStream outputStream;
        private PrintWriter printWriter;
        
        public SshSession() {}
        
        public SshSession(String hostname,int port,String username, String password, final Session session2, int rows, int cols) throws JSchException, IOException {
            this.websession = session2;
            jschSession = jsch.getSession(username, hostname, port);
            jschSession.setPassword(password);
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            jschSession.setConfig(config);
            jschSession.connect();
    
            channel = (ChannelShell) jschSession.openChannel("shell");
            channel.setPty(true);
            channel.setPtyType("xterm");
            channel.setPtySize(cols, rows, cols*8, rows*16);
            inputStream = channel.getInputStream();
            
            outputStream = channel.getOutputStream();
            printWriter = new PrintWriter(outputStream,false);
            channel.connect();
            
            outputStream.write("
    ".getBytes());
            outputStream.flush();
            //这里可以用newFixedThreadPool线程池,可以更方便管理线程
            Thread thread = new Thread() {
    
                @Override
                public void run() {
    
                    try {
                        byte[] byteset = new byte[3072];
                        int res = inputStream.read(byteset);
                        if(res == -1)res = 0;
                        while (session2 != null && session2.isOpen()) { // 这里会阻塞,所以必须起线程来读取channel返回内容
                            ByteBuffer byteBuffer = ByteBuffer.wrap(byteset, 0, res);
                            synchronized (this) {
                                if(res != 0)
                                    session2.getBasicRemote().sendBinary(byteBuffer);
                            }
                            res = inputStream.read(byteset);
                            if(res == -1)res = 0;
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };
            thread.start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public void close() {
            channel.disconnect();
            jschSession.disconnect();
            try {
                this.websession.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                this.inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                this.outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        public void getMessage(String message) throws IOException, JSchException {
    
            Session mysession = this.websession;
            System.out.println("来自客户端 " + mysession.getUserProperties().get("ClientIP") + " 的消息:" + message);
    
            JsonNode node = JsonUtil.strToJsonObject(message);
    
            if (node.has("resize")) {
                Iterator<JsonNode> myiter = node.get("resize").elements();
                int col = myiter.next().asInt();
                int row = myiter.next().asInt();
                channel.setPtySize(col, row, col*8, row*16);
                return;
            }
    
            if (node.has("data")) {
                String str = node.get("data").asText();
    
                outputStream.write(str.getBytes("utf-8"));
                outputStream.flush();
    
                return;
            }
    
        }
    
        public StringBuilder getDataToDst() {
            return dataToDst;
        }
    
        public OutputStream getOutputStream() {
            return outputStream;
        }
    
    }
    SshSession.java

      代理SSH客户端的核心逻辑在此,这里要注意不要用Reader和Writer,一些终端功能会无法运行。

      下载项目,开启Springboot,在浏览器上访问http://localhost:10003/,会进入登录页面,目前不支持RSA秘钥登录,只支持账号密码登录。

      客户端也可以用Vue.js实现:

    <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 selfy = this;
                    var ipport = this.$route.params.ipport.split(':');
                    var myport = ipport[1];
                    var myip = ipport[0];
                    var wsurl = 'ws://127.0.0.1:10003/ssh/1?hostname=' + myip + '&port=' + myport + '&username=root&password=xunfang';
                    this.wsServer = new WebSocket(wsurl);
                    var myserver = this.wsServer;
                    let term = this.term[0];
                    term.on("data", function(data) {
                        var you = data;
                        if(you.length > 1)you = you[0];
                        console.log(you.charCodeAt());
                        myserver.send(JSON.stringify({'data': data}));
                    });
                    
                    myserver.onopen = function(evt) {
                        console.log(evt);
                    };
                    
                    myserver.onmessage = function(msg) {
                        var reader = new window.FileReader();
                        var isend = false;
    
                        reader.onloadend = function(){
                            var decoder = new window.TextDecoder('utf-8');
                            console.log(decoder);
                            var text = decoder.decode(reader.result);
                            console.log(text);
                            term.write(text);
                        };
                        
                        reader.readAsArrayBuffer(msg.data);
                    };
                    term.attachCustomKeyEventHandler(function(ev) {
                        if (ev.keyCode == 86 && ev.ctrlKey) {
                            myserver.send(JSON.stringify({'data': new TextEncoder().encode("x00" + this.copy)}));
                        }
                    });
                },
                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();
                        myserver.send(JSON.stringify({'resize': [cols, rows]}));
                    }
                }
            },
            mounted(){
                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');
                        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>
    sshClient.vue

      两个项目的SSH窗口都是全屏的,只要窗口不小于某个大小,窗口的字会随着窗口缩放而调整位置。

      虚拟终端演示:

      

  • 相关阅读:
    leetcode 350. Intersection of Two Arrays II
    leetcode 278. First Bad Version
    leetcode 34. Find First and Last Position of Element in Sorted Array
    leetcode 54. Spiral Matrix
    leetcode 59. Spiral Matrix II
    leetcode 44. Wildcard Matching
    leetcode 10. Regular Expression Matching(正则表达式匹配)
    leetcode 174. Dungeon Game (地下城游戏)
    leetcode 36. Valid Sudoku
    Angular Elements
  • 原文地址:https://www.cnblogs.com/dgutfly/p/11359632.html
Copyright © 2011-2022 走看看