1 前言:

首先对参考文章作者表示感谢,你们的经验总结给我们这些新手提供了太多资源。
本文致力于解决AJAX的CORS问题,我在逻辑上进行了梳理:首先,系统的总结了CORS问题的起源---同源策略;其次,介绍JSONP这种仅能支持GET请求的跨域方式和CORS作对比;最后,阐述CORS的XHR解决方式和IE中的XDR解决方式,在此基础上提供了工具函数进行跨浏览器的HTTP请求对象创建。

2 跨域问题的源头---同源策略

在客户端编程语言中,如javascript和 ActionScript,同源策略是一个很重要的安全理念,它的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?

很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

那么什么叫相同域(同源),什么叫不同的域(不同源)呢?当两个域具有相同的协议(如http),相同的端口(如80),相同的host(如www.example.org),那么我们就可以认为它们是相同的域。比如 http://www.example.org/index....(默认端口号80可以省略)和http://www.example.org/sub/in...是同域,而http://www.example.org, https://www.example.org,http://www.example.org:8080, http://sub.example.org中的任何两个都将构成跨域。
注意:只有协议、域名、端口号完全一样才是同一域,其他情况,即使是相对应的IP和域名也是不同域,具体情况如下图:

(这个图片忘了从哪里引得了,感谢作者)

目前,如果非同源,共有三种行为受到限制。

