Ajax CORS

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 namedescriptionexample
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 CookieCookie: $Version=1; Skin=new;
Content-TypePUT或者POST请求体的MIME类型Content-Type: application/x-www-form-urlencoded
Host服务器域名,HTTP/2中不应该被使用Host: en.wikipedia.org:8000
Referer发出请求的页面的URIhttp://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 namedescriptionexample
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;需要考虑withCredentialsXDomainRequest的兼容性,以及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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil-

你们的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值