zoukankan      html  css  js  c++  java
  • 如何解决Vue.js里面noVNC的截图问题(2)——蛋疼的cookies验证和node.js的websocket代理

      在上一篇讲noVNC截图功能的文章中,我们利用WebSocket协议的连接不检查跨域的特性解决了noVNC截图失败的问题。

      但是这个方法仅限于没有cookies验证的noVNC服务,但是openstack的noVNC服务,每个虚拟机都要带token,而这个token,是写在cookies里面的。

      看了openstack虚拟桌面的代码,知道里面的代码用token = WebUtil.getQueryVar('token', null);和WebUtil.createCookie('token', token, 1)这两句语句获取token再放入cookies,但是把代码放入第1章的Vue代码里面,连接失败。调出F12抓包窗口,发现由于headers关于跨域没设置好,cookies浏览器不认,没有传入服务器。

      解决这个问题的思路是:找一个无视跨域限制,能传cookies的webSocket的客户端,并且对应系统也能开websocket服务端,这个系统作为openstack和主网站的webSocket代理传输数据,Node.js就可以用来解决,刚好整个Vue.js前端项目基于node.js。

    var WebSocketServer = require('websocket').server;
    var http = require('http');
    var WebSocketClient = require('websocket').client;
    
    var server1 = http.createServer(function(request, response) {
        console.log((new Date()) + ' Server is reseiveing on port 4949');
        response.writeHead(204);
        response.end();
    });
    server1.listen(4949, function() {
        console.log((new Date()) + ' Server is listening on port 4949');
    });
    
    var wsServer = new WebSocketServer({httpServer: server1,autoAcceptConnections: false});
    
    wsServer.on('request', function(request) {
        var token = request['resourceURL']['query']['token'];
        var ip = request['resourceURL']['query']['ip'];
        var client = new WebSocketClient();
        //模拟noVNC页面连接服务的过程
        client.connect('ws://' + ip + '/websockify','binary',
        'http://' + ip,{'Cookie':'token='+token});
        client.on('connectFailed',error=>{console.log(error);});
        //browserConnection就是客户端的webSocket连接
        var browserConnection = request.accept('binary', request.origin);
        client.on('connect', function(connection) {
            //connection就是服务端的连接
            connection.on('error', function(error) {
                console.log("Connection Error: " + error.toString());
                if(browserConnection != null)browserConnection.close();
                browserConnection = null;
            });
            connection.on('close', function() {
                console.log("Connection Close");
                if(browserConnection != null)browserConnection.close();
                browserConnection = null;
            });
            connection.on('message', function(message) {
                if (message.type === 'utf8') {
                    browserConnection.sendUTF(message.utf8Data);
                }
                else if(message.type === 'binary'){
                    browserConnection.sendBytes(message.binaryData);
                }
            });
            browserConnection.on('message', function(message) {
                if (message.type === 'utf8') {
                    connection.sendUTF(message.utf8Data);
                }
                else if (message.type === 'binary') {
                    connection.sendBytes(message.binaryData);
                }
            });
            browserConnection.on('error', function(error){
                console.log("BrowserConnection Error: " + error.toString());
                if(connection != null)connection.close();
                connection = null;
            });
            browserConnection.on('close', function(reasonCode, description){
                console.log("BrowserConnection Close");
                if(connection != null)connection.close();
                connection = null;
            });
        });
    });
    Node.js websocket代理

      客户端的Vue.js代码

    <template> 
      <div id="noVNC_all">
      <div id="noVNC_status_bar">
        <div id="noVNC_left_dummy_elem"></div>
        <div id="noVNC_status">Loading</div>
        <div id="noVNC_buttons">
          <input type=button value="Send CtrlAltDel"
                 id="sendCtrlAltDelButton" class="noVNC_shown">
          <span id="noVNC_power_buttons" class="noVNC_hidden">
            <input type=button value="Shutdown"
                   id="machineShutdownButton">
            <input type=button value="Reboot"
                   id="machineRebootButton">
            <input type=button value="Reset"
                   id="machineResetButton">
          </span>
        </div>
      </div>
      </div>
    </template>
    
    <script>
    import * as WebUtil from './webutil.js';
    import RFB from '@novnc/novnc/core/rfb.js';
    export default {
         components:{
         },
      data() {
        return {
              rfb:null,
              desktopName:null
        };
      },
      methods: {
        connectVNC () {},
        updateDesktopName(e) {
                this.desktopName = e.detail.name;
            },
        credentials(e) {
                var html;
    
                var form = document.createElement('form');
                form.innerHTML = '<label></label>';
                form.innerHTML += '<input type=password size=10 id="password_input">';
                form.onsubmit = this.setPassword;
    
                // bypass status() because it sets text content
                document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
                document.getElementById('noVNC_status').innerHTML = '';
                document.getElementById('noVNC_status').appendChild(form);
                document.getElementById('noVNC_status').querySelector('label').textContent = 'Password Required: ';
            },
        setPassword() {
                this.rfb.sendCredentials({ password: document.getElementById('password_input').value });
                return false;
            },
        sendCtrlAltDel() {
                this.rfb.sendCtrlAltDel();
                return false;
            },
        machineShutdown() {
                this.rfb.machineShutdown();
                return false;
            },
        machineReboot() {
                this.rfb.machineReboot();
                return false;
            },
        machineReset() {
                this.rfb.machineReset();
                return false;
            },
        status(text, level) {
                switch (level) {
                    case 'normal':
                    case 'warn':
                    case 'error':
                        break;
                    default:
                        level = "warn";
                }
                document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level;
                document.getElementById('noVNC_status').textContent = text;
            },
        connected(e) {
                document.getElementById('sendCtrlAltDelButton').disabled = false;
                if (WebUtil.getConfigVar('encrypt',
                                         (window.location.protocol === "https:"))) {
                    this.status("Connected (encrypted) to " + this.desktopName, "normal");
                } else {
                    this.status("Connected (unencrypted) to " + this.desktopName, "normal");
                }
            },
        disconnected(e) {
                document.getElementById('sendCtrlAltDelButton').disabled = true;
                this.updatePowerButtons();
                if (e.detail.clean) {
                    this.status("Disconnected", "normal");
                } else {
                    this.status("Something went wrong, connection is closed", "error");
                }
            },
        updatePowerButtons() {
                var powerbuttons;
                powerbuttons = document.getElementById('noVNC_power_buttons');
                if (this.rfb.capabilities.power) {
                    powerbuttons.className= "noVNC_shown";
                } else {
                    powerbuttons.className = "noVNC_hidden";
                }
            }
      },
      mounted() {
            document.getElementById('sendCtrlAltDelButton').onclick = this.sendCtrlAltDel;
            document.getElementById('machineShutdownButton').onclick = this.machineShutdown;
            document.getElementById('machineRebootButton').onclick = this.machineReboot;
            document.getElementById('machineResetButton').onclick = this.machineReset;
    
            WebUtil.init_logging(WebUtil.getConfigVar('logging', 'warn'));
            document.title = WebUtil.getConfigVar('title', 'noVNC');
            // By default, use the host and port of server that served this file
            var host = WebUtil.getConfigVar('host', window.location.hostname);
            var port = WebUtil.getConfigVar('port', window.location.port);
    
            // if port == 80 (or 443) then it won't be present and should be
            // set manually
            if (!port) {
                if (window.location.protocol.substring(0,5) == 'https') {
                    port = 443;
                }
                else if (window.location.protocol.substring(0,4) == 'http') {
                    port = 80;
                }
            }
            if(this.$route.params.ipport.indexOf('-') == -1)
                var password = WebUtil.getConfigVar('password', '123456');
            else
                var password = WebUtil.getConfigVar('password', '');
            var path = WebUtil.getConfigVar('path', 'websockify');
    
            // If a token variable is passed in, set the parameter in a cookie.
            // This is used by nova-novncproxy.
            var token = WebUtil.getConfigVar('token', null);
            if (token) {
                // if token is already present in the path we should use it
                path = WebUtil.injectParamIfMissing(path, "token", token);
    
                WebUtil.createCookie('token', token, 1)
            }
                this.status("Connecting", "normal");
    
                if ((!host) || (!port)) {
                    this.status('Must specify host and port in URL', 'error');
                }
    
                var url;
    
                if (WebUtil.getConfigVar('encrypt',
                                         (window.location.protocol === "https:"))) {
                    url = 'wss';
                } else {
                    url = 'ws';
                }
    
                if(this.$route.params.ipport == null)
                    url += '://192.168.80.61:30926/websockify';
                else if(this.$route.params.ipport.indexOf('-') == -1)//对于符合非openstack链接的ip端口处理
                    url += '://' + this.$route.params.ipport + '/websockify';
                else//对于符合openstack链接的ip端口处理
                    url += '://localhost:4949/websockify/websockify?token=' + this.$route.params.ipport.split(':-')[1] + '&ip=' + this.$route.params.ipport.split(':-')[0];
    
                this.rfb = new RFB(document.querySelector('#noVNC_all'), url,
                              { repeaterID: WebUtil.getConfigVar('repeaterID', ''),
                                shared: WebUtil.getConfigVar('shared', true),
                                credentials: { password: password } });
                this.rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
                this.rfb.addEventListener("connect",  this.connected);
                this.rfb.addEventListener("disconnect", this.disconnected);
                this.rfb.addEventListener("capabilities", function () { this.updatePowerButtons(); });
                this.rfb.addEventListener("credentialsrequired", this.credentials);
                this.rfb.addEventListener("desktopname", this.updateDesktopName);
                this.rfb.scaleViewport = WebUtil.getConfigVar('scale', false);
                this.rfb.resizeSession = WebUtil.getConfigVar('resize', false);
      }
    };
    </script>
    <style lang='scss' scoped>
    #noVNC_status_bar {
      width: 100%;
      display:flex;
      justify-content: space-between;
    }
    
    #noVNC_status {
      color: #fff;
      font: bold 12px Helvetica;
      margin: auto;
    }
    
    .noVNC_status_normal {
      background: linear-gradient(#b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%);
    }
    
    .noVNC_status_error {
      background: linear-gradient(#c83737 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%);
    }
    
    .noVNC_status_warn {
      background: linear-gradient(#b4b41e 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%);
    }
    
    .noNVC_shown {
      display: inline;
    }
    .noVNC_hidden {
      display: none;
    }
    
    #noVNC_left_dummy_elem {
      flex: 1;
    }
    
    #noVNC_buttons {
      padding: 1px;
      flex: 1;
      display: flex;
      justify-content: flex-end;
    }
    </style>
    客户端的Vue.js代码

      将node.js代理程序开起来之后,登陆对应页面。再点击截图按钮,可以看到截图成功了,并且之前的cookies跨域问题也没有了。

  • 相关阅读:
    +1和*2
    线段树(区间最大值和最大值的个数)
    CodeForces
    莫队算法入门(暴力而不失优雅)
    二分迷宫
    全错排公式
    C++ PAT乙 1051. 复数乘法 (15)
    C++ PAT乙 1070. 结绳(25)
    C++ PAT乙 1080. MOOC期终成绩 (25)
    C++ PAT 1073. 多选题常见计分法(20)
  • 原文地址:https://www.cnblogs.com/dgutfly/p/11359434.html
Copyright © 2011-2022 走看看