简介
HTML5 世界中的无名英雄之一是 XMLHttpRequest
。
严格来说,XHR2 不是 HTML5。不过,这也是我们在 Android Vitals 中
浏览器供应商对核心平台做了哪些贡献。我把 XHR2 放入了我们的新袋子里
因为它在当今复杂的 Web 应用中发挥着不可或缺的作用。
看来我们的老朋友变了大变身,但很多人 不知道它的新功能。XMLHttpRequest 级别 2 引入了大量新功能,以消除我们 Web 应用中的复杂黑客行为; 例如跨源请求、上传进度事件 并支持上传/下载二进制数据。这允许 AJAX 可与许多先进的 HTML5 API(例如 File System API、 Web Audio API、 和 WebGL。
本教程重点介绍了 XMLHttpRequest
中的一些新功能,
尤其是那些可用于处理文件的工具。
正在提取数据
通过 XHR 提取二进制 blob 形式的文件一直很痛苦。从技术上讲 这甚至不可能一个有据可查的技巧包括 使用用户定义的字符集替换 MIME 类型,如下所示。
提取图片的旧方法:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
// Hack to pass bytes through unprocessed.
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function(e) {
if (this.readyState == 4 && this.status == 200) {
var binStr = this.responseText;
for (var i = 0, len = binStr.length; i < len; ++i) {
var c = binStr.charCodeAt(i);
//String.fromCharCode(c & 0xff);
var byte = c & 0xff; // byte at offset i
}
}
};
xhr.send();
虽然这种方法可行,但是您实际上会在 responseText
中获得什么
不是二进制 blob。它是一个表示图片文件的二进制字符串。
我们要诱骗服务器将未处理的数据传回。
虽然这个技巧有用,但我还是称呼它为黑魔法,
反对它。只要您依靠字符代码黑客手段和字符串操作,
将数据强制转换为所需的格式,就成了问题。
指定响应格式
在前面的示例中,我们将图片下载为二进制“文件”
方法是覆盖服务器的 MIME 类型并将响应文本作为二进制字符串处理。
我们改为利用 XMLHttpRequest
的新
要通知的 responseType
和 response
属性
浏览器。
- xhr.responseType
- 在发送请求前,请设置
xhr.responseType
转换为“text”、“arraybuffer”、“blob”或“document”,具体取决于您的数据需求。 请注意,系统会默认设置xhr.responseType = ''
(或省略) 对“text”的响应 - xhr.response
- 请求成功后,xhr 的响应属性将
以
DOMString
、ArrayBuffer
、Blob
或Document
(具体取决于responseType
。)
有了这个新的强大功能,我们可以修改上一个示例,但这次,
以 Blob
而非字符串的形式提取图片:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
// Note: .response instead of .responseText
var blob = new Blob([this.response], {type: 'image/png'});
...
}
};
xhr.send();
好多了!
ArrayBuffer 响应
ArrayBuffer
是用于二进制数据的通用固定长度容器。如果您熟悉这些概念,
需要原始数据的通用缓冲区,但这些人员背后的真正强大之处在于
您可以创建“数据视图”使用 JavaScript 类型数组进行底层数据的映射。
实际上,可以通过单个 ArrayBuffer
来源创建多个视图。
例如,您可以创建一个具有相同 ArrayBuffer
的 8 位整数数组
现有的 32 位整数数组。底层数据
只是为它创建不同的表示形式。
例如,以下代码会提取与 ArrayBuffer
相同的图片,
但这次, 将从该数据缓冲区创建一个无符号 8 位整数数组:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer
// var byte3 = uInt8Array[4]; // byte at offset 4
...
};
xhr.send();
Blob 响应
如果您想直接与 Blob
和/或
无需操作文件的任何字节,请使用 xhr.responseType='blob'
:
window.URL = window.URL || window.webkitURL; // Take care of vendor prefixes.
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
var img = document.createElement('img');
img.onload = function(e) {
window.URL.revokeObjectURL(img.src); // Clean up after yourself.
};
img.src = window.URL.createObjectURL(blob);
document.body.appendChild(img);
...
}
};
xhr.send();
Blob
可在许多地方使用,包括
写入 indexedDB,将其写入 HTML5 文件系统,
或创建 Blob 网址,如下所示:
示例。
发送数据
能够以不同格式下载数据固然很好,但
在无法将这些富媒体格式发送回主基地(服务器)时,我们能为我们提供任何帮助。
XMLHttpRequest
限制我们发送 DOMString
或 Document
(XML) 数据一段时间。现在不需要了。改版后的 send()
方法已被重写,以接受以下任意类型:
DOMString
、Document
、FormData
、Blob
、
File
,ArrayBuffer
。本专精课程的剩余部分
部分演示了如何使用每种类型发送数据。
发送字符串数据:xhr.send(DOMString)
function sendText(txt) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) {
if (this.status == 200) {
console.log(this.responseText);
}
};
xhr.send(txt);
}
sendText('test string');
function sendTextNew(txt) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.responseType = 'text';
xhr.onload = function(e) {
if (this.status == 200) {
console.log(this.response);
}
};
xhr.send(txt);
}
sendTextNew('test string');
没有什么新内容,只是正确的代码段略有不同。
它会设置 responseType='text'
以进行比较。同样,省略这一行
会产生相同的结果。
提交表单:xhr.send(FormData)
很多人可能都习惯于使用 jQuery 插件
或其他库来处理 AJAX 表单提交。我们可以使用 FormData
,
另一种针对 XHR2 设计的新数据类型。FormData
有助于使用 JavaScript 即时创建 HTML <form>
。
然后可以使用 AJAX 提交该表单:
function sendForm() {
var formData = new FormData();
formData.append('username', 'johndoe');
formData.append('id', 123456);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
xhr.send(formData);
}
从本质上讲,我们只是动态创建了 <form>
,并添加了
<input>
值。
当然,您无需从头开始创建 <form>
。
FormData
对象可以通过现有的 HTMLFormElement
进行初始化
。例如:
<form id="myform" name="myform" action="/server">
<input type="text" name="username" value="johndoe">
<input type="number" name="id" value="123456">
<input type="submit" onclick="return sendForm(this.form);">
</form>
function sendForm(form) {
var formData = new FormData(form);
formData.append('secret_token', '1234567890'); // Append extra data before send.
var xhr = new XMLHttpRequest();
xhr.open('POST', form.action, true);
xhr.onload = function(e) { ... };
xhr.send(formData);
return false; // Prevent page from submitting.
}
HTML 表单可包含上传文件(例如 <input type="file">
)
FormData
也可以处理它。只需附加文件,浏览器就会
在调用 send()
时构造 multipart/form-data
请求:
function uploadFiles(url, files) {
var formData = new FormData();
for (var i = 0, file; file = files[i]; ++i) {
formData.append(file.name, file);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = function(e) { ... };
xhr.send(formData); // multipart/form-data
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
uploadFiles('/server', this.files);
}, false);
上传文件或 blob:xhr.send(Blob)
我们还可以使用 XHR 发送 File
或 Blob
数据。
请注意,所有 File
都是 Blob
,因此两者皆可在此处使用。
此示例使用 Blob()
构造函数从头开始创建一个新的文本文件。
并将该 Blob
上传到服务器。该代码还会设置一个处理程序
来通知用户上传进度:
<progress min="0" max="100" value="0">0% complete</progress>
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.send(blobOrFile);
}
upload(new Blob(['hello world'], {type: 'text/plain'}));
上传字节:xhr.send(ArrayBuffer)
最后但同样重要的一点是,我们可以将 ArrayBuffer
作为 XHR 的载荷发送。
function sendArrayBuffer() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
var uInt8Array = new Uint8Array([1, 2, 3]);
xhr.send(uInt8Array.buffer);
}
跨域资源共享 (CORS)
CORS 允许 Web 应用 以便在一个域上向另一个域发出跨域 AJAX 请求。 启用此功能非常简单,只需服务器发送一个响应标头即可。
启用 CORS 请求
假设您的应用位于 example.com
上,而您
想从 www.example2.com
提取数据。通常来说,如果您
进行此类 AJAX 调用时,请求会失败,浏览器
就会抛出来源不匹配错误使用 CORS,www.example2.com
只需添加一个标头,即可选择允许来自 example.com
的请求:
Access-Control-Allow-Origin: http://example.com
可向单个资源添加 Access-Control-Allow-Origin
特定网站下或整个网域中。要允许任何网域
请求,请设置:
Access-Control-Allow-Origin: *
实际上,此网站 (html5rocks.com) 已在其所有网页上启用了 CORS。点燃
然后您会看到 Access-Control-Allow-Origin
:
启用跨源请求非常简单,因此如果您的数据是公开的,请启用 CORS!
发出跨网域请求
如果服务器端点已启用 CORS,则发出跨源请求
与普通的 XMLHttpRequest
请求没有什么不同。例如:
example.com
现在可以向
www.example2.com
:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example2.com/hello.json');
xhr.onload = function(e) {
var data = JSON.parse(this.response);
...
}
xhr.send();
实际示例
下载并将文件保存到 HTML5 文件系统
假设您有一个图库,并希望从图片库中
然后使用 HTML5 文件系统将其保存在本地。
实现此目的的方法之一是以 Blob
形式请求图片
并使用 FileWriter
将其写出:
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
function onError(e) {
console.log('Error', e);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
fs.root.getFile('image.png', {create: true}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwrite = function(e) { ... };
writer.onerror = function(e) { ... };
var blob = new Blob([xhr.response], {type: 'image/png'});
writer.write(blob);
}, onError);
}, onError);
}, onError);
};
xhr.send();
分割文件并上传各个部分
使用 File API,我们可以将 上传大文件的工作。具体方法是将上传内容分成多个数据块 为每个部分生成一个 XHR,并将该文件放在服务器上。这是 类似于 Gmail 快速上传大型附件的方式。这种技术也可以 用于规避 Google App Engine 的 32MB http 请求限制。
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
xhr.send(blobOrFile);
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
var blob = this.files[0];
const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
const SIZE = blob.size;
var start = 0;
var end = BYTES_PER_CHUNK;
while(start < SIZE) {
upload(blob.slice(start, end));
start = end;
end = start + BYTES_PER_CHUNK;
}
}, false);
})();
用于在服务器上重建文件的代码未显示。
参考
- XMLHttpRequest 第 2 级规范
- 跨域资源共享 (CORS) 规范
- File API 规范
- FileSystem API 规范