Ajax是Asynchronous JavaScript and XML的缩写,可以达到无需重载整个页面的情况下进行页面内容的局部更新(不更新页面的同时从服务器获取数据);其核心是XMLHttpRequest对象(XHR),最早由微软提出,后来被多家浏览器厂商实现;
XHR对象
IE5是最早引入了XHR对象的浏览器,其通过ActiveX实现了XHR;目前,IE7+、Chrome、FireFox、Safai、Opera等都实现了原生XHR对象,可以使用new XMLHttpRequest()创建XHR对象; 而低版本的IE浏览器通过ActiveX对象实现,并且存在三个版本:MSXML2.XMLHttp/ MSXML2.XMLHttp3.0/ MSXML2.XMLHttp6.0;可以使用如下代码创建一个兼容的XHR对象:
function createXHR () {
if (typeof XMLHttpRequest !== 'undefined') { // for IE7+等
return new XMLHttpRequest();
} else if (typeof ActiveXObject !== 'undefined') {
if (typeof arguments.callee.activeXString !== 'string') {
var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'],
i,
len;
for (var i = 0, len = versions.length; i < len; i++) {
try { // 低于IE7版本的IE浏览器
new ActiveXObject(vesions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
continue;
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
} else {
throw new Error('No XHR object available.');
}
}
XHR对象具有多个方法,其中open()用于启动一个请求,而不是真正发送,其可以接受三个参数:第一个为请求方式(get/ post/ head等),第二个为URL(可以为相对或绝对路径,相对路径相对于当前页),第三个参数是表示是否异步请求(true为异步);
send(string)方法用于发起请求,string仅用于post请求;另外,由于对于某些浏览器来该参数必须,为保证兼容性需要传入null;
收到响应时,XHR对象的相关属性:
status:响应HTTP状态;
statusText:HTTp状态说明;
responseText:响应文本;
responseXML:当响应内容类型为‘text/xml’‘application/xml’时保存响应数据的XML DOM文档;其他类型则值为null;
XHR对象的readyState属性:
0:未初始化,未调用open()方法;
1:启动,调用open()方法未调用send()方法;
2:发送,调用send()方法,尚未收到响应;
3:接收部分响应数据;
4:接收到全部响应数据;
每当readyState状态发生变化时都会触发onreadystatechange;为保证浏览器兼容性,必须先在open()方法前为onreadystatechange设定事件处理程序;
var xhr = createXHR();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log(xhr.responseText);
} else {
console.log('request failed ' + xhr.status);
}
}
};
xhr.open('get', 'test.php', true);
xhr.send(null);
接收响应前可以使用abort()方法取消异步请求,之后无法访问与响应有关的XHR对象属性,调用abort()并需要对XHR进行解引用操作;
HTTP头部信息
1、HTTP请求头
Header field name | description | example |
---|---|---|
Accept | 浏览器可以接收响应的内容类型 | Accept: text/plain |
Accept-Charset | 浏览器可以处理的字符集 | Accept-Charset: utf-8 |
Accept-Encoding | 浏览器可以处理的编码类型 | Accept-Encoding: gzip, deflate |
Accept-Language | 浏览器可以处理的语言类型 | Accept-Language: en-US |
Accept-Control-Request-Method | 初始化一个跨域资源共享的请求 | Accept-Control-Request-Method: GET |
Cache-Control | 浏览器缓存控制机制 | Cache-Control: no-cache |
Connection | 浏览器与服务器间的连接类型,不能在HTTP/2中使用 | Connection: keep-alive;Connection: Update |
Cookie | 当前页面被浏览器设置的HTTP Cookie | Cookie: $Version=1; Skin=new; |
Content-Type | PUT或者POST请求体的MIME类型 | Content-Type: application/x-www-form-urlencoded |
Host | 服务器域名,HTTP/2中不应该被使用 | Host: en.wikipedia.org:8000 |
Referer | 发出请求的页面的URI | http://en.wikipedia.org/wiki/Main_Page |
User-Agent | 浏览器的用户代理字符串 | User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 |
可以使用setRequestHeader(name, value)方法设置请求头,注意需要在open()方法后调用;为了不影响浏览器的响应,建议使用自定义的请求头信息;
2、HTTP响应头
Header field name | description | example |
---|---|---|
Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Allow-Methods, Access-Control-Allow-Headers | 允许参与跨域资源共享的站点 | Access-Control-Allow-Origin: * |
Allow | 规定特定资源的合法请求方式 | Allow: GET/HEAD |
Expires | 请求资源的有效期 | Expires: Thu, 01 Dec 1994 16:00:00 GMT |
同样,取得响应头信息可以使用getResponseHeader(name)和getAllResponseHeaders();后者是一个无参方法,返回的是一个包含所有响应头信息的多行文本信息;
可以利用头部信息从服务端向客户端发送额外的结构化数据;
GET请求
常用语信息查询,可以将查询字符串正确编码后添加到URL末端;GET请求是安全且幂等的
function addURLParam(url, name, value) {
url += (url.indexOf('?') === -1) ? '?' : '&';
url += encodeURIComponent(name) + '=' + encodeURIComponent(value);
return url;
POST请求
通常用于向服务器提交需要保存的数据;数据通常放在POST的请求主体中,可以包含任意格式的数据,数据量的大小理论上没有限制;POST方法既不安全也不幂等(修改资源时,URL指示的是父级资源,而待修改资源的身份信息保存在请求体中,注意与PUT请求的不同,PUT请求不安全但幂等,是因为其URL直接指示为待修改资源,多次操作结果是一致的;安全性指的是服务端的资源状态不被改变,幂等性指的是多次请求返回结果相同);
设置了请求头的POST请求的数据请求时在服务端才能使用$_POST超级变量取到,否则需要使用$HTTP_RAW_POST_DATA
;
由于请求数据和请求头的原因,POST请求一般资源消耗比GET请求大;
XMLHttpRequest 2级
FormData
XMLHttpRequest 2级定义了用于表单序列化的FormData,同时为FormData对象添加了append(key,value)用于添加表单字段的名字和值;该方法还可以接受一个可选的第三参数,用于指定文件的文件名,当value参数被指定为一个Blob对象或者一个File对象时,该文件名会被发送到服务器上,对于Blob对象来说,这个值默认为”blob”.
优势是可以不必明确地通过setRequestHeader指定请求头部,XHR对象能够识别并为FormData指定相应的头部信息;
创建FormData实例:
var data = new FormData(form); // form为可选的表单元素参数;
var data = new FormData();
var data = new FormData(document.forms[0]);
创建的FormData实例可以直接作为send()函数的参数;
浏览器对于FormData的支持能力一般,IE10+,Chrome7+、FireFox4+等才可支持
跨域资源共享(CORS)
使用XHR实现Ajax通信会受到同源策略(Fetch也受到该限制)的限制,为了满足某些合理的跨域需求,CORS便是其中的一种跨域解决方案。(很多时候跨域请求可以正常发起,但是返回结果会被浏览器拦截;但是从HTTPS跨域访问HTTP时浏览器会在请求发出前就拦截该请求);
CORS需要浏览器和服务器的同时支持,目前所有的浏览器都支持CORS;
CORS(Cross-Origin Resource Sharing)通过使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求和响应是否 成功;
使用CORS进行请求时,需要附加一个额外的Origin请求头,其中包含了请求页面的协议、域名、端口号信息;当服务器接收到该头部信息时,如果决定对该请求给予响应,将会在Access-Control-Allow-Origin响应头部加入相同的源信息;
Origin: http://www.baidu.com
Access-Control-Allow-Origin: http://www.baidu.com
请求和响应都不包含cookie信息;
根据跨域资源共享标准,可以在以下实际应用中使用跨域HTTP请求:
1、XHR或者Fetch发起的HTTP请求;
2、通过CSS的@font-face使用跨域Web字体;
3、Scripts;
4、WebGL贴图;
5、使用 drawImage 绘制 Images/video 画面到 canvas;
// drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)
// 使用drawImage绘制imgs或者video时到canvas时可以进行跨域请求
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
img = new Image(60, 50);
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
img.onload = function () {
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
ctx.drawImage(img, 0, 0);
}
可以看到发起请求的页面在本地服务器,而请求资源地址为https://mdn.mozillademos.org/files/5397/rhino.jpg,显然是一个跨域的HTTP请求;同样,通过<img>
的src属性也可以获取到外域资源;
IE中CORS的实现
IE中使用与XHR类似的XDR(XDomainRequest)对象实现跨域通信,其具有与XHR一些不同的特点:
1、仅支持GET和POST请求;
2、只能设置请求头部中的Content-Type字段(用于POST请求);
3、不能访问响应头部信息;
4、cookie既不会随请求发送也不会随响应返回;
这些特点使得有效缓解了CSRF(Cross-Site-Request-Forgery)和XSS(Cross-Site-Scripting),因此提供了安全可靠的跨域通信;
XDR对象建立的请求都是异步的,因此其open()方法仅接受两个参数;另外,响应返回的数据均保存在responseText中,浏览器收到响应后仅可以访问responseText(即响应的原始文本),无法访问status等其他信息;响应有效和失败会分别触发load事件和error事件(除错误本身外没有任何其他详细信息);
同XHR一样支持timeout属性及ontimeout事件处理程序;
其他浏览器的CORS实现
FireFox 3.5+、Safari 4+、Chrome、IOS版Safari及Android的webkit都通过XMLHTTPRequest实现对原生CORS的支持;
通过跨域XHR对象可以访问到status及statusText,而且支持同步请求,这与IE中的实现不同;
为了保障安全性,跨域XHR对象受到一定限制:
1、不可以发送和接收cookie,这与XDR一样;
2、不能用setRequestHeader()自定义请求头部;
3、调用getAllResponseHeaders()的结果总是空字符串;
简单请求
所谓的简单请求不会在实际请求发送前发出预检请求,通常需要满足以下请求方法之一:
1、GET;
2、HEAD;
3、Content-Type为text/plian、multipart/formdata、application/x-www-form-urlencoded之一的POST;
对于简单请求,以上请求头部的字段还要满足限制(满足对CORS安全的首部集合),比如不能使用Access-Control-Request-Method等;
预检请求(Preflighted Request)
预检请求是一种透明服务器验证机制,CORS中的预检请求就是在正式请求前使用OPTIONS方法发送一个请求,以获知服务端是否允许该跨域请求;预检请求的返回结果也可以告知浏览器是否需要发送身份凭证信息;
CORS基于该机制支持开发人员使用自定义的头部、多种请求方法及多种类型的主体内容;
兼容性:IE10及较低版本不支持,FireFox 3.5+、Safari 4+、Chrome均支持;
满足以下条件之一,就会发送预检请求:
1、使用了这些HTTP请求方法之一:PUT/ DELETE/ OPTIONS/ CONNECT/ TRACE/ PATCH
2、人为设置了对CORS安全的首部字段集合之外的字段;
3、Content-Type的值不是text/plain、multipart/formdata、application/x-www-form-urlencoded之一;
当使用Origin、Access-Control-Request-Method(请求自身的方法)及Access-Control-Request-Headers(可选的自定义头部,用逗号分割)时,就会向服务器以OPTIONS请求方式发送请求(上述请求头部信息);
服务器允许请求的情况下,响应头部包含以下字段:
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age(预检请求最大缓存时间(秒));
由于预检请求响应头部中包含了Access-Control-Max-Age,请求结果会根据响应头部中的相应字段缓存起来以便后续访问,有效期内下次访问不会再进行预检请求;与普通HTTP请求相比,在第一次进行预检请求时会多一次HTTP请求;
带凭证的请求
凭证指的是cookie、HTTP认证、客户端SSL等信息;默认情况下,跨域请求时不会发送这些凭证;如有需要,可以设置XHR对象实例的withCredentials属性为true,以指定该请求需要发送凭证;(支持xhr.withCredentials属性的浏览器:IE10及较低版本不支持,FireFox 3.5+、Safari 4+、Chrome均支持;)
当服务器接收该请求时,响应头部中会包含以下字段值:
Access-Control-Allow-Credentials: true
当该类型的请求响应成功但是响应头部不含Access-Control-Allow-Credentials字段时,浏览器不会将得到的响应交给JS脚本处理,并会触发onerror事件处理程序(responseText为空,status为0);
对于带凭证的请求,服务端不得将Access-Control-Allow-Origin设置为通配符“*”,需要设置为具体源地址,否则会请求失败;
跨浏览器的CORS
虽然不同浏览器对预检请求和带凭证的请求兼容性较差,但是由于所有的浏览器都支持简单的请求,因此可以基于此实现一个跨浏览器的CORS;需要考虑withCredentials与XDomainRequest的兼容性,以及IE的XDR对象与XHR对象有一定区别,好在它们都具有以下共有属性/ 方法:
1、send()
2、onload()
3、onerror()
4、responseText
5、abort()
基于这些共性,实现兼容的CORS方案:
/**
* 跨浏览器的CORS请求
* @param {[type]} method [description]
* @param {[type]} url [description]
* @return {[type]} [description]
*/
function createCORSRequest (method, url) {
var xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) { // IE10及较低版本不支持该属性
xhr.open(method, url, true);
} else if (typeof XDomainRequest !== 'undefined') {
xhr = new XDomainRequest();
xhr.open(method, url); // 该方式仅支持异步
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest('get', 'http://www.baidu.com');
if (request) {
request.onload = function () {
console.log(xhr.responseText);
}
request.send();
}
参考文献
1. https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
2. https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS