首页 » 前端开发 » 正文

ajax在IE8/9下跨域的解决方案

本文给出了ajax在IE8/9下跨域访问数据时存在的”No Transport“问题的解决方案,同时辅助以demo让读者可以在阅读过程中自行进行尝试。本文的读者假定已经具备了一定的JavaScript编程知识,使用过jquery,而且接触过ajax,对“跨域”等概念是完全理解的。如果急于寻找解决方案请直接跳到“解决方案”一小节

同时文章末尾会简单给出其他几种解决方案供读者参考。

下面进入正题:

 服务端

一般来说,不管通过哪种方式进行的跨域,服务端一定是需要进行相关配置的[注1],这里就涉及到了跨域资源共享(CORS)。这种方案采用新的”Origin:”请求头和新的Access-Control-Allow-Origin响应头来扩展HTTP。它允许服务器用头信息显示地列出源,或使用通配符来匹配所有的源并允许由任何地址请求文件[1]。

本文中可以测试的服务端地址是: https://www.hellocoolguy.com/test/cors.php

读者有兴趣可以用该链接进行测试,其源代码如下:

header("Access-Control-Allow-Origin:*");
$str = "";
if($d = $_REQUEST['d']){
    $str .= $d . ":";
}
$str .= "HelloWorld";
echo json_encode(array("msg"=>$str));

