介绍
XMLHttpRequest
可以让开发者发送 HTTP 与 HTTPS 请求并在不重載当前页面的情况下更动页面。一般常用的两个使用情景是:提交表单与取得更多内容。
在之前的 XMLHttpRequest
中,请求的内容只能是文本、HTML 或 XML。要发送索引键值配对需要使用读取与写入皆不干净的语法 − URL 编码字符串。
之前的 XHR 必须遵守让跨域请求相当困难的相同来源政策。举例来说,要是没有像是 Flash 补助 XHR 或是代理服务器之类的中介,开发者不能在 http://foo.example/ 与 http://bar.example/ 之间分享数据。要在子域间(例如 http://foo.example 与 http://www.foo.example)分享数据,开发者必须在两个来源的脚本中设置 document.domain
,但是这样做又会有安全风险。
以前的 XHR 可以上传档案吗?不行。开发者必须使用依赖插件的 SWFUpload,不然就是需要一个隐藏 iframe
,但是后者又欠缺用户端的进展事件。
本文将会踏入 XMLHttpRequest 的一些最新进展并详述 Opera 12 的支持情形。
XMLHttpRequest 有什么新东西
在 XMLHttpRequest
规范与浏览器支持有所进步的现在,开发者可以:
- 设置请求超时
- 透过
FormData
对象管理数据 - 传输二位元数据
- 监听数据传输的进展
- 进行更安全的跨域请求
- 覆盖媒体类型与响应的媒体形态
设置并处理超时
有时候有可能是因为网路频宽不够或是服务器响应时间太长导致请求的完成速度很慢,这会造成应用反应迟钝,对用户体验有不良的影响。
XHR 现在已经有处理这个问题的方法了:请求超时。开发者可以用 timeout
属性指定应用的等待时间,如果超过了这个时间,开发者可以让应用进行别的工作。下面的例子设置了三秒(3000 微秒)的超时:
function makeRequest() {
var url = 'data.json';
var onLoadHandler = function(event){
// 解析 JSON 并建一个数组。
}
var onTimeOutHandler = function(event){
var content = document.getElementById('content'),
p = document.createElement('p'),
msg = document.createTextNode('再等一下!');
p.appendChild(msg);
content.appendChild(p);
// 重新启动请求。
event.target.open('GET', url);
// 也可以用一个更长的超时,盖掉原来的。
event.target.timeout = 6000;
event.target.send();
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.timeout = 3000;
xhr.onload = onLoadHandler;
xhr.ontimeout = onTimeOutHandler;
xhr.send();
}
window.addEventListener('DOMContentLoaded', makeRequest, false);
当经过了三秒但是响应数据还没到达时,上面例子会告知用户请求要花多一点时间,并启动一个设有更长超时时间的请求(观看 XHR 超时演示)。在 timeout
事件监听中重设超时时间不是必要的,但是在这个例子中,由于新的 URL 的响应总是会超过原来的超时时间,这里才为了避免循环重设超时时间。
到目前为止,Chrome 与 Safari 不支持 XHR 超时。Opera、Firefox 与 IE 10 则支持。IE 8 与 9 也支持 XDomainRequest
对象上的超时。
从别的网域请求数据
之前 XHR 的一个限制是相同来源政策:发出请求的文档和被请求的文档必须有相同的协议、域名、端口。一个从 http://www.foo.example 至 http://www.foo.example:5050 的跨端口请求会导致浏览器抛出安全例外(除了允许跨端口请求的旧版本 IE 以外)。
而现在 XMLHttpRequest
已经在跨来源资源共享(CORS)启动的时候支持跨来源请求了。
尽管 IE10 支持跨域 XMLHttpRequest
,但是 IE 8 与 9 不支持。然而,在 IE 8 与 9 下可以用作用差不多的 XDomainRequest
对象。
除了使用绝对 URL 而不是相对 URL 以外,跨来源请求长得跟相同来源请求差不多:
var xhr = new XMLHttpRequest();
var onLoadHandler = function(event) {
/* 用响应作点事 */
}
xhr.open('GET','http://other.server/and/path/to/script');
xhr.onload = onLoadHandler;
xhr.send();
重要的不同点在于目标 URL 必须透过发送 Access-Control-Allow-Origin
响应标头来表示允许存取。
跨来源资源共享概论
请参阅《DOM access control using cross-origin resource sharing》以更深入了解 CORS,这里仅介绍两个标头:Origin
请求标头,与 Access-Control-Allow-Origin
响应标头。
Origin
请求标头
Opera 与其他浏览器会在进行跨来源 XHR 请求的时候自动加入 Origin
标头,如同下面的例子:
GET /data.xml HTTP/1.1
User-Agent: Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.10.289 Version/12.00
Host: datahost.example
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en,en-US
Accept-Encoding: gzip, deflate
Referer: http://requestingserver.example/path/to/askingdocument.html
Connection: Keep-Alive
Origin: http://requestingserver.example
Origin
一般包含请求文档的协议、域名、端口。Origin
不是一个“网页作者请求标头”,也就是说 Origin
不能用 setRequestHeader()
方法设置或更改 ― 用户代理会忽略这种设置。Origin
标头唯一的目的就是告知目标服务器该请求的来源。请注意 Origin
标头的结尾没有斜线。
Access-Control-Allow-Origin
响应标头
Access-Control-Allow-Origin
标头是在目标服务器收到跨来源请求时回应的标头。Access-Control-Allow-Origin
告诉用户代理是否应给予发出请求的来源存取
权限。 除非被请求的 URL 允许跨域存取,否则与跨来源 XHR 请求相关的 DOM 操作皆不会完成。以下是例子:
HTTP/1.1 200 OK
Date: Fri, 27 May 2011 21:27:14 GMT
Server: Apache/2
Last-Modified: Fri, 27 May 2011 19:29:00 GMT
Accept-Ranges: bytes
Content-Length: 1830
Keep-Alive: timeout=15, max=97
Connection: Keep-Alive
Content-Type: application/xml; charset=UTF-8
Access-Control-Allow-Origin: *
这个例子用通配符(*)让任何来源的文档都可以存取此 URL,这在开发者要提供一个公用的 API 的时候是无所谓,但是在其他的使用情景里,开发者应该设置比较具体的值。
在跨域请求中发送认证讯息
开发者可能会有要在跨域请求中发送 Cookie 数据的状况,这时候 withCredentials
属性就登场了。withCredentials
属性是一个布尔属性,用来告知浏览器是否应该随同请求发送用户认证讯息。默认情况下,认证讯息旗帜为 false
。在下面的代码中,我们假设请求是一个从 http://foo.example 送到 http://other.server 的请求:
var xhr = new XMLHttpRequest();
var onLoadHandler = function(event) {
doSomething(event.target.responseText);
}
xhr.open('GET', 'http://other.server/and/path/to/script');
xhr.withCredentials = true;
xhr.onload = onLoadHandler;
xhr.send();
XHR 认证讯息演示使用 Cookie 作为计数器来追踪访问量。去检视请求与响应的数据(可以用 Dragonfly 的网路面板)就会看到浏览器的确在发送请求 Cookie 并收到响应 Cookie。服务器端脚本会回传包含新的访问计数的文本,并更新 Cookie 的值。
请记得在使用代有认证讯息的请求的时候:
- 仅在跨来源请求的时候需要设置
withCredentials
。 - 请求 URI 的
Access-Control-Allow-Origin
标头不能包含通配符(*)。 - 请求 URI 的
Access-Control-Allow-Credentials
标头必须设为true
。 - 除非设有
Access-Control-Expose-Headers
标头,getAllRequestHeaders()
仅可以取得一部分的响应标头。
在相同来源的情况下,浏览器求会忽略认证讯息旗帜。代有通配符的 Access-Control-Allow-Origin
标头会让浏览器抛出异常。若 Access-Control-Allow-Credentials
是 false
,浏览器会发送并接受 Cookie,但是不能从 DOM 中取得。
透过 FromData
对象发送索引键值配对
在之前的版本中,透过 XHR 发送的数据必须是一个 URL 编码或是(利用 JSON.strigify()
得到的)JSON 字符串。下面的例子使用 URL 编码:
var msg = 'field1=foo&field2=bar';
var xhr = new XMLHttpRequest();
xhr.open('POST','/processing_script');
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(msg);
但是现在开发者可以使用 FormData
对象跟它提供的索引键值配对语法。这个语法有三种个特点:
- 让脚本更容易读。
- 跟一般的 HTML 表单一样,数据以索引键值配对发送。
FormData
对象会使数据以multipart/form-data
编码发送,让 XHR 可以发送二进制的数据。
FormData
跟 ActionScript 3.0 的 URLVariables
使用起来的感觉很像:先创建一个 FormData
对象,再使用 append()
方法添加数据。append()
方法使用两个参数:键与值。FormData
的每一个键会变成服务器端脚本可以使用的一个变量名。以下是个例子:
var xhr = new XMLHttpRequest();
var dataToSend = new FormData(); // 创建一个 FormData 对象
xhr.open('POST','/processing_script');
dataToSend.append('name', '哆啦A梦'); // 在对象上添加数据
dataToSend.append('age', '11');
dataToSend.append('hobby', '吃铜锣烧');
xhr.send(dataToSend); // 发送对象
在最后面的地方 FormData
对象被当成 send()
方法的参数:xhr.send(dataToSend)
。上面的例子并没有在 XMLHttpRequest
对象上设置 Content-Type
标头。来看看 Opera 传了什么请求标头:
POST /processing_script HTTP/1.1
User-Agent: Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8) Presto/2.10.289 Version/12.00
Host: datahost.example
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en,en-US
Accept-Encoding: gzip, deflate
Expect: 100-continue
Referer: http://datahost.example/upload/
Connection: Keep-Alive
Content-Length: 4281507
Content-Type: multipart/form-data; boundary=----------J2GMKTyAkjRjNgFzKv3VBJ
由于例子里用了 FormData
对象,Opera 帮忙添加了 Content-Type
标头。其他浏览器也是这样的情形。
由 HTML 表单创建 FormData
开发也可以使用 FormData
发送表单的值,只要将表单传给 FormData
对象就可以了(XHR FormData 演示),如下:
var submitHandler = function(event) {
var dataToSend = new FormData(event.target), xhr = new XMLHttpRequest();
xhr.open('POST', '/processing_script');
xhr.send(dataToSend);
}
var form = document.getElementById('myform');
form.addEventListener('submit', submitHandler, false);
FormData
仍是不受信任的数据。请将由 FromData
对象来的数据当成与一般的表单提交得到的数据无异。
使用进度事件监测数据传输
现在的 XMLHttpRequest
已经提供了让开发者监测数据传输的进度事件属性。在之前的时候,开发者需要监听 readystatechange
事件,如下面的例子:
var xhr = new XMLHttpRequest();
var onReadyStateHandler = function(event) {
if( event.target.readyState == 4 && event.target.status == 200){
/* 处理响应 */
}
}
xhr.open('GET','/path_to_data');
xhr.onreadystatechange = onReadyStateHandler;
xhr.send();
虽然 readystatechange
在开发者仅注意所有数据的下载完全时间的情形下足够用了,但是 readystatechange
并不能提供到底已经接收了多少数据的即时信息。为了向后兼容,readystatechange
仍是规范的一部分,但是新的 ProgressEvent
接口可靠多了。新的规范为 XMLHttpRequest
与 XMLHttpRequestUpload
对象加上了七个事件。
以下是 XMLHttpRequest
的 ProgressEvent
列表:
属性 | 形态 | 解释 |
---|---|---|
onloadstart | loadstart | 请求开始的时候。 |
onprogress | progress | 读取与发送数据的时候会不断触发本事件。 |
onabort | abort | 经由调用 abort() 方法或浏览到别的页面使得请求退出的时候。 |
onerror | error | 请求发生错误的时候。 |
onload | load | 请求成功完成的时候。 |
ontimeout | timeout | 开发者指定的超时时间已经超过了但是请求仍未能完成的时候。 |
onloadend | loadend | 不管请求成功与否,请求已经完成的时候。 |
由于 XMLHttpRequest
与 XMLHttpRequestUpload
继承 EventTarget
接口,开发者者可以在代码中使用事件属性(如 onload
)或是 addEventListener
方法。下面的范例使用 addEventListener
。
监测上传的进度
所有基于 XMLHttpRequest
的档案上传都会产生一个 XMLHttpRequestUpload
对象,开发者可以通过 XMLHttpRequest
的 upload
属性取得该对象。要监测上传进度,开发者需要监听 XMLHttpRequestUpload
对象上的事件。
下面的代码监听了 progress
、load
与 error
事件:
var onProgressHandler = function(event) {
if(event.lengthComputable) {
var howmuch = (event.loaded / event.total) * 100;
document.querySelector('progress').value = Math.ceil(howmuch);
} else {
console.log("无法取得档案的大小。");
}
}
var onLoadHandler = function() {
displayLoadedMessage();
}
var onErrorHandler = function() {
displayErrorMesssage();
}
xhr.upload.addEventListener('progress', onProgressHandler, false);
xhr.upload.addEventListener('load', onLoadHandler, false);
xhr.upload.addEventListener('error', onErrorHandler, false);
情特别注意 onProgressHandler
函式里用的 lengthComputable
、loaded
与 total
,这些都是进度事件对象上的属性。lengthComputable
属性告诉开发者浏览器可否侦测上传档案的大小,而 loaded
与 total
告诉开发者已上传了多少个字节与档案的总大小。请看 XHR 进度事件的演示。
这些事件监测了浏览器传输数据到服务器或服务器接收数据的进度。在上传的情况下,有可能在触发 load
事件与服务器回传响应之间有一个时间差。这个时间差的长度取决于档案的大小、服务器的资源与网路的速度。
上面的例子为 XMLHttpRequestUpload
设置了事件监听,要监档案下载要在 XMLHttpRequest
对象上添加事件监听。
强制变更响应的 MIME 形态
MIME 不正确的情况在万维网上十分常见。有时候 XML 数据的响应标头有 Content-type: text/html
,使得 xhr.responseXML
变成 null
。
为了确保浏览器依照开发者的意思处理这样的响应,开发者可以使用 overrideMimeType()
方法。在下面的例子里,data.xml 会回传下列的响应标头:
Date: Sat, 04 Jun 2011 03:11:31 GMT
Server: Apache/2.2.17
Access-Control-Allow-Origin: *
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
也就是 XML 文档的内容形态是错误的。下面代码确保浏览器将 data.xml 视为 XML并设置 responseXML
属性:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'data.xml');
xhr.overrideMimeType('application/xml');
xhr.send();
xhr.addEventListener('load', function(event) {
console.log(event.target.responseXML);
}, false);
现在 xhr.responseXML
的之已经是一个 Document
对象了,也就是开发者可以像碰到正常的 XML 文档一样解析数据了。观看覆盖 XHR 的 MIME 形态的演示。
强制使用某种数据形态
开发者也可以使用 responseType
属性让浏览器将响应当成 text
、json
、arraybuffer
、blob
或 document
。
开发者必须在送出请求前设置 responseType
属性,就像 overrideMimeType
一样。下面的例子让 Opera 把响应当作是 document
,并将 firstChild
写到控制台里(观看强制使用某种数据形态的演示):
var xhr = new XMLHttpRequest();
xhr.open('GET','data.xml');
xhr.responseType = 'document';
xhr.send();
xhr.addEventListener('load', function(e) {
console.log(event.target.response.firstChild);
} false);
虽然 responseType
可以让开发者将图像数据当作一个字节数组而不是二位元字符串,但是它不会创造奇迹:在上面的例子中将 document
改成 json
会让 response
属性变成 null
,因为 XML 不是 JSON。同样的,不合法的 JSON 数据也会让 response
变成 null
。开发者需要在设置 responseType
的时候确保数据符合指定的形态而且合法。
注:在文章发布的这个时间点,Opera 尚未支持 blob
值,在 document
形态下也只之持 XML 而不支持 HTML。Chrome 与 Safari 不支持 json
值。
学习更多
这些 XMLHttpRequest
的进步对于用户端的交互性能是一种跃升。请阅读以下资源以获取更多关于 XMLHttpRequest
、CORS 与相关 API 的讯息:
- XMLHttpRequest specification
- DOM access control using cross-origin resource sharing
- Bruce Lawson 写的 The W3C file API
- XDomainRequest Object (Internet Explorer 8 & 9)