guacamole实现上传下载
分析的入手点,查看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实现上传下载的更多相关文章
- Struts的文件上传下载
Struts的文件上传下载 1.文件上传 Struts2的文件上传也是使用fileUpload的组件,这个组默认是集合在框架里面的.且是使用拦截器:<interceptor name=" ...
- 基于Spring Mvc实现的Excel文件上传下载
最近工作遇到一个需求,需要下载excel模板,编辑后上传解析存储到数据库.因此为了更好的理解公司框架,我就自己先用spring mvc实现了一个样例. 基础框架 之前曾经介绍过一个最简单的spring ...
- Android okHttp网络请求之文件上传下载
前言: 前面介绍了基于okHttp的get.post基本使用(http://www.cnblogs.com/whoislcj/p/5526431.html),今天来实现一下基于okHttp的文件上传. ...
- 用Canvas+Javascript FileAPI 实现一个跨平台的图片剪切、滤镜处理、上传下载工具
直接上代码,其中上传功能需要自己配置允许跨域的文件服务器地址~ 或者将html文件贴到您的站点下同源上传也OK. 支持: 不同尺寸图片获取. 原图缩小放大. 原图移动. 选择框大小改变. 下载选中的区 ...
- Javaweb学习笔记——上传下载文件
一.前言 在Javaweb中,上传下载是经常用到的功能,对于文件上传,浏览器在上传的过程中是以流的过程将文件传给服务器,一般都是使用commons-fileupload这个包实现上传功能,因为comm ...
- 服务器文件上传下载(XShell+Xftp)
1.下载XShell安装包+Xftp安装包.百度网盘(XShell):https://pan.baidu.com/s/1eR4PFpS 百度网盘(Xftp):https://pan.baidu.com ...
- springmvc 上传下载
springmvc文件上传下载在网上搜索的代码 参考整理了一份需要使用的jar.commons-fileupload.jar与commons-io-1.4.jar 二个文件 1.表单属性为: enct ...
- 简单Excel表格上传下载,POI
一.废话 Excel表格是office软件中的一员,几乎是使用次数最多的办公软件.所以在java进行企业级应用开发的时候经常会用到对应的上传下载便利办公. 目前,比较常用的实现Java导入.导出Exc ...
- JAVA中使用FTPClient上传下载
Java中使用FTPClient上传下载 在JAVA程序中,经常需要和FTP打交道,比如向FTP服务器上传文件.下载文件,本文简单介绍如何利用jakarta commons中的FTPClient(在c ...
随机推荐
- IOS AFN请求 总结
一.2大管理对象 1.AFHTTPRequestOperationManager* 对NSURLConnection的封装 2.AFHTTPSessionManager* 对NSURLSession的 ...
- bzoj 3028 生成函数
计算完后为 f(x): 根据我翻高数书,终于推倒出来了. (- ̄▽ ̄)-
- 融云SDK:获取用户Token的方法
融云SDK查看ServerAPI里面有个获取Token的方法,本以为只要传三个参数就可以.后来发现,在请求头有几个必须要传的参数,否则服务器返回401(未授权).拿获取Token接口为例子 如图所示, ...
- asp.net 过滤器
asp.net 制作过滤器原理:重写ASP.net管道事件 1.通过HttpApplicationFactory创建一个HttpApplication对象,负责处理整个请求. 2.调用ProcessR ...
- 【luogu T24743 [愚人节题目5]永世隔绝的理想乡】 题解
题意翻译 我们来说说王的故事吧. 星之内海,瞭望之台.从乐园的角落告知汝等.汝等的故事充满了祝福.只有无罪之人可以进入——『永世隔绝的理想乡(Garden of Avalon)』! 题目背景 zcy入 ...
- Extjs treePanel 的treestore重复加载问题解决
在Extjs 4.2.2 中构建一个treePanel 发现设置rootVisible后 ,treeStore中设置的autoLoad:false不启作用,在组件初始化的时候即加载数据源,造成数据重复 ...
- fastRPC的数据库服务
根据整理的RPC模型,在此上,根据最近的项目,发布了DB服务,操作数据库.以RPC模型,发布数据库的操作服务,主要发送SQL语句,在服务端执行:同时引入了流行的数据库连接池:服务端还发布了文件接收服务 ...
- Less 常用基础知识
LESS 中的注释 也可以额使用css 中的注释(/**/) 这种方式是可以被编译出来的. 也可以使用// 注释 不会被编译的 变量 声明变量的话一定要用@开头 例如:@变量名称:值: @test_w ...
- Easyui多个下拉框联动效果
好久没写前端了,以前在做多级联动的时候,用的是easyui的tree结构,但是需要一次性全部加载,不是按需加载,性能不好,退而求其之,用多个下拉框做 eayui的combobox 有onSelect ...
- layDate 闪现 循环一个以上会闪现
一个render一次渲染一个日期组件,这个是内置的,所以需要循环绑定, 又不能确定页面有多少个,还好layDate 提供了内置方法, //同时绑定多个 lay('.test-item').each(f ...