研究过程中关于本主体的相关参考

好文:https://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/

好文:http://www.zhangxinxu.com/wordpress/2013/10/understand-domstring-document-formdata-blob-file-arraybuffer/

关于 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 的格式。


后记

最终,我还是耐不住寂寞,做了一个插件,自动做好这些封装,当然,中间还涉及到了接口的设计,如果再做此类工作,参考我的这个插件就可以了。

对象的使用

input file reader的更多相关文章

  1. HTML中上传与读取图片或文件(input file)----在路上(25)

    input file相关知识简例 在此介绍的input file相关知识为: 上传照片及文件,其中包括单次上传.批量上传.删除照片.增加照片.读取图片.对上传的图片或文件的判断,比如限制图片的张数.限 ...

  2. input file 模拟预览图片。

    首先申明,接下来内容只是单纯的预览图片,最多选择九张,并没有和后台交互,交互的话需要自己另外写js. 本来想写一个调用摄像头的demo,意外的发现input file 在手机端打开的话,ios可以调用 ...

  3. JS 更改表单的提交时间和Input file的样式

    JS转换时间 function renderTime(data) { var da = eval('new ' + data.replace('/', '', 'g').replace('/', '' ...

  4. JS input file 转base64 JS图片预览

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. HTML5的 input:file上传 以及 类型控制

    以HTML5的文件上传API 如下demo代码在.html文件打开即可: !DOCTYPE html> <html lang="zh_cn"> <head& ...

  6. input file获取选择图片的本地路径和base64路径

    input file获取选择图片的本地路径和base64路径 本地路径: myHeadFile: function (e) { // 这里是input file 的onchange事件 ] const ...

  7. JQuery input file 上传图片

    表单元素file设置隐藏,通过其他元素打开: .imgfile为input file $(".ul").click(function () {return $(".img ...

  8. 文件上传:input file FileReader

    js: window.onload = function () { var input = document.getElementById('input-file'), info = document ...

  9. Extending JMeter – Creating Custom Config Element – Property File Reader

    JMeter is one of the best open source tools in the Test Automation Community. It comes with all the ...

随机推荐

  1. vue - webpack.dev.conf.js for FriendlyErrorsPlugin

    描述:webpack网页端友好的报错信息就来自它 官网:https://www.npmjs.com/package/friendly-errors-webpack-plugin new Friendl ...

  2. 字符串在内存中的存储——C语言进阶

    字符串是以ASCII字符NUL结尾的字符序列. ASCII字符NUL表示为\0.字符串通常存储在数组或者从堆上分配的内存中.只是,并不是全部的字符数组都是字符串,字符数组可能没有NUL字符. 字符数组 ...

  3. Linux配置虚拟主机后,只能访问到主页怎么办?

    Linux配置虚拟主机后,只能访问到主页怎么办? 今天配置了lamp后,添加了一个虚拟主机,配置http.conf后,增加虚拟主机,测试访问发现只有域名下能访问,ljt.com但是域名下所有的都访问不 ...

  4. vue打包后出现一些map文件的解决方法

    Vue打包后出现一些map文件的解决办法: 问题: 可能很多人在做vue项目打包,打包之后js中,会自动生成一些map文件,那我们怎么把它去掉不要呢? 1.运行  cnpm run build  开始 ...

  5. margin外边距问题

    1 .上下边距会叠加 !DOCTYPE html> <html> <head> <m<etacharset="UTF-8"> < ...

  6. Java 线程池的原理与实现 (转)

        最近在学习线程池.内存控制等关于提高程序运行性能方面的编程技术,在网上看到有一哥们写得不错,故和大家一起分享. [分享]Java 线程池的原理与实现 这几天主要是狂看源程序,在弥补了一些以前知 ...

  7. Drupal中的模块载入

    什么是模块载入?首先说载入,这里的载入是指require_once.模块载入就是指require_once模块目录中的某个PHP文件. 每个Drupal模块都应该有自己的主文件.模块主文件以模块名开始 ...

  8. Python-常用字符串转换实例

    当字符串是:'\u4e2d\u56fd' >>>s=['\u4e2d\u56fd','\u6e05\u534e\u5927\u5b66']>>>str=s[0].d ...

  9. jQuery Autocomplete 用户快速找到并从预设值列表中选择

    jQuery Autocomplete 插件根据用户输入值进行搜索和过滤,让用户快速找到并从预设值列表中选择.通过给 Autocomplete 字段焦点或者在其中输入字符,插件开始搜索匹配的条目并显示 ...

  10. 在Visual Studio 2013顯示SCSS詳細錯誤訊息

    在WebEssentials套件加持之下,Visual Studio 2013可以直接編修SCSS,每次存檔自動編譯出css.min.css及.map,非常方便.但初心者如我,寫錯語法在所難免,一旦造 ...