标签:端口 sub word scree play cas ctp 控制 utf-8
noVNC可以给linux系统提供基于VNC虚拟桌面的WEB服务,这使得openstack使用noVNC对外提供虚拟机的WEB版虚拟桌面。
不过用这个noVNC也有一些问题,在使用HTML2canvas截图或者使用一些需要外部操控的操作就出问题。
经查,HTML2canvas这个js控件的工作原理是读取HTML元素,但是noVNC或openstack提供的noVNC窗口url都是与现在用的系统不同域(简单来说这些服务就是运行在不同的机子上),这一步因为headers不支持跨域的问题失败了——下载的截图noVNC画面部分为空白,键入F12查看控制台,显示Uncaught SecurityError: Failed to execute ‘toDataURL‘ on ‘HTMLCanvasElement‘: Tainted canvases may not be exported.
网上很多解决方案,无一不是在服务端修改配置、修改headers、修改HTML2canvas参数使被跨域的服务支持跨域,但是画面截图这个功能,发起者是外部系统,跨的域是noVNC或openstack的ip和端口,没有支持跨域的配置,又不能随意修改里面的代码,修改HTML2canvas参数也无效。
这时就有一个想法:既然noVNC的窗口本质上就是一堆HTML代码,是否可以将代码直接贴在本系统上?
noVNC窗口代码
<!DOCTYPE html> <html> <head> <!-- noVNC example: lightweight example using minimal UI and features Copyright (C) 2012 Joel Martin Copyright (C) 2017 Samuel Mannehed for Cendio AB noVNC is licensed under the MPL 2.0 (see LICENSE.txt) This file is licensed under the 2-Clause BSD license (see LICENSE.txt). Connect parameters are provided in query string: http://example.com/?host=HOST&port=PORT&encrypt=1 or the fragment: http://example.com/#host=HOST&port=PORT&encrypt=1 --> <title>noVNC</title> <meta charset="utf-8"> <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame Remove this if you use the .htaccess --> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <!-- Icons (see Makefile for what the sizes are for) --> <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png"> <link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png"> <link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/novnc-32x32.png"> <link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/novnc-48x48.png"> <link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png"> <link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/novnc-64x64.png"> <link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/novnc-72x72.png"> <link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png"> <link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/novnc-96x96.png"> <link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png"> <link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/novnc-144x144.png"> <link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png"> <link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/novnc-192x192.png"> <!-- Firefox currently mishandles SVG, see #1419039 <link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/novnc-icon.svg"> --> <!-- Repeated last so that legacy handling will pick this --> <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png"> <!-- Apple iOS Safari settings --> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <!-- Home Screen Icons (favourites and bookmarks use the normal icons) --> <link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png"> <link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png"> <link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png"> <link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png"> <!-- Stylesheets --> <link rel="stylesheet" href="app/styles/lite.css"> <!-- <script type=‘text/javascript‘ src=‘http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js‘></script> --> <!-- promise polyfills promises for IE11 --> <script src="vendor/promise.js"></script> <!-- ES2015/ES6 modules polyfill --> <script type="module"> window._noVNC_has_module_support = true; </script> <script> window.addEventListener("load", function() { if (window._noVNC_has_module_support) return; var loader = document.createElement("script"); loader.src = "vendor/browser-es-module-loader/dist/browser-es-module-loader.js"; document.head.appendChild(loader); }); </script> <!-- actual script modules --> <script type="module" crossorigin="anonymous"> // Load supporting scripts import * as WebUtil from ‘./app/webutil.js‘; import RFB from ‘./core/rfb.js‘; var rfb; var desktopName; function updateDesktopName(e) { desktopName = e.detail.name; } function 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 = 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: ‘; } function setPassword() { rfb.sendCredentials({ password: document.getElementById(‘password_input‘).value }); return false; } function sendCtrlAltDel() { rfb.sendCtrlAltDel(); return false; } function machineShutdown() { rfb.machineShutdown(); return false; } function machineReboot() { rfb.machineReboot(); return false; } function machineReset() { rfb.machineReset(); return false; } function 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; } function connected(e) { document.getElementById(‘sendCtrlAltDelButton‘).disabled = false; if (WebUtil.getConfigVar(‘encrypt‘, (window.location.protocol === "https:"))) { status("Connected (encrypted) to " + desktopName, "normal"); } else { status("Connected (unencrypted) to " + desktopName, "normal"); } } function disconnected(e) { document.getElementById(‘sendCtrlAltDelButton‘).disabled = true; updatePowerButtons(); if (e.detail.clean) { status("Disconnected", "normal"); } else { status("Something went wrong, connection is closed", "error"); } } function updatePowerButtons() { var powerbuttons; powerbuttons = document.getElementById(‘noVNC_power_buttons‘); if (rfb.capabilities.power) { powerbuttons.className= "noVNC_shown"; } else { powerbuttons.className = "noVNC_hidden"; } } document.getElementById(‘sendCtrlAltDelButton‘).onclick = sendCtrlAltDel; document.getElementById(‘machineShutdownButton‘).onclick = machineShutdown; document.getElementById(‘machineRebootButton‘).onclick = machineReboot; document.getElementById(‘machineResetButton‘).onclick = 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; } } 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) } (function() { status("Connecting", "normal"); if ((!host) || (!port)) { status(‘Must specify host and port in URL‘, ‘error‘); } var url; if (WebUtil.getConfigVar(‘encrypt‘, (window.location.protocol === "https:"))) { url = ‘wss‘; } else { url = ‘ws‘; } //noVNC本质上是用webSocket实时传输信息的 url += ‘://‘ + host; if(port) { url += ‘:‘ + port; } url += ‘/‘ + path; rfb = new RFB(document.body, url, { repeaterID: WebUtil.getConfigVar(‘repeaterID‘, ‘‘), shared: WebUtil.getConfigVar(‘shared‘, true), credentials: { password: password } }); rfb.viewOnly = WebUtil.getConfigVar(‘view_only‘, false); rfb.addEventListener("connect", connected); rfb.addEventListener("disconnect", disconnected); rfb.addEventListener("capabilities", function () { updatePowerButtons(); }); rfb.addEventListener("credentialsrequired", credentials); rfb.addEventListener("desktopname", updateDesktopName); rfb.scaleViewport = WebUtil.getConfigVar(‘scale‘, false); rfb.resizeSession = WebUtil.getConfigVar(‘resize‘, false); })(); </script> </head> <body> <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> </body> </html>
从代码里面可以看到,传输noVNC虚拟桌面关键点在225行的url对应的webSocket链接,而这个链接恰好就是noVNC提供服务的ip和端口,实际上完全可以把整个页面内嵌在提供虚拟桌面的窗口,或者写在同域系统里面,让其他页面在iframe框架里面调用。
我们项目用的是Vue.js,为了使页面能适应Vue系统,把代码重构成了这样:
<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) url += ‘://‘ + this.$route.params.ipport + ‘/websockify‘; else url += ‘://localhost:10003/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>
代码中this.$route.params.ipport可以改成其他提供noVNC服务的ip端口。
WebSocket协议的连接是不会验证跨域的,所以即使WebSocket的ip端口和本页面的不同也没关系。
这个页面是写成单独的vue文件,让其他vue通过iframe调用的,这个iframe里面的src和外部页面同域,HTML2canvas成功截到图
如何解决Vue.js里面noVNC的截图问题(1)——论可以跨域的webSocket
标签:端口 sub word scree play cas ctp 控制 utf-8
原文地址:https://www.cnblogs.com/dgutfly/p/11353874.html