ArcherWong博客
首页
博客
guacamole实现上传下载
作者:ArcherWong
分类:guacamole
时间:2019-01-04 10:34:45
阅读:916
[TOC] 分析的入手点,查看websocket连接的frame  看到首先服务端向客户端发送了filesystem请求,紧接着浏览器向服务端发送了get请求,并且后面带有根目录标识(“/”)。 # 1. 源码解读 查看指令 ``` /** * Handlers for all instruction opcodes receivable by a Guacamole protocol * client. * @private */ var instructionHandlers = { ...其它指令 "filesystem" : function handleFilesystem(parameters) { var objectIndex = parseInt(parameters[0]); var name = parameters[1]; // Create object, if supported if (guac_client.onfilesystem) { //这里实例化一个object,并且传递给客户端监听的onfilesystem方法 var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex); guac_client.onfilesystem(object, name); } // If unsupported, simply ignore the availability of the filesystem }, ...其它指令 } ``` 查看实例化的object源码 ``` /** * An object used by the Guacamole client to house arbitrarily-many named * input and output streams. * * @constructor * @param {Guacamole.Client} client * The client owning this object. * * @param {Number} index * The index of this object. */ Guacamole.Object = function guacamoleObject(client, index) { /** * Reference to this Guacamole.Object. * * @private * @type {Guacamole.Object} */ var guacObject = this; /** * Map of stream name to corresponding queue of callbacks. The queue of * callbacks is guaranteed to be in order of request. * * @private * @type {Object.<String, Function[]>} */ var bodyCallbacks = {}; /** * Removes and returns the callback at the head of the callback queue for * the stream having the given name. If no such callbacks exist, null is * returned. * * @private * @param {String} name * The name of the stream to retrieve a callback for. * * @returns {Function} * The next callback associated with the stream having the given name, * or null if no such callback exists. */ var dequeueBodyCallback = function dequeueBodyCallback(name) { // If no callbacks defined, simply return null var callbacks = bodyCallbacks[name]; if (!callbacks) return null; // Otherwise, pull off first callback, deleting the queue if empty var callback = callbacks.shift(); if (callbacks.length === 0) delete bodyCallbacks[name]; // Return found callback return callback; }; /** * Adds the given callback to the tail of the callback queue for the stream * having the given name. * * @private * @param {String} name * The name of the stream to associate with the given callback. * * @param {Function} callback * The callback to add to the queue of the stream with the given name. */ var enqueueBodyCallback = function enqueueBodyCallback(name, callback) { // Get callback queue by name, creating first if necessary var callbacks = bodyCallbacks[name]; if (!callbacks) { callbacks = []; bodyCallbacks[name] = callbacks; } // Add callback to end of queue callbacks.push(callback); }; /** * The index of this object. * * @type {Number} */ this.index = index; /** * Called when this object receives the body of a requested input stream. * By default, all objects will invoke the callbacks provided to their * requestInputStream() functions based on the name of the stream * requested. This behavior can be overridden by specifying a different * handler here. * * @event * @param {Guacamole.InputStream} inputStream * The input stream of the received body. * * @param {String} mimetype * The mimetype of the data being received. * * @param {String} name * The name of the stream whose body has been received. */ this.onbody = function defaultBodyHandler(inputStream, mimetype, name) { // Call queued callback for the received body, if any var callback = dequeueBodyCallback(name); if (callback) callback(inputStream, mimetype); }; /** * Called when this object is being undefined. Once undefined, no further * communication involving this object may occur. * * @event */ this.onundefine = null; /** * Requests read access to the input stream having the given name. If * successful, a new input stream will be created. * * @param {String} name * The name of the input stream to request. * * @param {Function} [bodyCallback] * The callback to invoke when the body of the requested input stream * is received. This callback will be provided a Guacamole.InputStream * and its mimetype as its two only arguments. If the onbody handler of * this object is overridden, this callback will not be invoked. */ this.requestInputStream = function requestInputStream(name, bodyCallback) { // Queue body callback if provided if (bodyCallback) enqueueBodyCallback(name, bodyCallback); // Send request for input stream client.requestObjectInputStream(guacObject.index, name); }; /** * Creates a new output stream associated with this object and having the * given mimetype and name. The legality of a mimetype and name is dictated * by the object itself. * * @param {String} mimetype * The mimetype of the data which will be sent to the output stream. * * @param {String} name * The defined name of an output stream within this object. * * @returns {Guacamole.OutputStream} * An output stream which will write blobs to the named output stream * of this object. */ this.createOutputStream = function createOutputStream(mimetype, name) { return client.createObjectOutputStream(guacObject.index, mimetype, name); }; }; ``` 读取下官方的注释,关于此类的定义: ``` An object used by the Guacamole client to house arbitrarily-many named input and output streams. ``` 我们需要操作的应该就是input 和 output stream,下面我们进行下猜测 1> this.onbody对应的方法应该就是我们需要实际处理inputStream的地方, 2> this.requestInputStream后面调用了client.requestObjectInputStream(guacObject.index, name);方法,源码如下: ``` Guacamole.Client = function(tunnel) { ...其它内容 this.requestObjectInputStream = function requestObjectInputStream(index, name) { // Do not send requests if not connected if (!isConnected()) return; tunnel.sendMessage("get", index, name); }; ...其它内容 } ``` 可以看出这个方法就是向服务端方发送get请求。我们上面分析websocket请求的时候,提到过向客户端发送过这样一个请求,并且在一直监听的onbody方法中应该能收到服务器返回的响应。 3> this.createOutputStream应该是创建了一个通往guacamole服务器的stream,我们上传文件的时候可能会用到这个stream,调用了client.createObjectOutputStream(guacObject.index, mimetype, name);方法,其源码如下: ``` Guacamole.Client = function(tunnel) { ...其它内容 /** * Creates a new output stream associated with the given object and having * the given mimetype and name. The legality of a mimetype and name is * dictated by the object itself. The instruction necessary to create this * stream will automatically be sent. * * @param {Number} index * The index of the object for which the output stream is being * created. * * @param {String} mimetype * The mimetype of the data which will be sent to the output stream. * * @param {String} name * The defined name of an output stream within the given object. * * @returns {Guacamole.OutputStream} * An output stream which will write blobs to the named output stream * of the given object. */ this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) { // 得到了stream,并向服务端发送了put请求 // Allocate and ssociate stream with object metadata var stream = guac_client.createOutputStream(); tunnel.sendMessage("put", index, stream.index, mimetype, name); return stream; }; ...其它内容 } ``` 继续往下追diamante, 这句var stream = guac_client.createOutputStream(); 源码如下: ``` Guacamole.Client = function(tunnel) { ...其它内容 /** * Allocates an available stream index and creates a new * Guacamole.OutputStream using that index, associating the resulting * stream with this Guacamole.Client. Note that this stream will not yet * exist as far as the other end of the Guacamole connection is concerned. * Streams exist within the Guacamole protocol only when referenced by an * instruction which creates the stream, such as a "clipboard", "file", or * "pipe" instruction. * * @returns {Guacamole.OutputStream} * A new Guacamole.OutputStream with a newly-allocated index and * associated with this Guacamole.Client. */ this.createOutputStream = function createOutputStream() { // Allocate index var index = stream_indices.next(); // Return new stream var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index); return stream; }; ...其它内容 } ``` 再继续,new Guacamole.OutputStream(guac_client, index);源码: ``` /** * Abstract stream which can receive data. * * @constructor * @param {Guacamole.Client} client The client owning this stream. * @param {Number} index The index of this stream. */ Guacamole.OutputStream = function(client, index) { /** * Reference to this stream. * @private */ var guac_stream = this; /** * The index of this stream. * @type {Number} */ this.index = index; /** * Fired whenever an acknowledgement is received from the server, indicating * that a stream operation has completed, or an error has occurred. * * @event * @param {Guacamole.Status} status The status of the operation. */ this.onack = null; /** * Writes the given base64-encoded data to this stream as a blob. * * @param {String} data The base64-encoded data to send. */ this.sendBlob = function(data) { //发送数据到服务端,并且数据格式应该为该base64-encoded data格式,分块传输过去的 client.sendBlob(guac_stream.index, data); }; /** * Closes this stream. */ this.sendEnd = function() { client.endStream(guac_stream.index); }; }; ``` 到此,我们可以知道this.createOutputStream是做的是事情就是建立了一个通往服务器的stream通道,并且,我们可以操作这个通道发送分块数据(stream.sendBlob方法)。 # 2. 上传下载的核心代码 关于文件系统和下载的代码 ``` var fileSystem; //初始化文件系统 client.onfilesystem = function(object){ fileSystem=object; //监听onbody事件,对返回值进行处理,返回内容可能有两种,一种是文件夹,一种是文件。 object.onbody = function(stream, mimetype, filename){ stream.sendAck('OK', Guacamole.Status.Code.SUCCESS); downloadFile(stream, mimetype, filename); } } //连接有滞后,初始化文件系统给个延迟 setTimeout(function(){ //从根目录开始,想服务端发送get请求 let path = '/'; fileSystem.requestInputStream(path); }, 5000); downloadFile = (stream, mimetype, filename) => { //使用blob reader处理数据 var blob_builder; if (window.BlobBuilder) blob_builder = new BlobBuilder(); else if (window.WebKitBlobBuilder) blob_builder = new WebKitBlobBuilder(); else if (window.MozBlobBuilder) blob_builder = new MozBlobBuilder(); else blob_builder = new (function() { var blobs = []; /** @ignore */ this.append = function(data) { blobs.push(new Blob([data], {"type": mimetype})); }; /** @ignore */ this.getBlob = function() { return new Blob(blobs, {"type": mimetype}); }; })(); // 收到blob的处理,因为收到的可能是一块一块的数据,需要把他们整合,这里用到了blob_builder stream.onblob = function(data) { // Convert to ArrayBuffer var binary = window.atob(data); var arrayBuffer = new ArrayBuffer(binary.length); var bufferView = new Uint8Array(arrayBuffer); for (var i=0; i<binary.length; i++) bufferView[i] = binary.charCodeAt(i); blob_builder.append(arrayBuffer); length += arrayBuffer.byteLength; // Send success response stream.sendAck("OK", 0x0000); }; // 结束后的操作 stream.onend = function(){ //获取整合后的数据 var blob_data = blob_builder.getBlob(); //数据传输完成后进行下载等处理 if(mimetype.indexOf('stream-index+json') != -1){ //如果是文件夹,需要解决如何将数据读出来,这里使用filereader读取blob数据,最后得到一个json格式数据 var blob_reader = new FileReader(); blob_reader.addEventListener("loadend", function() { let folder_content = JSON.parse(blob_reader.result) //这里加入自己代码,实现文件目录的ui,重新组织当前文件目录 }); blob_reader.readAsBinaryString(blob_data); } else { //如果是文件,直接下载,但是需要解决个问题,就是如何下载blob数据 //借鉴了https://github.com/eligrey/FileSaver.js这个库 var file_arr = filename.split("/"); var download_file_name = file_arr[file_arr.length - 1]; saveAs(blob_data, download_file_name); } } } ``` 感受下console.log(blob_data)和 console.log(folder_data)的内容如下  关于上传的代码 ``` const input = document.getElementById('file-input'); input.onchange = function() { const file = input.files[0]; //上传开始 uploadFile(fileSystem, file); }; uploadFile = (object, file) => { const _this = this; const fileUpload = {}; //需要读取文件内容,使用filereader const reader = new FileReader(); var current_path = $("#header_title").text(); //上传到堡垒机的目录,可以自己动态获取 var STREAM_BLOB_SIZE = 4096; reader.onloadend = function fileContentsLoaded() { //上面源码分析过,这里先创建一个连接服务端的数据通道 const stream = object.createOutputStream(file.type, current_path + '/' + file.name); const bytes = new Uint8Array(reader.result); let offset = 0; let progress = 0; fileUpload.name = file.name; fileUpload.mimetype = file.type; fileUpload.length = bytes.length; stream.onack = function ackReceived(status) { if (status.isError()) { //提示错误信息 //layer.msg(status.message); return false; } const slice = bytes.subarray(offset, offset + STREAM_BLOB_SIZE); const base64 = bufferToBase64(slice); // Write packet stream.sendBlob(base64); // Advance to next packet offset += STREAM_BLOB_SIZE; if (offset >= bytes.length) { stream.sendEnd(); } } }; reader.readAsArrayBuffer(file); return fileUpload; }; function bufferToBase64(buf) { var binstr = Array.prototype.map.call(buf, function (ch) { return String.fromCharCode(ch); }).join(''); return btoa(binstr); } ```
标签:
上一篇:
guacamole实现RDP的下载
下一篇:
guacamole实现虚拟键盘
文章分类
css
elasticsearch
git
golang
guacamole
javascript
letsencrypt
linux
nginx
other
php
python
vue
web
阅读排行
centos7.3配置guacamole
golang笔记---关联数组(map)
letsencrypt证书-管理工具certbot
golang笔记---template模板语法
详解网络连接
友情链接
node文件
laravel-vue
ArcherWong的博客园