blog icon indicating copy to clipboard operation
blog copied to clipboard

关于 CORS —— 小船翻在了 IE8 的小沟沟里

Open YIXUNFE opened this issue 10 years ago • 0 comments

关于 CORS —— 小船翻在了 IE8 的小沟沟里

如今使用 CORS 技术做跨域通信已经不是什么新鲜事情了,我们网页中的很多接口在跨域方案上也首选 CORS。然而 CORS 看似完美的背后,却有值得我们注意的地方。

我们团队在开发新需求的时候,测试 MM 发现 IE8 下网页中的一个 CORS 接口返回异常,提示没有权限,排查这个问题的过程中加深了我们对 IE 实现 CORS 技术的认知。


## CORS 是什么

网上已经有太多的篇幅描述 CORS 是什么,这里简单介绍一下。CORS 技术就是为了实现跨域请求,而在响应头中设置一些 CORS 技术规定的响应头,如 Access-Control-Allow-Origin 等用以实现客户端与服务端进行跨域通信。

通常一个简单的 CORS 响应头组成有如下内容:

Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: http://www.yixun.com

CORS 方案的 HTTP 请求是不会携带 cookie 的,如果需要带上 cookie,需要在响应头中多添加一些字段:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:withCredentials

同时客户端在请求头中需要设置字段 withCredentialstrue

withCredentials: true

允许带 cookie 时,Access-Control-Allow-Origin 字段值不可以设置为 *


## 为什么选择 CORS 方案

CORS 方案较之于传统的方案优点很多。

  • 相比 JSONP,CORS 可以使用 POST 方式提交数据就完败了 JSONP 方案;
  • 相比 document.domain 方案,CORS 方案的响应内容没有格式限制,而 document.domain 方案不仅需要设置 document.domain,还需要借助 iframe 元素接受带有指定格式的返回数据,方案比较繁琐;
  • window.postMessage 方案和上述的 document.domain 方案基本类似,不仅需要对接口的返回数据进行特别处理,还需要使用一个 iframe 元素进行接受。

总结下来,选择 CORS 跨域方案最大的好处是返回数据可以在跨域与同域时完美兼容,不需要接口多 处理不同情况下的返回数据格式,前端实现也十分简单方便。


## IE8 下为什么请求提示没有权限?

返回文章起首提到的问题,请求接口提示没有权限。但是在 Chrome、Firefox 中确认接口是正确添加了 CORS 需要的响应头字段了。

后来在爆栈上搜索发现,原来 IE8\9 实现 CORS 的对象和实现 AJAX 的对象(XMLHTTP)不是同一个,而是使用了 window.XDomainRequest 对象

由于我们的 PC 站使用的 jQuery 版本太低(5.1版),$.ajax 方法没有兼容 IE8\9 的 XDomainRequest 对象,于是网页使用了 XMLHTTP 对接口发起请求,所以会返回“没有权限”的提示。为了兼容这个情况,我们做了一些手动的兼容:

if (/*判断是否跨域*/ && window.XDomainRequest) {
  var xdr = new XDomainRequest()
  xdr.open('post', url)
  xdr.onload = function() {
    //success(xdr.responseText);
  }
  xdr.send()
} else {
  $.ajax(...)
}

在 IE8/9 下使用 XDomainRequest 进行跨域时还有一些限制:

  • 无法设置请求头中的自定义字段;
  • 无法携带 cookie 。

在有上述两项需求并需要兼容 IE8\9 时,CORS 方案就无法满足我们的需求了,需要定制另外的跨域方案。于是我们有部分接口只能通过修改接口将需要的 cookie 值放在请求的 query string 中或者采用其他的跨域方案解决问题。


## 扩展阅读 ### document.domain 跨域方案

当仅二级域名不同时,通过两个页面设置相同的主域名,比如

//base.yixun.com
document.domain = 'yixun.com'

就可以互相通信。接口可以通过 form 元素的 target 属性指定提交至一个 iframe 元素中,接口中返回带有格式的数据,如:

<!--接口的返回数据-->
<script>
document.domain = 'yixun.com'
frameElement.callback(...) // frameElement 即 iframe 元素本身,callback 方法在父页面中定义
</script>

iframe 元素得到了一个 script 标签并执行,而 iframe 元素的 callback 预先已经定义好:

ifr = document.getElementById(...) //获取 iframe 元素
ifr.callback = function (d) { //定义 iframe 元素的 callback 方法
  success(d)
  this.src = 'about:blank' //避免页面刷新重复提交
}

postMessage 方案和 document.domain 原理相似,就不再赘述。


## Thanks

YIXUNFE avatar Jan 12 '16 14:01 YIXUNFE