JavaScript中的跨域学习

什么是跨域

来看看一个url地址:
http://www.baidu.com:8080/scripts/jquery.js
它包含如下几个部分:

  • 协议:http
  • 子域名:www.
  • 主域名:baidu.com
  • 端口号:8080
  • 请求的资源:scripts/jquery.js

当协议、子域名、主域名和端口号中任何一个不相同时都是不同域,不同域间相互请求资源称作跨域。

跨域技术

XHR对象通常只能访问与包含它的页面处于同一域中的资源。
W3C中的CORS的思想是:通过用户自定义请求头部来与服务器交互,服务器根据自定义的请求头部信息来决定是否要成功响应该请求,并在响应头部中进行相应的设置。即新增了一些请求头部信息,并按照策略返回相应的响应头以及所请求的资源。被请求的资源根据它认为合适的数据(用户代理、来源页面等)决定是否设置Access-Control-Allow-Origin头部。
具体来说,首先是请求头部中设置一个头部信息:Origin:url(请求页面的源信息);服务器收到请求后,查看是否接受这个请求,如果接受,则在响应头部中设置:Access-Control-Allow-Origin:相同的源信息(如果是公共资源,则发送*,表示服务器接受来自任何站点的跨站请求)。即通过Origin和
如果无该头部或源信息不匹配则浏览器会驳回该请求。

一. 根据浏览器来划分跨域技术

IE中的CORS

IE8对W3C中的跨域请求进行了部分实现。是通过XDR(XDomainRequest)对象实现跨域访问的。它的用法如下:

var xdr = new XDmainRequest();
xdr.onload=function(){
    alert(xdr.responseText);
};
xdr.onerror=function(){//
};
xdr.open("get","http://www.someothersite.com/page/");
xdr.send(null);

可看出与XHR用法上的区别是open方法只包含两个参数:方式和url,它只支持异步请求返回后会触发load事件(XHR也可以用load事件代替readyStateChange事件检测是否响应成功)
没有办法确定响应的状态码,如果响应未成功则会触发error事件,应该为xdr指定error事件处理程序,否则即使失败了也没有任何提示。
此外还有如下几个区别:

  • cookie不会同请求发送和返回;
  • 只能设置请求头部的Conten-Type字段(表示发送数据的格式,post中常用) xdr.Conten-Type=”“
  • 不能访问响应头部(意味着无法获取状态码)
  • 只支持GET和POST(而XHR还支持其他的)

其他浏览器的实现

可使用原生的XHR,url使用绝对路径即可。可支持同步请求。获取状态码。但也有以下区别:

  • 不能使用setRequestHeader()设置头部
  • 不能发送和接收cookie
  • 调用getAllRequestHeaders()返回空字符串

由于跨源和同源请求都使用了相同的接口,因此为消除歧义和对访问头部和cookie的限制,建议同源的就使用相对路径。

二. 简单请求和Preflight请求

以下内容针对XHR对象。
preflight即预请求,是相对于前面所述的简单请求,即通过使用 Origin 和 Access-Control-Allow-Origin 就可以完成最简单的跨站请求。简单请求是指:

  • 只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-Type)只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。

  • 不会使用自定义请求头

服务器如果认为接收该源页面的请求,则直接在响应中设置头部信息 Access-Control-Allow-Origin并发送响应数据。
而预请求允许用户自定义头部、使用GET、POST之外的方法。它要求必须先发送一个 OPTIONS 请求(OPTIONS 是 HTTP/1.1 里的方法,用来获取更多服务器端的信息,类似GET等是一种请求方式)给目的站点,来查明这个跨站请求对于目的站点是不是安全可接受的。这样做,是因为跨站请求可能会对目的站点的数据造成破坏(其他请求方式)。

具备以下条件就被会当做预请求来处理:

  • 请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为application/x-www-form-urlencoded, multipart/form-data 或者 text/plain以外的数据类型。比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。
  • 使用自定义请求头(比如添加诸如 X-PINGOTHER)。

通过这种方式,先发送一个预请求,服务器接收到后判断是否接受请求,并会在响应头部中告知允许的请求方式、自定义头部等信息。之后浏览器根据预请求响应再发送一个真正的请求,之后服务器再返回响应数据。

OPTIONS请求头部中包含如下信息:
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:告诉浏览器,本次“预请求”的响应结果有效时间是多久。在这段时间内,浏览器在处理针对该服务器的跨站请求,都可以无需再发送“预请求”,只需根据本次结果进行判断处理。

带凭据的请求

