XMLHttpRequest Level 2的跨域功能

XMLHttpRequest Level 2的功能已经大幅提升了,
参见:http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html

我们知道,受到浏览器”同域限制“制约,以前的xhr对象是无法完成跨域请求的,而现在只需在Server端做一个访问控制,Client端再用xhr对象请求就行了,一般情况下Client并不需要设置,当然还有些相关的方法属性可供使用的,比如setRequestHeader,withCredentials。
服务端设置参见: https://developer.mozilla.org/en-US/docs/Server-Side_Access_Control

下面我们简单的做个demo
Server端代码(PHP):

1
2
3
4
5
6
7
8
9
10
11
12
13
if($_SERVER['HTTP_ORIGIN'] == "http://www.cssass.com")
{
header('Access-Control-Allow-Origin: http://www.cssass.com');
header('Content-Type: text/plain');
if($_SERVER['REQUEST_METHOD'] == "GET")
{
$arr = array(
'id' => '1',
'name' => 'XMLHttpRequest Response'
);
echo json_encode($arr);
};
};

http响应头设置参见:https://developer.mozilla.org/en-US/docs/HTTP_access_control#The_HTTP_response_headers

Client端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<meta charset="UTF-8" />
<title>XMLHttpRequest 2演示</title>
<a href="javascript:;" onclick="get_xhr2()">获取数据!(xhr2)</a>
<script>
function get_xhr2(){
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.hzmilo.com/me.php?r=/xhr2/send'); //POST也支持
xhr.onload = function(e) { //绑定onload
var data = JSON.parse(this.response); //json解析
alert(data.name);
}
xhr.send();
};
</script>

正如你所知,ie下是不支持XMLHttpRequest Level 2的,不过ie8引入了自己的跨域对象XDomainRequest。(同时ie8下的xhr对象也引入了 timeou属性t和ontimeout方法)
所以我们做下兼容(不支持ie6,ie7):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!doctype html>
<meta charset="UTF-8" />
<title>XMLHttpRequest 2演示</title>
<a href="javascript:;" onclick="get_xhr2()">获取数据!(xhr2)</a>
<script>
function get_xhr2(){
var xr,xrName;
if(window.XDomainRequest){
xr = new XDomainRequest();
xrName = "XDomainRequest";
}else{
xr = new XMLHttpRequest();
xrName = "XMLHttpRequest";
}
xr.open('GET', 'http://www.hzmilo.com/me.php?r=/xhr2/send/&xr='+xrName);
xr.onload = function(e) {
var data = JSON.parse(this.responseText); //json解析
alert(data.name);
}
xr.send();
};
</script>

接下来,我们来了解下JSONP这种在同域限制的情况下实现跨域请求(GET)的实现过程。
原理其实就是:以script标签的形式在页面中放置一个请求地址,该请求地址返回的数据格式为:

jsonp1354513528560({“id”:”2″,”name”:”JSON with Padding”})

如果jsonp1354513528560是一个预先定义好的JS方法,那么获取其参数(我们实际需要获取的数据)就顺理成章了。

以下是Client端的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
(function(){
//jsonp的具体实现
var randomNum = (new Date).getTime(),
callName = null,
sendScriptRequest = function(url,id){
//将请求地址以script标签形式插入到页面。(注定是GET请求)
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.id = id;
script.src = url;
script.charset = 'utf-8';
head.appendChild(script);
},
buildTempFunction = function(callback){
//创建一个全局方法,并将方法名当做请求地址的一个参数
callName = "jsonp" + randomNum++;
window[ callName ] = function(data){
callback(data);
window[ callName ] = undefined;
try{
delete window[ callName ];
//var jsNode = document.getElementById(callName);
//jsNode.parentElement.removeChild(jsNode); //执行全局方法后,将script标签删除
} catch(e){}
};
return callName;
},
$jsonp = function(url,params){
//生成GET请求地址
params.callback = buildTempFunction(params.callback);
url += (url.indexOf("?")>0 ) ? "" : "?" ;
for(var i in params)
url += "&" + i + "=" + params[i];
sendScriptRequest(url,callName);
};
//对外开放接口:$jsonp
/**
* @$jsonp JSONP方法
* @param {String} url 请求地址
* @param {Object} params 请求参数
*/
if (!window.$jsonp)
window.$jsonp = $jsonp;
})();

Server端很简单,只需拼接输出一个js的执行方法即可。

1
2
3
4
5
6
$jsonp = $_GET['callback']; //请求端传递的callback参数,作为输出的方法名
$arr = array(
'id' => '2',
'name' => 'JSON with Padding'
);
echo $jsonp, '(', json_encode($arr), ')';

演示:

1
2
3
4
5
6
7
8
9
10
11
12
<!doctype html>
<meta charset="UTF-8" />
<title>jsonp演示</title>
<script src="/public/js/extend.js"></script>
<a href="javascript:;" onclick="get_jsonp()">获取数据!(jsonp)</a>
<script>
function get_jsonp(){
$jsonp('http://www.hzmilo.com/me.php?r=/xhr2/send2',{"id":1001},function(data){
alert(data.name);
});
};
</script>

因为大多数网站不会开启server端的访问控制,所以xhr2目前比较适用于自己所属的几个域名下网站的连结,并且放弃ie6、7。
JSONP应用倒是很普遍,很多网站开放API的时候,也会用jsonp的形式给js提供接口,这样一来,使得ajax也能直接调用到API,当然只限一些普通的无需授权即用的接口。

然而很多网站并未开放API, 也未在服务端设置callback之类的参数,而我们也不想自己写server端代码去抓取。
那么我们可以试试中间代理:http://developer.yahoo.com/yql/

演示:我们抓取一下我很喜欢的一个电影网站(dianying.fm)的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<meta charset="UTF-8" />
<title>yql演示</title>
<script src="/public/js/extend.js"></script>
<a href="javascript:;" onclick="get_yql()">获取数据!(jsonp代理)</a>
<script>
function get_yql(){
$jsonp('http://query.yahooapis.com/v1/public/yql',{
q: "select * from json where url=\"http://dianying.fm/reflect/cannes/e30=/2\"",
format: "json"},function(data){
alert(data.query.results.json.html);
});
};
</script>