浏览器与服务器之间,采用HTTP协议通信。用户在浏览器她址栏键入一个网址,或者通过网页表单向服务器提交内容,这时浏览器就会向服务器发出HTTP请求。
AJAX
1999年,微软公司发布E浏览器5.0版,第一次引入新功能:允许JavaScript脚本向服务器发起HTTP请求。这个功能当时并没有引起注意,直到2004年Gmail 发布和2005年Google Map 发布,才引起广泛重视。2005年2月,AJAX这个词第一次正式提出,它是Asynchronous JavaScript and XML的缩写,指的是通过JavaScript的异步通信,从服务器获取XML文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。后来,AJAX这个词就成为JavaScript脚本发起HTTP通信的代名词,也就是说,只要用脚本发起通信,就可以叫做AJAX通信。W3C也在2006年发布了它的国际标准。
AJAX包括以下几个步骤。
1.创建XMLHttpRequest 实例
2.发出HTTP请求
3.接收服务器传回的数据
4.更新网页数据
AJAX通过原生的XMLHttpRequest对象发出HTTP请求,得到服务器返回的数据后,再进行处理。现在,服务器返回的都是JSON格式的数据,XML格式已经过时了,但是AJAX这个名字已经成了一个通用名词,字面含义已经消失了。
XMLHttpRequest对象是AAX的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有XML和Http,它实际上可以使用多种协议(比如file或ftp),发送任何格式的数据(包括字符串和二进制)。
XMLHttpRequest本身是一个构造函数,可以使用new命令生成实例。它没有任何参数。
var xhr = new XMLHttpRequest();
XML文档实例
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading></heading>
<body>this is a string</body>
</note>
一旦新建实例,就可以使用open()方法指定建立HTTP连接的一些细节。
xhr. open( ' GET', 'http: / /www.example.com/page.php', true);
上面代码指定使用GET方法,跟指定的服务器网址建立连接。第三个参数true,表示请求是异步的。然后,指定回调函数,监听通信状态(readystate属性)的变化。.
xhr. onreadystatechange = handleStateChange;
function handlestatechange( ){
// ...
}
上面代码中,一旦XMLHttpRequest实例的状态发生变化,就会调用监听函数handlestatechange最后使用send()方法,实际发出请求。
xhr. send(null);
上面代码中,send()的参数为null,表示发送请求的时候,不带有数据体。如果发送的是POST请求,这里就需要指定数据体。
一旦拿到服务器返回的数据,AJAX不会刷新整个网页,而是只更新网页里面的相关部分,从而不打断用户正在做的事情。
注意,AJAX只能向同源网址(协议、域名、端口都相同)发出 HTTP请求,如果发出跨域请求,就会报错(详见《同源政策》和《CORS通信》两章)。
下面是XMLHttpRequest对象简单用法的完整例子。
var xhr = new XMLHttpRequest();
xhr. onreadystatechange = function({
//通信成功时,状态值为4
if (xhr.readystate === 4){
if (xhr.status === 200){
console.log(xhr.responseText);
}else {
console.error(xhr. statusText);
}
}
};
xhr. onerror = function (e) {
console.error(xhr. statusText);
};
xhr.open( ' GET','/endpoint ' , true);
xhr.send(nu11);
XMLHttpRequest.onreadystatechange属性指向一个监听函数。readystatechange事件发生时(实例的readystate属性变化),就会执行这个属性。
另外,如果使用实例的abort()方法,终止XMLHttpRequest请求,也会造成readystate属性变化,导致调用XMLHttpRequest.onreadystatechange属性。
XMLHttpRequest. readystate返回一个整数,表示实例对象的当前状态。该属性只读。它可能返回以下值。
0,表示XMLHttpRequest 实例已经生成,但是实例的open()方法还没有被调用。
1,表示open()方法已经调用,但是实例的send()方法还没有调用,仍然可以使用实例的setRequestHeaier()方法,设定HTTP请求的头信息。
2,表示实例的send()方法已经调用,并且服务器返回的头信息和状态码已经收到。
3,表示正在接收服务器传来的数据体(body部分)。这时,如果实例的responseType属性等于text或者空字符串,responseText属性就会包含已经收到的部分信息。
4,表示服务器返回的数据已经完全接收,或者本次接收已经失败。
通信过程中,每当实例对象发生状态变化,它的readystate属性的值就会改变。这个值每一次变化,都会触发readystatechange事件。
XMLHttpRequest.responseType属性是一个字符串,表示服务器返回数据的类型。这个属性是可写的,可以在调用open()方法之后、调用send()方法之前,设置这个属性的值,告诉浏览器如何解读返回的数据。如果responseType设为空字符串,就等同于默认值text。
XMLHttpRequest.responseType属性可以等于以下值。
“”(空字符串):等同于text,表示服务器返回文本数据。
“arraybuffer”: ArrayBuffer 对象,表示服务器返回二进制数组。 “blob”: Blob对象,表示服务器返回二进制对象。
“document”: document对象,表示服务器返回一个文档对象。“json”: JSON对象。
“text”:字符串。
上面几种类型之中,text类型适合大多数情况,而且直接处理文本也比较方便。document类型适合返回HTML/XML文档的情况,这意味着,对于那些打开CORS的网站,可以直接用Ajax抓取网页,然后不用解析HTML字符串,直接对抓取回来的数据进行DOM操作。blob类型适合读取二进制数据,比如图片文件。
XMLHttpRequest.responseText属性返回从服务器接收到的字符串,该属性为只读。只有HTTP请求完成接收以后,该属性才会包含完整的数据。
XMLHttpRequest.responseXML属性返回从服务器接收到的HTML或XML文档对象,该属性为只读。如果本次请求没有成功,或者收到的数据不能被解析为XML 或 HTML,该属性等于null。
该属性生效的前提是HTTP回应的content-Type头信息等于text/xml或application/xml。这要求在发送请求前,XMNLHttpRequest.responseType属性要设为document。如果HTTP回应的content-Type头信息不等于text/xm1和application/xm,但是想从responseXML拿到数据(即把数据按照DOM格式解析),那么需要手动调用XMLHttpRequest.overrideMimeType()方法,强制进行XML解析。
XMLHttpRequest.responseURL属性是字符串,表示发送数据的服务器的网址。
XMLHttpRequest.status属性返回一个整数,表示服务器回应的HTTP状态码。一般来说,如果通信成功的话,这个状态码是200;如果服务器没有返回状态码,那么这个属性默认是200。请求发出之前,该属性为0。该属性只读。
. 200, OK,访问正常
. 301, Moved Permanently,永久移动
. 302, Moved temporarily,暂时移动
. 304, Not Modified,未修改
. 307, Temporary Redirect,暂时重定向
. 401, Unauthorized,未授权
. 403, Forbidden,禁止访问
. 404, Not Found,未发现指定网址
· 500, Internal Server Error,服务器发生错误
基本上,只有2xx和304的状态码,表示服务器返回是正常状态。
XMLHttpRequest.statusText属性返回一个字符串,表示服务器发送的状态提示。不同于status属性,该属性包含整个状态信息,比如"OK"“和"Not Found”。在请求发送之前(即调用open()方法之前),该属性的值是空字符串;如果服务器没有返回状态提示,该属性的值默认为“OK"。该属性为只读属性。
XMLHttpRequest.withCredentials属性是一个布尔值,表示跨域请求时,用户信息(比如Cookie和认证的HTP头信息)是否会包含在请求之中,默认为false,即向example.com发出跨域请求时,不会发送example.com设置在本机上的Cookie (如果有的话)。
如果需要跨域AJAX请求发送Cookie,需要withCredentials属性设为true。注意,同源的请求不需要设置这个属性。
CORS
CORS是一个W3C标准,全称是"跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与普通的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。
CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
(1)请求方法是以下三种方法之一
. HEAD
. GET
. POST
(2)HTTP的头信息不超过以下几种字段
. Accept
. Accept-Language
. Content-Language
. Last-Event-ID
. Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data ,text/plain
凡是不同时满足上面两个条件,就属于非简单请求。一句话,简单请求就是简单的HTTP方法与简单的HTTP头信息的结合。原因在于表单在历史上一直可以跨域发出请求。简单请求就是表单请求,浏览器沿袭了传统的处理方式,不把行为复杂化,否则开发者可能转而使用表单,规避CORS的限制。对于非简单请求,浏览器会采用新的处理方式。
(1) Access-control-Allow-origin
该字段是必须的。它的值要么是请求时origin字段的值,要么是一个 *,表示接受任意域名的请求。
(2) Access-control-Allow-credentials
该字段可选。它的值是一个布尔值,表示是否允许爱送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,浏览器可以把Cookie包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,不发送该字段即可。
(3) Access-control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个服务器返回的基本字段: cache-control、content-Language、content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar ')可以返回FooBar字段的值。
上面说到,CORS请求默认不包含Cookie信息(以及HTTP认证信息等),这是为了降低CSRF攻击的风险。但是某些场合,服务器可能需要拿到Cookie,这时需要服务器显式指定Access-Control-Allow-credentials字段,告诉浏览器可以发送Cookie。
Access-contro1-Allow-credentials: true
同时,开发者必须在AJAX请求中打开withcredentials属性。
var xhr = new XMLHttpRequest;
xhr.withcredentials = true;
否则,即使服务器要求发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
但是,有的浏览器默认将withcredentials属性设为true。这导致如果省略withcredentials设置,这些浏览器可能还是会一起发送Cookie。这时,可以显示关闭withcredentials 。
xhr.withcredentials = false;
需要注意的是,如果服务器要求浏览器发送Cookie,Access-Control-Allow-origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie 并不会上传,且(跨域)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
非简单请求是那种对服务器提出特殊要求的请求,比如请求方法是PUT或DELETE,或者content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。这是为了防止这些新增的请求,对传统的没有CORS支持的服务器形成压力,给服务器一个提前拒绝的机会,这样可以防止服务器收到大量DELETE和PUT请求,这些传统的表单不可能跨域发出的请求。
一旦服务器通过了"“预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个origin头信息字段。服务器的回应,也都会有一个Access-control-Allow-origin头信息字段。