关于 HTML5 的文件上传处理,兼容性问题,以及 BLOB 对象的使用 (转载)
研究过程中关于本主体的相关参考
好文:https://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/
关于 FormData
FormData 是一个 HTML5 的原生对象,使用 FormData 可以将一个 Form 或者一系列的字段包装成一个对象,然后通过 jQuery 或者标准的 XHR 进行 Ajax 发送。
下面是一个简单的例子:
1. 直接封装整个 form:
var formElement = document.querySelector("form");
var request = new XMLHttpRequest();
request.open("POST", "submitform.php");
request.send(new FormData(formElement));
2. 逐个字段产生:
var formData = new FormData();
formData.append("username", "Groucho");
formData.append("accountnum", 123456); // number 123456 is immediately converted to a string "123456"
// HTML file input, chosen by user
formData.append("userfile", fileInputElement.files[0]);
// JavaScript file-like object
var content = '<a id="a"><b id="b">hey!</b></a>'; // the body of the new file...
var blob = new Blob([content], { type: "text/xml"});
formData.append("webmasterfile", blob);
var request = new XMLHttpRequest();
request.open("POST", "http://foo.com/submitform.php");
request.send(formData);
如果使用 jQuery,产生含有数据的 FormData 对象之后,我们可以将其传进 $.ajax 的 data 参数里面。
$.ajax({
url: 'http://example.com/api',
method: 'post',
processData: false
contentType: false,
data: formdata
});
这种情况,只需要将 formdata 对象传入,并且制定 processData 和 contentType 为 false,就可以用 multipart/form-data 的方式通过 ajax post 一个请求出去,当然这里面就可以包含一般的二进制对象(File 或者 Blob)
但是,实际测试中发现腾讯QQ浏览器在将 Blob 传入 Formdata 中的时候就会出问题,肯定是内核对 FormData 的实现上面有 Bug。
于是为了兼容这个问题,我试图自己封装一个模拟出来的表单,即由 boundary 分割的multipart/form-data 请求体。
请求体的封装
再重复一下,如果使用 multipart/form-data 的 Content-Type 去提交一个请求,实际上发出的 HTTP 请求是这样的:
请求头:
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryWwE7y8P3JK82rxsk
请求体:
------WebKitFormBoundaryWwE7y8P3JK82rxsk
Content-Disposition: form-data; name="username"
admin
------WebKitFormBoundaryWwE7y8P3JK82rxsk
Content-Disposition: form-data; name="avatar"; filename="avatar.png"
Content-Type: image/png
[binary stream]
------WebKitFormBoundaryWwE7y8P3JK82rxsk--
关键的格式就是这样,只要满足这个规范,后台就可以从(例如php) $_POST 和 $_FILE获取提交的字段或者上传的文件。
因此,只要我们能够将这样的请求头和请求体按格式生成出来,就可以为所欲为了。
至于我们的调用方式,就是通过 $.ajax 的接口来给出。
其中,请求头很简单,首先随机一个 boundary 字符串,然后通过 ajax 的 contentType 参数输入即可:
var makeBoundary = function() {
return '----JQBoundary'+btoa(Math.random().toString()).substr(0,12);
};
var boundary = makeBoundary();
$.ajax({
contentType: 'multipart/form-data; boundary='+boundary,
// ...
});
如此即可在 ajax 请求中指定请求头。
难点在于请求体 ,请求体是在 $.ajax 方法的 data 部分给出的。
一般初学者来说,传进去的 data 是一个字典,还有我们刚刚上面提到的,给一个 FormData 对象也是可以的。
然后有一个关键点,processData 参数默认是 true,这时候 jQuery 在 ajax 之前会将我们传进去的字典串行化之后,放在 url 中(get 方式)或者放在 payload 请求体里面。
那么如果我们传进去 FormData 或者后面要讲的,传进去一个二进制流,就需要将这个 processData 设置为 false 了。
那么问题来了,我们如果要传进去一个二进制流,应该怎么整?
首先,我们并不知道,data 这个参数除了会吃字典和 FormData 还会吃些什么,我们先假设它会吃普通的 string。
所以我们试一下先不涉及二进制内容,将一个含有 unicode 内容的字符串传进去,看看能行不能行:
$.ajax(url, {
method: 'post',
processData: false,
contentType: 'multipart/form-data; boundary='+boundary,
data: '--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="username"\r\n\r\n' +
'呆滞的慢板\r\n' +
'--' + boundary + '--\r\n'
});
上面这段是行得通的,因为是我从后台读取请求体之后一比一仿造出来的,肯定可以骗过后台。
只是我们要知道,前端还默默为我们做了一件事,就是将中文自动执行了编码,因为从前台看,’呆滞的慢板’在字符串中的长度是 5,但是在后台看,这五个字被编制成了 15 位的 utf 编码二进制串。
ok,一种方法行得通,那么如果涉及二进制内容呢(例如图片)。
获取和处理二进制流
首先,我们需要读取二进制流的内容。
对于二进制流,我们可以这样获取:
var file = document.getElementById('fileElement').files[0];
var reader = new FileReader();
reader.onload = function(e) {
var content = e.target.result;
console.log(content);
}
reader.readAsBinaryString();
这样得出来的 binaryString 格式,是一个字节流,与 atob (相当于base64decode) 一个 base64 串得到的输出是同样的格式。
同样,还有 readAsText,readAsDataUrl, readAsArrayBuffer 等方法,但是获取出来的e.target.result 是不一样的。
可以看到,如果这样,就可以异步获取文件的二进制内容,作为一个字符串,然后我们加上 boundary 拼接到其他字段的整体 formdata 中,然后就可以最终串接成一个完整的 payload 了。
然后我们将这样的 data ajax 出去,发现死翘翘了。
另一种可接受的 data 流格式:ArrayBuffer
失败的原因是,由于文本类型(而且还是 unicode 文本)类型与直接的二进制流放在一起,产生了编码混乱,ajax 发出之间,由于这是一个字符串,因此 xhr 对象帮我们自动编码这个字符串,结果造成了二进制流的破坏,后台识别不出来了。
换个说法,我们遍历这个字符串,碰到一些中文的 unicode 字符,他的取值是超出一个字节的,因此作为流编码,应该按照 utf8 方式,编码成三个字节才对。
那怎么办?只有我们自己来做了。
经过了无尽的折腾撞墙试错,直接写出宝贵的结论:
可以通过 unicode 和二进制混编构造的字符串,在传递给 ajax 之前,将其一个一个字节编码到 Uint8Array 中,再获取其 buffer,作为 data 传给 ajax。
下面一步一步来:
首先,我们要将中间所有涉及的 unicode 字符一个一个拆开:
关于这个问题,我在另一篇文章已经写得很详细了:
https://www.huangwenchao.com.cn/2015/09/javascript-utf8-encoding.html
于是我们可以得到一个确保每一个值都不会超过一个字节的字符串。
/**
* Encode a given string to utf8 encoded binary string.
* @param str:
* @returns string:
*/
var str2utf8 = window.TextEncoder ? function(str) {
var encoder = new TextEncoder('utf8');
var bytes = encoder.encode(str);
var result = '';
for(var i = 0; i < bytes.length; ++i) {
result += String.fromCharCode(bytes[i]);
}
return result;
} : function(str) {
return eval('\''+encodeURI(str).replace(/%/gm, '\\x')+'\'');
};
然后我们将其编码成 Uint8Array,过程省略,最终就是这个函数:
var str2Uint8Array = function(str) {
var arr = [], c;
for(var i = 0; i < str.length; ++i) {
c = str.charCodeAt(i);
if(c > 0xff) {
alert('Char code range out of 8 bit, parse error!');
return [];
}
arr.push(str.charCodeAt(i));
}
return new Uint8Array(arr);
};
那么最终我们可以这样来发送一个 ajax,就可以完全兼容二进制流和普通字段了:
var strctured_body = '...'; // 这是我们手工混编出来的,带有 unicode 字符的完整 request body
var encoded_body = str2utf8(structured_body);
var byte_array = str2Uint8Array(encoded_body);
$.ajax(url, {
method: 'post',
processData: false,
contentType: 'multipart/form-data; boundary='+boundary,
data: byte_array.buffer
});
试了无数种方法,最后只有这样能够将自己编制的内容完整 post 出去,使用 ArrayBuffer 的格式。
后记
最终,我还是耐不住寂寞,做了一个插件,自动做好这些封装,当然,中间还涉及到了接口的设计,如果再做此类工作,参考我的这个插件就可以了。
对象的使用
关于 HTML5 的文件上传处理,兼容性问题,以及 BLOB 对象的使用 (转载)的更多相关文章
- HTML5多文件上传
文章转载自:http://xiechengxiong.com/288.html 一个简单的HTML5多文件上传demo. 以前我们上传文件的时候,如果通过js上传,我们无法在本地直接预览图片,还得跑到 ...
- MVC5:使用Ajax和HTML5实现文件上传功能
引言 在实际编程中,经常遇到实现文件上传并显示上传进度的功能,基于此目的,本文就为大家介绍不使用flash 或任何上传文件的插件来实现带有进度显示的文件上传功能. 基本功能:实现带有进度条的文件上传功 ...
- html5拖动文件上传
使用html5的fileReader api <!DOCTYPE html><html lang="en"><head> <meta ch ...
- html5 ajax 文件上传
http://html5demos.com/dnd-upload 看这个例子看了一会儿...这个是支持拖拽的上传. 下面代码是一个简单的ajax的文件上传: function match(url,rs ...
- 前端开发之旅- 移动端HTML5实现文件上传
一. 在一个客户的webapp项目中需要用到 html5调用手机摄像头,找了很多资料,大都是 js调用api 然后怎样怎样,做了几个demo测试发现根本不行, 后来恍然大悟,用html5自带的 in ...
- 移动端HTML5实现文件上传
PC端上传文件多半用插件,引入flash都没关系,但是移动端要是还用各种冗余的插件估计得被喷死,项目里面需要做图片上传的功能,既然H5已经有相关的接口且兼容性良好,当然优先考虑用H5来实现. 用的技术 ...
- HTML5 JavaScript 文件上传
function fileUpload(targetUrl) { // 隐藏表单名称 var inputName = '_fileselect'; // 文件尺寸 this.fileSize = 0; ...
- 基于HTML5 Ajax文件上传进度条如何实现(jquery版本)
<!DOCTYPE html> <html> <head> <title>html5_2.html</title> <meta htt ...
- 文件上传 accept 兼容性
写法1 在chrome下有反应很慢的问题,不要使用 写法2 在firefox.Safari 中有兼容性问题,弹出选择框不会高亮显示jpg后缀的图片 写法3 在写法2上都添加了image/jpeg,解决 ...
随机推荐
- CSRF攻击原理及测试方法
CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执 ...
- Fragment的陷阱
以前做过的一个项目,Fragment嵌套高德地图,当再次进入Fragment的时候,会出现奇怪的现象.嵌套的地图会出现滑动不动的情况,起先还以为是高德的bug呢,经过一番研究,终确定这是一个坑. 先对 ...
- 自制MVC框架的插件与拦截器基础
上篇谈到我自己写的MVC框架,接下来讲讲插件及拦截器! 在处理一些通用的逻辑最好把它封装一个插件或者拦截器,以便日后可以直接拿过来直接使用.在我的框架中可以通过继承以下抽象类来实现插件或者拦截器. 1 ...
- linux 安装 登录 centos7
常用资源下载 r.aminglinux.com centos7.aminglinux.com http://www.apelearn.com/study_v2/ 认识linux Debian Slac ...
- visual studio 2017无建模项目?
教一跨行同事学C#,我想从基础的讲一下,也就是最基本的面象对象分析与设计(OOAD),我直接打开我最新安装的 visual studio 2017.准备建一个“建模项目”.结果发现死活找不到?打开一个 ...
- Atitit.png 图片不能显示 php环境下
Atitit.png 图片不能显示 php环境下 1.1. 不能显示png 下载png 检查使用bcompare与正常png对比.. 多了bom头 , "\xEF\xBB\xBF" ...
- 深入浅出FPGA-2-让source insight 支持verilog HDL
引言 工欲善其事,必先利其器.最近用verilog在FPGA上做一个简易的计算器,整个工程由6个模块组成,之前写的代码都是很小的,模块也很少,一般就一两个,没感觉出别扭.但是模块多了就发现,模块之间的 ...
- uva 11400 - Lighting System Design(动态规划 最长上升子序列问题变型)
本题难处好像是在于 能够把一些灯泡换成电压更高的灯泡以节省电源的钱 .所以也才有了对最优方案的探求 好的处理方法是依照电压从小到大排序.仅仅能让前面的换成后面的.也就满足了把一些灯泡换成电压更高的灯泡 ...
- 聊一聊Android的消息机制
聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,消息机制还是比 ...
- python学习之urllib.parse.unquote()
urllib.parse.unquote(string,encoding ='utf-8',errors ='replace') 用同一个字符换成%xx转义.相当于JS中的urldecode(),对u ...