代码中的header(“Access-Control-Allow-Origin:*”);相当于允许了所有的源地址访问该链接。源代码也很好理解,如果访问这个链接,并且传入了d的参数,那么将输出一个json字符串,其键是msg,值是传入的d的参数的值加上”:”和”HelloWorld”。读者也可以直接访问链接(https://www.hellocoolguy.com/test/cors.php?d=CoolGuy)得到这样的效果。有了以上代码,前端就可以进行数据的请求了。

 前端

常规代码

前端很容易写出获取数据的代码,采用jquery的ajax其代码如下:

var url = "https://www.hellocoolguy.com/test/cors.php";
$.ajax(url,{data:"d=CoolGuy",type:"post","dataType":"json"}).done(function(data){
console.log(data.msg);
}).fail(function(data){
console.log(data);
})

以上代码采用了jquery提供的deferred对象,要求版本应该是高于1.5的,当然你也可以完全采用更简单的方式去实现。

写完以上代码,用火狐或者chrome浏览器测试,发现完全没有问题,直接就正常输出了结果。

然后在IE8/9下跑,发现出错了,进一步发现是进入了fail回调(如果采用的是兼容jquery1.4版本的代码,就是进入了error回调),进一步打出日志发现,data.statusText=”No Transport“.

解决IE8/9问题的过程

上网查阅资料发现,有人给出了添加一行代码的方案:

jQuery.support.cors = true;

实际测试发现,错误不再是”No Transport”,而是变成了没有权限的error[注2]。参考资料2给出了一个解决这个问题的一个方案,那就是:

点击IE浏览器的的“工具->Internet 选项->安全->自定义级别”将“其他”选项中的“通过域访问数据源”选中为“启用”或者“提示”

这个方案的确能够让访问变得正常,但是你肯定不能期待每个用户使用你的网站时,还这样去操作一遍。那么应该如何解决这个问题呢?

解决方案

 通过阅读微软的MSDN,我们发现了XDomainRequest对象,详细内容参见参考资料3。

XDomainRequest是定义在window上的一个对象,主要用于解决跨域ajax。其解决方案代码如下:

var url = "https://www.hellocoolguy.com/test/cors.php";
var sendData = "d=CoolGuy";
if (window.XDomainRequest) {
    var xdr = new XDomainRequest();
    xdr.open("post", url);
    xdr.onprogress = function () {
    };
    xdr.ontimeout = function () {
    };
    xdr.onerror = function (data) {
        console.error(data);
    };
    xdr.onload = function () {
        console.log(xdr.responseText);
        //todo:后续处理,由于此处获取回来是文本,需要手动转化为JSON格式处理
        //可以采用JSON.parse(xdr.responseText)的方式}; xdr.send(sendData);
 }//下方代码和上文提及的一样
 else $.ajax(url, {data: sendData, type: "post", "dataType": "json"}).done(function (data) { console.log(data.msg); }).fail(function (data) { console.log(data); })

相关技术细节就不再描述了,读者可以自行阅读参考资料,同时需要注意的是,根据参考资料4, XDomainRequest只在IE8和9中实现了,在IE10中也直接给咔嚓掉了。目前也仅有IE8/9支持该对象,其他浏览器均不支持,因为它是一个非标准的实现。

另外注意,根据参考资料4,xdr.send()调用应该放在一个setTimeout(…,0)中,用来防止多个XDomainRequest同时请求时出错。(本文为了简化就直接无视掉这个情况)

 其他解决方案

  • 提到跨域,首先想到的应当是JSONP,他采用的是新增script标签去请求的方案,其优点在于通用性不错,缺点在于一方面不支持POST请求(因为script标签一定是GET方式的),另一方面是需要服务端额外处理数据,具体细节不在此处讨论,有兴趣的读者可以查看参考资料5的相关部分。
  • 如果可以配置服务器端的话,还可以通过反向代理的方式来解决问题。有兴趣的读者可以查看参考资料6.
  • 如果是不同域之间的通信,还可以考虑用H5的postMessage,不过貌似对于ajax这一块暂时没有不能起到作用。关于postMessage和其他一些解决方案可以查阅参考资料7.

 总结

IE一直是一个让前端工程师头疼的浏览器,jquery帮我们屏蔽了很多差异,但是在某些方面我们还是没法依赖,只能靠对IE多一份了解来解决问题,这一块可以多参考微软的MSDN等。

本文完成得比较仓促,若有不正确的地方,还望各位读者批评与指正。

 附录

注意事项

注1: 其实,如果2个域名之间需要跨域通信(一般是其中一个在当前页面的iframe里边),但是他们的一级域名相同,可以通过在2个域名中将document.domain设置成相同的值来解决。比如可以将a.example.com和b.example.com的document.domain设置成example.com,那么他们之间是能够正常地相互访问的。(domain的设置是有限制的,比如你不能把b.example.com的domain设置成baidu.com来和百度进行通信)

注2:测试发现,不是所有的IE8/9都会出现类似的情况,比如本机测试的情况是,会弹出警告对话框让用户选择是否允许加载跨域数据;而部分IE8/9会直接不给任何提示直接拒绝掉。

 

参考资料

1. 《JavaScript权威指南》第6版 13.6.2 同源策略 P337

2. 《IE9版本以下ajax 跨域问题解决

3. 《XDomainRequest object

4. 《XDomainRequest

5. 《说说JSON和JSONP,也许你会豁然开朗

6. 《apache做反向代理服务器

7. 《JavaScript跨域总结与解决办法

本文共 3 个回复

  • 的十大 2016/05/19 19:18

    🙂 😮 😮 是多少 😯 😯 😯

  • jimmy 2016/08/23 19:04

    力哥,一般跨域问题,是服务端来负责解决吗?另外,在什么情况下,必须要跨域呢?

    • coolguy 博主 2016/08/23 19:54

      @ jimmy 其实跨域问题基本上都是需要服务器端配合的,也就是说一般来说前端都无法单独解决跨域问题,当然具体情况应该具体分析,比如附录1的注1的情况下是不太需要服务端配合,总体来说不能单纯的理解成是服务端负责的。目前我接触到比较多的跨域主要就是2种情况,一种是三方提供API,这种情况下提供方基本上就能处理完跨域问题(当然如果用IE8/9请参考本文的实现),前端基本上不需要介入,当然提供方也能可选的提供JSONP的方式来处理;另一种情况就是附录1的注1的情况,在我们项目中具体情况是这样的: 我们正常的请求路径都是类似shop.example.com,但是图片接口是在image.example.com上的,两者的顶级域名相同(都是example.com),但是二级域名不同,对前端而言就是跨域了,所以在shop.example.com上请求了image.example.com上的数据之后,不能够正常地读到里边的数据;所以需要设置document.domain来达到可以访问的情况。

发表评论