Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ajax-3 跨域资源共享 #14

Open
lazyken opened this issue Mar 29, 2018 · 0 comments
Open

Ajax-3 跨域资源共享 #14

lazyken opened this issue Mar 29, 2018 · 0 comments

Comments

@lazyken
Copy link
Owner

lazyken commented Mar 29, 2018

CORS

XHR对象只能访问与包含它的页面位于同一个域中的资源,这种安全策略可以预防某些恶意行为,但是合理的跨域请求也是至关重要的。
CORSCross-Origin Resource Sharing,跨域资源共享)定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS基本思想是使用自定义的HTTP头部让浏览器与服务器沟通,从而决定请求或者响应是应该成功还是失败。
比如一个简单的使用GET或POST发送的请求,没有自定义的头部,给他加上Origin头部,其中包含请求页面的源信息(协议、域名、端口),服务器可以根据这个头部信息来决定是否响应,例:Origin:http://www.nczonline.net
如果服务器认为请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息。例:Access-Control-Allow-Origin:http://www.nczonline.net
如果没有这个头部,或者有但是源信息不匹配,浏览器就会驳回请求。另外请求和响应都不包含cookie信息。

IE对CORS的实现

微软在IE8中引入了XDR(XDomainRequest)类型,它与XHR对象类似,但能实现安全可靠的跨域通信。XDR与XHR的不同之处:

  1. Cookie不会随请求发送,也不会随响应返回;
  2. 只能设置请求头部信息中的Content-Type字段;
  3. 不能访问响应头部信息;
  4. 只支持GET和POST请求。

被请求的资源可以根据它认为合适的任意数据(用户代理、来源页面等)来决定是否设置Access-Control-Allow-Origin头部。作为请求的一部分,Origin头部的值表示请求的来源域,以便远程资源明确地识别XDR请求。
XDR对象的使用,也是先创建一个XDomainRequest的示例,调用open()方法,再调用send()方法。XDR的open()方法只接收2个参数,请求类型和URL。所有的XDR请求都是异步执行的。
请求返回之后会触发load事件,响应的数据也会保存在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事件处理程序:

var xdr = new XDomainRequest();
xdr.onload = function () {
  alert(xdr.reponseText);
}
xdr.onerror = function () {
  alert("An error occurred.")
}
xdr.timeout = 1000;
xdr.ontimeout = function () {
  alert("Request took too long.")
}
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

为支持POST请求,XDR对象提供了contentType属性,用来表示发送数据的格式:

var xdr = new XDomainRequest();
xdr.onload = function () {
  alert(xdr.reponseText);
};
xdr.onerror = function () {
  alert("An error occurred.")
};
xdr.open("post","http: //www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("namel=value1&name2=value2");

这个属性是通过XDR对象影响头部信息的唯一方式。

其他浏览器对CORS的实现

Firefox3.5+、Safari4+、Chrome、iOS版Safari和Android平台中的WebKit都通过XMLHttpRequest对象实现了对CORS的原生支持。使用XHR对象在open()方法中传入绝对URL即可。

var xhr = createXHR();
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
      alert(xhr.responseText);
    } else {
      alert("Request was unsuccessful:" + xhr.status);
    }
  }
};
xhr.open("get", "http://www.somewhere-else.com/page", true); 
xhr.send(null);

跨域的XHR对象可以访问status和statusText属性,而且还支持同步请求。但跨域XHR对象也有一些限制:
1.不能使用setRequestHeader()设置自定义头部。
2.不能发送和接收cookie
3.调用getAllResponseHeaders()方法总会返回空字符串
总结:访问本地资源用相对URL,访问远程资源使用绝对URL。

跨浏览器的CORS

检测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") {
    xhr = new XDomainRequest();
    xhr.open(method, url);
  } else {
    Xhr = null;
  }
  return xhr;
}
var request = createCORSRequest("get", "http://www.somewhere-else.com/page/");
if (request) {
  request.onload = function () {
    //对request.resopnseText 进行处理
  };
  request.send();
}