默认情况下,跨域请求浏览器是不会发送凭证信息的(如cookie、HTTP认证、客户端SSL证明等)。通过设置xhr对象的属性withCredentials = true来发送带凭据的请求,这样cookie等就可以随请求一起发送了。
这个也是会根据xhr请求有无自定义头部等方式来判断需不需要发送预请求。
如果在响应头部中没有字段Access-Control-Allow-Credentials: true,那么浏览器将不会把响应结果传递给发出请求的脚步程序,responseText中将会是空字符串,以保证信息的安全。并且会触发error事件。

特别注意: 给一个带有withCredentials的请求发送响应的时候,服务器端必须指定允许请求的域名,不能使用’*’.如果响应头是这样的:Access-Control-Allow-Origin: * ,则响应会失败. 在这个例子里,因为Access-Control-Allow-Origin的值是http://foo.example这个指定的请求域名,所以客户端把带有凭证信息的内容被返回给了客户端. 另外在响应中更多的cookie信息也被创建了.

注意:IE10及之前的版本不支持预请求和带凭据的请求。

慕课网中的跨域讲解:

1. jsonp

JSONP是当前非常流行的跨域方式,是一种非正式的传输协议,它是利用了<\script>标签没有跨域限制的“漏洞”(历史遗迹)来达到与第三方通讯的目的。客户端可以跨域请求一个JS文件,在客户端创建一个回调函数,参数应该就是期待服务端返回的JSON数据,在客户端动态地生成请求脚本(主要是想设置script的src属性值,其中带参数回调函数名)。在远程服务器上设法把客户所需要的数据装进js格式的文件里(实际上所有包含src属性的标签都没有跨域限制)。客户端成功调用请求到的JS文件也就获取到了所需要的JSON数据。
该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

实际上就是一种扩展的支持在用户定义函数中包含返回数据的能力。这种方法依赖于必须接受一个回调函数的名字作为参数。

具体做法是:

  • 本站脚本创建一个<\script>元素,地址指向第三方的API网址,形如: <\script
    src=”http://www.anthersite.net/api?param1=1”><\/script>
  • 并提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递)。
  • 第三方产生的响应用回调函数把json格式的数据作为参数包装起来(故称之为jsonp,即json padding),
    形如:callback({“name”:”hax”,”gender”:”Male”})。
  • 这样浏览器在加载跨域的js文件后(<\script>标签相当于把src中的文件代码嵌入到了当前区域中),会立即执行callback函数(之前在本地脚本中已经定义了该函数),这是callback函数中的参数是服务器端包装好的Json数据,这样就可以根据之前定义好的callback函数解析json数据了。
  • 还有一个问题是怎么让服务器端知道用来包裹数据的回调函数的名字呢?可以把函数名作为url后面的参数传递过去,由服务器端动态生成js脚本。
    jsonp只支持GET请求,不支持POST请求。
    jQuery中的$.ajax()方法也支持jsonp。
    前端JS脚本:
$("#search").click(function(){ 
        $.ajax({ 
            type: "GET",    
            url: "http://127.0.0.1:8000/ajaxdemo/serverjsonp.php?number=" + $("#keyword").val(),
            dataType: "jsonp",//
            jsonp: "callback",  //
            success: function(data) {
                if (data.success) {
                    $("#searchResult").html(data.msg);
                } else {
                    $("#searchResult").html("出现错误:" + data.msg);
                }  
            },
            error: function(jqXHR){     
               alert("发生错误:" + jqXHR.status);  
            },     
        });
    });

后端GET这个值为callback的$jsonp对象,通过这个名字拼接json字符串。调用数据服务器的返回值需要用$jsonp.’()’的方式连接起来, ‘.’号在php中用于连接字符串。

$jsonp = $_GET["callback"];

$result = $jsonp . '({"success":true,"msg":"找到员工:员工编号:' . $value["number"] .',员工姓名:' . $value["name"] .',员工性别:' . $value["sex"] . ',员工职位:' . $value["job"] . '"})';

echo $result; //返回的数据及前端的data

上段参考知乎回答:http://www.zhihu.com/question/19966531
说说JSON和JSONP,也许你会豁然开朗,含jQuery用例

2.XHR2级对象

只需在被请求的服务器端中进行下列头部设置:

<?php header("Control-Allow-Origin: *");//允许的跨域源地址,*表示允许所有 header("Control-Allow-Origin-Methods: POST,GET"); ?>

而无需修改客户端。

此外还有一些别的方法,比如document.domain来实现框架间的交互,这篇文档中有详细的解释:
js中几种实用的跨域方法原理详解
参考资料:Mozilla技术文档中对跨域请求的讲解

你可能感兴趣的:(JavaScript,跨域)