(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。

作为前端开发者,我们很多时候要做的是突破这种限制。

补充:同源策略还应该对一些特殊情况做处理,比如限制file协议下脚本的访问权限。本地的HTML文件在浏览器中是通过file协议打开的,如果脚本能通过file协议访问到硬盘上其它任意文件,就会出现安全隐患,目前IE8还有这样的隐患。

3 跨域方式JSONP[参考1]

JSONP是JSON with Padding的简写,是应用JSON实现服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
JSOP包含两部分:回调函数和数据,回调函数是在响应到来时应该调用的函数,一般通过查询字符串添加;数据就是传入回调函数中的JSON数据,确切的说,是一个JSON对象,可以直接访问。

实例:

//访问跨域src并将数据填入到script标签中的函数
    function addScriptTag(src) {
      var script = document.createElement('script');
      script.setAttribute("type","text/javascript");
      script.src = src;
      document.body.appendChild(script);
    }
//网页动态插入<script>元素,由它向跨源网址src发出请求,src中包含回调函数
    window.onload = function () {
      addScriptTag('http://example.com/ip?callback=foo');
    }
//回调函数的参数默认是返回的数据
    function foo(data) {
      console.log('Your public IP address is: ' + data.ip);
    };

/上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。/

JSONP的缺点
只能实现GET,没有POST;从其他域中加载的代码可能不安全;难以确定JSONP请求是否失败(XHR有error事件),常见做法是使用定时器指定响应的允许时间,超出时间认为响应失败。CORS与JSONP的使用目的相同,但是比JSONP更强大,它支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

4 AJAX实现跨域[参考2]

参考[2]中介绍了复杂HTTP请求,本文只包含简单的GET和POST请求。
CORS,即Cross-Origin Resource Sharing(https://www.w3.org/TR/cors/),跨域资源共享,定义了在必须跨域访问资源时,浏览器怎样和服务器交互。基本思想就是使用自定义的HTTP头部让浏览器和服务器进行沟通,从而决定响应的成功与失败。因此了解XHR的跨域必须要了解HTTP头部。

4.1 HTTP header

HTTP header分为请求头部和响应头部,在发送XHR请求时,会发送以下请求头部:
 Accept:浏览器能够处理的内容类型。
 Accept-Charset:浏览器能够显示的字符集。
 Accept-Encoding:浏览器能够处理的压缩编码。
 Accept-Language:浏览器当前设置的语言。
 Connection:浏览器与服务器之间连接的类型。
 Cookie:当前页面设置的任何 Cookie。
 Host:发出请求的页面所在的域 。
 Referer:发出请求的页面的 URI。注意, HTTP 规范将这个头部字段拼写错了,而为保证与规范一致,也只能将错就错了。(这个英文单词的正确拼法应该是 referrer。)
 User-Agent:浏览器的用户代理字符串。
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

使用 setRequestHeader()方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用 open()方法之后且调用 send()方法之前调用 setRequestHeader()。
request.open("GET","https://sf-static.b0.upaiyun.com/v-57fcb48b/user/script/index.min.js",true);
xhr.setRequestHeader("MyHeader","MyValue");
request.send(null);
服务器在接收到这种自定义的头部信息之后,可以执行相应的后续操作。

调用 XHR 对象的 getResponseHeader()方法并传入头部字段名称,可以取得相应的响应头部信息。而调用 getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串,这种格式化的输出可以方便我们检查响应中所有头部字段的名称。

测试实例:

//创建请求对象
  var request = createRequest();
  if (request == null) {
    alert("Unable to create request");
    return;
  }
  request.onreadystatechange = showSchedule;
  //使用DOM0级方法添加event handler,因为不是所有浏览器都支持DOM2;没有event对象,直接使用request对象
  request.open("GET",selectedTab + ".html",true);//返回HTML片段
  request.send(null);
}
function showSchedule() {
  if (request.readyState == 4) {
    if ((request.status >= 200 && request.status <= 300)|| request.status == 304) {
    //返回响应头部
    document.getElementById("content").innerHTML = request.getAllResponseHeaders();
    }else
    {
      document.getElementById("content").innerHTML =request.status;
    }
  }
}

输出结果显示全部的响应头部:
last-modified: Tue,11 Oct 2016 09:44:48 GMT content-type: application/x-javascript cache-control: max-age=2592000 expires: Thu,10 Nov 2016 09:45:10 GMT

4.2 CORS 涉及的头部

IE10及以上、Firefox 3.5+、 Safari 4+、 Chrome、 iOS 版 Safari 和 Android 平台中的 WebKit 都通过 XMLHttpRequest对象实现了对 CORS 的原生支持。只要在open()方法的URL中使用绝对定位即可实现CORS。一般推荐在同域中使用相对URL,在跨域时使用绝对URL。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

对于简单请求(GET和POST),浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段用来说明:本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段,如下:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。详细解释如下:
(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字段的值。

4.3 测试实例

把上例中的open()方法改成如下所示,URL指向其他域中的一个JavaScript文件,测试结果表明可以成功加载该文件。
//测试文件在本地主机localhost上
request.open("GET","https://sf-static.b0.upaiyun.com/v-57fcb48b/user/script/index.min.js"/selectedTab + ".html"/,true);

使用FireFox的FireBug观察上述请求的响应过程,对应的请求头部:Origin是http://localhost

响应头部:access-control-allow-origin:*代表任意源都可以访问。

跨域使用的XHR对象可以访问status和statusText属性,而且支持同步请求,虽然这没多大用处。限制是不能使用 setRequestHeader()设置自定义头部;不能发送和接收 cookie;调用 getAllResponseHeaders()方法总会返回空字符串。

5 特殊的IE: XDR对象实现跨域

对于XHR2,IE浏览器的支持是IE10以上。但是IE早在IE8时就推出了 XDomainRequest 对象进行跨域操作,一直沿用到IE10才被取代掉。因此在IE8,IE9中应该使用 XDomainRequest(XDR)来实现。XDR有以下几个特点:
1)cookie 不会随请求发送,也不会随响应返回。(和跨域XHR一样)
2)只能设置请求头部信息中的 Content-Type 字段。
3)不能访问响应头部信息。(和跨域XHR一样)
4)只支持 GET 和 POST 请求。

XDR 对象的使用方法与 XHR 对象非常相似,也是创建一个 XDomainRequest 的实例,调用 open()方法,再调用 send()方法。但与 XHR 对象的 open()方法不同, XDR 对象的 open()方法只接收两个参数:请求的类型和 URL。
所有 XDR 请求都是异步执行的,不能用它来创建同步请求(和XHR不同同)。请求返回之后,会触发 load 事件(和XHR同),如果失败(包括响应中缺少 Access-Control-Allow-Origin 头部)就会触发 error 事件。响应的数据也会保存在 responseText 属性中。

var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.open("get","http://www.somewhere-else.com/page/");
xdr.send(null);

在请求返回前调用 abort()方法可以终止请求:

xdr.abort(); //终止请求