Firefox、Safari和Chrome中的XMLHttpRequest对象与IE中的XDomainRequest对象类似,他们共同的属性/方法如下:
1.abort():用于停止正在进行的请求;
2.Onerror:用于替代onreadystatechange检测错误;
3.Onload用于替代onreadystatechange检测成功。
4.responseText:用于取得响应内容。
5.Send():用于发送请求。
以上成员都包含在createCORSRequest()函数返回的对象中,在所以浏览器都能正常使用。

其他跨域技术

图像ping

一个网页可以从任何网页中加载图像,不用担心跨不跨域。这也是在线广告跟踪浏览量的主要方式。也可以动态地创建图像,使用它们的onload和onerror事件处理程序来确定是否接收到了响应。
动态创建图像经常用于图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候接收到的。例子:

var img = new Image();
img.onload = img.onerror = function () {
  alert("Done!");
};
img.src = "http://www.example.com//test?name=Nicholas";

这里创建了一个Image的实例,然后将onload和onerror事件处理程序指定为同一个函数。这样无论是什么响应,只要请求完成,就能得到通知。请求从设置src属性那一刻开始,而这个例子在请求中发送了一个name参数。
图像Ping最常用于跟踪用户点击页面或动态广告曝光次数。它也有两个缺点:
1.只能发送get请求,
2.无法访问服务器的响应文本。
所以只能用于浏览器与服务器间的单向通信。

JSONP

我们发现,web页面调用js文件时不存在跨域问题(如我们在页面中引入百度地图api)

<script type=”text/javascript” src=”http://api.map.baidu.com/api?v=2.0&ak=e3ZohdqyB0RL98hFOiC29xqh”></script>

总结发现,凡是拥有”src”属性的标签都拥有跨域能力,如<script><img><iframe>等。
JSONP的一个要点:允许客户端传一个callback参数给服务器,然后服务器返回数据时会用这个callback参数作为函数名,包裹住JSON数据返回客户端,客户端执行返回函数。
JSONP客户端具体实现:
①直接执行返回函数
本地服务器local.com下有个jsonp.html页面代码如下:

<head>
    <title></title>
    <script type="text/javascript" src="http://remote.com/remote.js"></script>
</head>
<body>
</body>

远程服务器remote.com下有个remote.js文件代码如下:

alert('我是远程文件');

运行jsonp.html,会弹出“我是远程文件”,显示跨域调用成功。
②客户端执行回调函数
jsonp.html页面代码如下:

<head>
    <title></title>
    <script type="text/javascript">
    var localHandler = function(data){
        alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
    };
    </script>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
</body>

remote.js文件代码如下:

localHandler({"result":"我是远程js带来的数据"});

运行jsonp.html,会弹出“我是远程js带来的数据”,显示跨域调用成功。
③客户端传递回调函数
jsonp.html页面代码如下:

<head>
  <title></title>
  <script type="text/javascript">
    // 得到航班信息查询结果后的回调函数
    var flightHandler = function (data) {
      alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票' + data.tickets + '张。');
    };
    // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
    var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
    // 创建script标签,设置其属性
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script);
  </script>
</head>
<body>
</body>

flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html

flightHandler({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});

④4)jquery封装了③中的代码,以方便客户端使用。

<head>
  <title>Untitled Page</title>
  <script type="text/javascript" src=jquery.min.js></script>
  <script type="text/javascript">
    jQuery(document).ready(function () {
      $.ajax({
        type: "get",
        async: false,
        url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
        dataType: "jsonp",
        //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
        jsonp: "callback",
        //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"? ",jQuery会自动为你处理数据
        jsonpCallback: "flightHandler",
        success: function (json) {
          alert('您查询到航班信息:票价:' + json.price + ' 元,余票:' + json.tickets + '张。');
        },
        error: function () {
          alert('fail');
        }
      });
    });
  </script>
</head>
<body>
</body>

示例中没有定义flightHandler函数,但代码成功运行了。
这是因为jquery自动把success属性方法作为了回调函数。
JSONP是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,此时除了放弃JSONP调用外,没有办法追究。另外要确定JSONP请求是否失败也不容易。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant