abbshr.github.io
abbshr.github.io copied to clipboard
大话文件上传的前前后后
web标准发展至今,浏览器能干的事越来越多了,各种新鲜技术的出现无疑扩展了客户端app的职能,比如说上一篇po提到的ArrayBuffer
、Blob
、URL
等。这个寒假我写了一個文件拖拽上传的脚本。当时我想毕竟这是一个正经的web应用,就算是前端也一定要做到尽量无可挑剔。因此,新鲜技术终于得以利用。
我们暂且不考虑应用架构和代码风格,怎么做才能无可挑剔呢?答案就是把严格控制工作流程的每一环节:从选择文件到解析文件再到文件上传最后到善后工作。(当然,如果人家禁用JavaScript所有的工作都前功尽弃了。不过为了弥补这一不足,我们在后面会有一个小小的解决方案。)
获取文件
文件的获取有两个途径:A.上传按钮选择; B.拖拽选择。这没啥可说的,只是为了方便拖放操作额外写了一个拖放事件库,代码就不贴了。
类型检测
关键是文件解析。或许你要问“上传个文件给服务器,为毛在前端弄那么复杂?”,还是那句话“为了做到应用的完整与无可挑剔”。上传时我们考虑之允许指定格式的文件,如“PDF、DOC、JPEG、CAD”等等能形成人类可读文档的文件,如果文件格式不满足要求则拒绝上传。
要想检测文件类型,至少得把文件头读出来。怎么做?要是以前还真就麻烦了,可是现在web标准给出了FileReader
,使用它可以将二进制的blob文件进行转化处理。还记得ArrayBuffer
吗?这里我们就用他们来完成文件头的获取:
var typeVerify = function (file) {
var filereader;
var token = true;
// 创建一个FileReader
filereader = new FileReader();
filereader.onload = verify;
// 以异步方式将file转成ArrayBuffer(同步方式只有在Worker线程中才可用)
filereader.readAsArrayBuffer(file.slice(0, 4));
function verify(e) {
var buffer = e.target.result;
// 创建一个DataView对象用来按字节处理结果
var dataView = new DataView(buffer);
// false以大端次序读取2字节(true为小端次序)
var fh = dataView.getUint16(0, false);
// 如果得到的值不在fileHeader里
if (fileHeader.indexOf(fh) == -1) token = false;
return token;
}
};
OK,我们已经把过程封装到函数,用来检测来自用户输入的文件是否满足要求。
异步上传
WebSocket、Ajax可供选择,鉴于长连接开销问题,选择Ajax吧。新的XMLHttpRequest 2标准中规定了用于文件上传的接口,可用于二进制文件上传以及监测文件上传进度等。
var asyncUpload = function (binfile) {
var xhr = new XMLHttpRequest();
// 注册该事件可用于获取上传进度
xhr.upload.onprogress = updateProcess;
// 上传成功
xhr.upload.onload = success;
// 上传结束(无论成败)
xhr.upload.onloadend = end;
xhr.upload.onerror = error;
// 服务器返回结果
xhr.onreadystatechange = watchState;
xhr.open('POST', '/upload');
// 设置响应类型
xhr.responseType = 'blob';
xhr.send(binfile);
};
最后一步
大体的架构设计算结束了,最后一步是什么呢?
对了,我们折腾了半天,要是用户不买账,把JavaScript给禁用了或给你改了咋办???……
这个做法着实让人气愤,为了防止自己的成果功亏一亏,我考虑了这种办法:
- 纯粹JS型页面。即整个页面都是由JS生成,让你禁用~
- 服务器检测请求的
Header
,通常web浏览器最差也得通过表单实现上传,表单上传的缺点之一是没法控制请求头。那么我们就用ajax添加个特殊的请求头。 - 把写好的JS文件进行压缩(这个普遍都有,最好弄得面目全非让人看了就想吐。。)
不过呢,尽管是这样做,还是有很多漏洞的,仅仅能打消一下hacking的念头,如果用户真的想整整你,办法还是多的去的。