与 XHR 一样, XDR 对象也支持 timeout 属性以及 ontimeout 事件处理程序
为支持 POST 请求, XDR 对象提供了 contentType 属性,用来表示发送数据的格式。该属性可以影响头部信息,在open()之后,send()之前使用。这个属性是通过 XDR 对象影响头部信息的唯一方式

6 跨浏览器的跨域解决方案

即使浏览器对 CORS 的支持程度并不都一样,但所有浏览器都支持简单的(非 Preflight 和不带凭据的)请求,因此有必要实现一个跨浏览器的方案。检测 XHR 是否支持 CORS 的最简单方式,就是检查是否存在 withCredentials 属性。再结合检测 XDomainRequest 对象是否存在,就可以兼顾所有浏览器了。

function createCORSRequest(method,url){
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr){
    xhr.open(method,url,true);
  } else if (typeof XDomainRequest != "undefined"){
    vxhr = new XDomainRequest();
    xhr.open(method,url);
  } else {
    xhr = null;
  }
  return xhr;
}

Firefox、 Safari 和 Chrome 中的 XMLHttpRequest 对象与 IE 中的 XDomainRequest 对象类似,都提供了共同的属性/方法如下:
 abort():用于停止正在进行的请求。
 onerror:用于替代 onreadystatechange 检测错误。
 onload:用于替代 onreadystatechange 检测成功。
 responseText:用于取得响应内容。
 send():用于发送请求。
以上成员都包含在 createCORSRequest()函数返回的对象中,在所有浏览器中都能正常使用。

var request = createCORSRequest("get","http://www.somewhere-else.com/page/");
if (request){
    request.onload = function(){
        //对 request.responseText 进行处理
    };
    request.send();
}

AJAX学习笔记2:XHR实现跨域资源共享CORS以及和JSONP的对比的更多相关文章

  1. 详解使用postMessage解决iframe跨域通信问题

    这篇文章主要介绍了详解使用postMessage解决iframe跨域通信问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. HTML5 Web缓存和运用程序缓存(cookie,session)

    这篇文章主要介绍了HTML5 Web缓存和运用程序缓存(cookie,session),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. HTML5 3D书本翻页动画的实现示例

    这是一款十分炫酷的HTML5 3D书本翻页动画,效果相对比较简单,拖拽鼠标模拟用手翻页,需要的朋友们下面随着小编来一起学习学习吧

  4. 使用postMessage让 iframe自适应高度的方法示例

    这篇文章主要介绍了使用postMessage让 iframe自适应高度的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. 关于h5中的fetch方法解读(小结)

    这篇文章主要介绍了关于h5中的fetch方法解读(小结),fetch身为H5中的一个新对象,他的诞生,是为了取代ajax的存在而出现,有兴趣的可以了解一下

  6. ios – 如何在Swift中手动为UIWebView设置Cookie

    我需要在swift中为webview设置一个cookie.我找到了一个解决方案,但它是针对objective-c的.如何在Swift中做到这一点?

  7. ios – UIPopoverController出现在错误的位置

    所以我花了一些时间寻找答案,但到目前为止还没有找到任何答案.我正在尝试从UIInputAccessoryView上的按钮呈现弹出窗口.UIBarButtonItem我想显示popover来自定制视图,所以我可以使用图像.我创建这样的按钮:当需要显示popover时,我这样做:但我得到的是:弹出窗口看起来很好,但它应该出现在第一个按钮上时出现在第二个按钮上.然后我发现了这个问题:UIBarButto

  8. 通过在iOS中处理cookie来维护会话信息

    我是iOS开发的新手.我正在使用NSURLSession来管理会话信息.下面是我用来调用任何服务器API的示例代码,我的申请流程是,如果没有登录–>登录(呼叫登录api)Else转到主屏幕并调用其他API.我的问题是,一旦从内存中删除应用程序,会话信息就不会被维护,我不得不再次调用Login.我的要求就像Facebook一样,用户只需登录一次,并且在下次应用程序启动时保持会话.编辑:我想我必须通过

  9. ios – 以http无效的自定义URL方案开头

    我在应用程序中使用了自定义URL方案.我成功地从safari重定向到我的应用程序.就像我已经制作了URL方案“appname”.请检查http://prntscr.com/2cjx0p.我需要使用像iosurlredirectfrommailtoapp这样的解决方案,但我不确定如何设置cookie.我发现我必须首先在我的应用程序中为服务器“http://myappname.com”设置一个cook

  10. 通过AFNetworking 2.0上传iOS图像

    我一直在寻找新的AFNetworking2.0上传图像的例子.但是我正在撞墙,无法弄清楚代码有什么问题.所以这是我使用的代码TIA解决方法我最终使用了多部分请求

