分析的入手点,查看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实现上传下载的更多相关文章

  1. Struts的文件上传下载

    Struts的文件上传下载 1.文件上传 Struts2的文件上传也是使用fileUpload的组件,这个组默认是集合在框架里面的.且是使用拦截器:<interceptor name=" ...

  2. 基于Spring Mvc实现的Excel文件上传下载

    最近工作遇到一个需求,需要下载excel模板,编辑后上传解析存储到数据库.因此为了更好的理解公司框架,我就自己先用spring mvc实现了一个样例. 基础框架 之前曾经介绍过一个最简单的spring ...

  3. Android okHttp网络请求之文件上传下载

    前言: 前面介绍了基于okHttp的get.post基本使用(http://www.cnblogs.com/whoislcj/p/5526431.html),今天来实现一下基于okHttp的文件上传. ...

  4. 用Canvas+Javascript FileAPI 实现一个跨平台的图片剪切、滤镜处理、上传下载工具

    直接上代码,其中上传功能需要自己配置允许跨域的文件服务器地址~ 或者将html文件贴到您的站点下同源上传也OK. 支持: 不同尺寸图片获取. 原图缩小放大. 原图移动. 选择框大小改变. 下载选中的区 ...

  5. Javaweb学习笔记——上传下载文件

    一.前言 在Javaweb中,上传下载是经常用到的功能,对于文件上传,浏览器在上传的过程中是以流的过程将文件传给服务器,一般都是使用commons-fileupload这个包实现上传功能,因为comm ...

  6. 服务器文件上传下载(XShell+Xftp)

    1.下载XShell安装包+Xftp安装包.百度网盘(XShell):https://pan.baidu.com/s/1eR4PFpS 百度网盘(Xftp):https://pan.baidu.com ...

  7. springmvc 上传下载

    springmvc文件上传下载在网上搜索的代码 参考整理了一份需要使用的jar.commons-fileupload.jar与commons-io-1.4.jar 二个文件 1.表单属性为: enct ...

  8. 简单Excel表格上传下载,POI

    一.废话 Excel表格是office软件中的一员,几乎是使用次数最多的办公软件.所以在java进行企业级应用开发的时候经常会用到对应的上传下载便利办公. 目前,比较常用的实现Java导入.导出Exc ...

  9. JAVA中使用FTPClient上传下载

    Java中使用FTPClient上传下载 在JAVA程序中,经常需要和FTP打交道,比如向FTP服务器上传文件.下载文件,本文简单介绍如何利用jakarta commons中的FTPClient(在c ...

随机推荐

  1. 设计模式——抽象工厂模式(AbstractFactoryPattern)

    抽象工厂模式(AbstractFactory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类. UML图: IFactory: package com.cnblog.clarck ...

  2. BAT的云

    近期,关于用国内那家云非常纠结! 我也来说道说道各家云. 首先,说说我想要的云服务(按优先级): 0.最好能提供二级域名.移动互联网时代,顶级域名必需要吗?在手机浏览器上输入长长的域名非常蛋痛(即不要 ...

  3. 「bzoj3956: Count」

    题目 刚开始并没有看懂题意于是痛苦的挣扎了好久 题意是这样的 问\([l,r]\)有多少对\((i,j)\)满足\(a_i\)和\(a_j\)恰好是\(a_i...a_j\)中严格最大的两个数 强制在 ...

  4. 【[AH2017/HNOI2017]礼物】

    题目 又是我不会做的题了 看看柿子吧 \[\sum(a_i+c-b_i)^2\] 最小化这个柿子 之所以不写下标是因为我们这个\(\{a\},\{b\}\)可以循环同构 那就开始化吧 \[\sum(a ...

  5. PSROIAlign的代码实现

    https://github.com/afantideng/R-FCN-PSROIAlign

  6. MVC学习五:Razor布局页面 _ViewStart.cshtml

    如图: _ViewStart.cshtml就是MVC中的布局页面/模板页面. 用户访问流程图: 原理:先去执行Views文件夹下[_ViewStart.cshtml]页面,然后同级目录文件夹(上图中的 ...

  7. 【luogu P3865 ST表】 模板

    跟忠诚是一样滴,不过是把min改成max就AC了.模板题. #include <cstdio> #include <algorithm> using namespace std ...

  8. React 相关开发工具

    Gulp:是一个NodeJs项目构建工具,高效易学:把一个开发中的项目构建成一个可以部署在服务器上的项目,压缩 整合 gulp.task('1',['2','3'],function(){});// ...

  9. GNS3的安装和配置

    一.为什么安装GNS3 简单说来它是dynamips的一个图形前端,相比直接使用dynamips这样的虚拟软件要更容易上手和更具有可操作性.更重要的一点是很多Cisco实验在cisco packet ...

  10. Excel 批量重命名照片

    理历史照片的时候发现,用文件夹进行分类之后,还有很多照片,如果继续分类,就会导致每个文件夹照片过少,查看不便,但是如果不分类,手机原始的命名方式没有办法满足查看需求,故而,产生了对照片进行批量重命名的 ...