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

好文: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. Flutter网络请求与JSON解析

    本文介绍如何在Flutter中创建HTTP网络请求和对请求的json string进行类型解析. 网络请求 官方使用的是用dart io中的HttpClient发起的请求,但HttpClient本身功 ...

  2. 如何让你的webapp也能跳窗口搜索

    目前很多手机app或者一些webapp,搜索栏基本采用跳窗口的搜索方式 怎么做 实现方式: 1.在触发外层的input的时候打开个modal层,默认打开该modal层的时候就触发了moda里面的inp ...

  3. 【Firefly API文档】—— Package Distributed

    http://bbs.gameres.com/forum.php?mod=viewthread&tid=219654 package distributed 这个包中主要封装了各个服务进程间进 ...

  4. 如何在Jconsole 监控 Jboss Tomcat

    Java在jdk5开始就自带有Jconsole了,要想用Jconsol监控且需要添加启动参数: Linux系统:JAVA_OPTS="$JAVA_OPTS     -Dcom.sun.man ...

  5. React-Native系列Android——Touch事件原理及状态效果

    Native原生相比于Hybrid或H5最大长处是具有流畅和复杂的交互效果,触摸事件便是当中重要一项,包括点击(Click).长按(LongClick).手势(gesture)等. 以最简单常见的点击 ...

  6. sqlserver远程备份到其他服务器

    直接将数据库备份到其他机器上 --如果xp_cmdshell没有启用,请先启用 sp_configure reconfigure go sp_configure reconfigure go --1. ...

  7. 不吐不快之EJB演练——开篇概述

    EJB(Enterprise Java Bean)是J2EE规范的重要核心,它是一个用户分布式业务应用的标准服务端组件模型,它是一种能够高速开发大规模企业应用的组件体系结构.上面这样官方的解释可能对于 ...

  8. iOS 滑块拼图游戏(Puzzle8)

    代码地址如下:http://www.demodashi.com/demo/11505.html 一.准备工作 先了解一个定义和定理 定义:在一个1,2,...,n的排列中,如果一对数的前后位置与大小顺 ...

  9. JSP简单练习-EL获取表单数据

    输入username和password的界面: <%@ page language="java" contentType="text/html;charset=gb ...

  10. zabbix报错listener failed: zbx_tcp_listen() fatal error: unable to serve on any address

    I'm trying to install zabbix on a new (virtual) server. But i have troubles with the zabbix_agentd o ...