随机推荐

  1. xe-ajax-mock 前端虚拟服务

    最新版本见Github,点击查看历史版本基于XEAjax扩展的Mock虚拟服务插件;对于前后端分离的开发模式,ajax+mock使前端不再依赖后端接口开发效率更高。CDN使用script方式安装,XEAjaxMock会定义为全局变量生产环境请使用xe-ajax-mock.min.js,更小的压缩版本,可以带来更快的速度体验。

  2. vue 使用 xe-ajax

    安装完成后自动挂载在vue实例this.$ajaxCDN安装使用script方式安装,VXEAjax会定义为全局变量生产环境请使用vxe-ajax.min.js,更小的压缩版本,可以带来更快的速度体验。cdnjs获取最新版本点击浏览已发布的所有npm包源码unpkg获取最新版本点击浏览已发布的所有npm包源码AMD安装require.js安装示例ES6Module安装通过Vue.use()来全局安装示例./Home.vue

  3. AJAX POST数据中文乱码解决

    前端使用encodeURI进行编码后台java.net.URLDecoder进行解码编解码工具

  4. Koa2框架利用CORS完成跨域ajax请求

    实现跨域ajax请求的方式有很多,其中一个是利用CORS,而这个方法关键是在服务器端进行配置。本文仅对能够完成正常跨域ajax响应的,最基本的配置进行说明。这样OPTIONS请求就能够通过了。至此为止,相当于仅仅完成了预检,还没发送真正的请求呢。

  5. form提交时,ajax上传文件并更新到&lt;input&gt;中的value字段

  6. ajax的cache作用

    filePath="+escape;},error:{alert;}});解决方案:1.加cache:false2.url加随机数正常代码:网上高人解读:cache的作用就是第一次请求完毕之后,如果再次去请求,可以直接从缓存里面读取而不是再到服务器端读取。

  7. 浅谈ajax上传文件属性contentType = false

    默认值为contentType="application/x-www-form-urlencoded".在默认情况下,内容编码类型满足大多数情况。在这里,我们主要谈谈contentType=false.在使用ajax上传文件时:在其中先封装了一个formData对象,然后使用post方法将文件传给服务器。说到这,我们发现在JQueryajax()方法中我们使contentType=false,这不是冲突了吗?这就是因为当我们在form标签中设置了enctype=“multipart/form-data”,

  8. 909422229_ajaxFileUpload上传文件

    ajaxFileUpload.js很多同名的,因为做出来一个很容易。我上github搜AjaxFileUpload出来很多类似js。ajaxFileUpload是一个异步上传文件的jQuery插件传一个不知道什么版本的上来,以后不用到处找了。语法:$.ajaxFileUploadoptions参数说明:1、url上传处理程序地址。2,fileElementId需要上传的文件域的ID,即的ID。3,secureuri是否启用安全提交,默认为false。4,dataType服务器返回的数据类型。6,error

  9. AJAX-Cache:一款好用的Ajax缓存插件

    原文链接AJAX-Cache是什么Ajax是前端开发必不可少的数据获取手段,在频繁的异步请求业务中,我们往往需要利用“缓存”提升界面响应速度,减少网络资源占用。AJAX-Cache是一款jQuery缓存插件,可以为$.ajax()方法扩展缓存功能。

  10. jsf – Ajax update/render在已渲染属性的组件上不起作用

    我试图ajax更新一个有条件渲染的组件。我可以确保#{user}实际上是可用的。这是怎么引起的,我该如何解决呢?必须始终在ajax可以重新呈现之前呈现组件。Ajax正在使用JavaScriptdocument.getElementById()来查找需要更新的组件。但是如果JSF没有将组件放在第一位,那么JavaScript找不到要更新的内容。解决方案是简单地引用总是渲染的父组件。

返回
顶部