分析的入手點,查看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);
}
 
       