转自OSChina,
原文: http://www.oschina.net/translate/ajax-cross-origin-http-request
背景
跨源HTTP请求(也称跨域AJAX请求)是大多数Web开发人员可能遇到的一个问题,根据同源策略,浏览器将限制客户端的JavaScript在一个安全沙箱内,通常JS不能直接同一台不同的域的远程服务器通信。在过去,开发者们创造了许多解决方法以实现跨域资源请求,常用的方法如下:
-
使用Flash/Silverlight或服务器端“代理”来与远程通讯
-
带填充JSON (JSONP).
-
在iframe中嵌入远程服务器并通过fragment或window.name通信,参考这里。
如此等等..
这些解决方法或多或少都有问题,比如使用JSONP时若只是简单的“eval”将导致安全漏洞,#3虽然能用,但两个域间必须依据严格的协议,恕我直言它既不灵活也不优雅
W3C已经引入了跨域资源共享 (CORS)作为能够解决该问题并提供安全、灵活以及推荐标准的解决方案。
|
|
机制
从较高的层次来看我们可以简单认为CORS 是介于 域A客户端 的AJAX调用 和一个托管在域B的页面 之间的契约, 一个典型的跨源
请求或者响应将会是这样:
域 A 的 AJAX 请求头
Host DomainB.com User-Agent Mozilla/5.0 (Windows NT
6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0 Accept
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
Accept-Language en-us; Accept-Encoding gzip,
deflate Keep-Alive 115
Origin http://DomainA.com
域 B 的 响应头
Cache-Control private /> Content-Type application/json;
charset=utf-8
Access-Control-Allow-Origin
DomainA.com Content-Length 87
Proxy-Connection Keep-Alive Connection
Keep-Alive
我上面标记的蓝色部分是关键实现, "Origin" 请求头表示 跨源请求 或者 预检请求 源于哪里,
"Access-Control-Allow-Origin" 请求头 表示这个页面允许来自域A 的请求(其值为 *
表示允许任何域的远程请求)。
像我上面提到的,W3 建议浏览器在提交实际跨源HTTP 请求前,实现“预检请求”,
简而言之,就是一个HTTP OPTIONS 请求:
OPTIONS DomainB.com/foo.aspx HTTP/1.1
如果 foo.aspx 支持 OPTIONS HTTP 指令, 它可能会像下面这样返回响应:
HTTP/1.1 200 OK Date: Wed, 01 Mar 2011 15:38:19 GMT
Access-Control-Allow-Origin: http://DomainB.com Access-Control-Allow-Methods:
POST, GET, OPTIONS, HEAD Access-Control-Allow-Headers:
X-Requested-With Access-Control-Max-Age: 1728000
Connection: Keep-Alive Content-Type:
application/json
只有满足在响应中包含 "Access-Control-Allow-Origin" , 并且其值为 "*"
或者包含提交CORS请求的域,这些强制条件的浏览器才能提交正式的跨域请求, 并在 预检结果缓存” 中缓存请求结果 。 |
|
|
实现
让我们看一下服务器端代码,例子如下(ASP.NET和PHP)
ASP.NET (C#)
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 |
protected void Page_Load( object sender, EventArgs e)
{
String data = String.Empty;
String returnJSONStr = String.Empty;
switch (Request.HttpMethod)
{
case "GET" :
data = Request.QueryString[ "Data" ];
returnJSONStr = "{\"Data\":\"Hi remote friend, you tried to passed me data: *" + data + "* through HTTP GET.\"}" ;
break ;
case "POST" :
data = Request.Form[ "Data" ];
returnJSONStr = "{\"Data\":\"Hi remote friend, you tried to POST some mock data: *" + data + "* to me.\"}" ;
break ;
case "OPTIONS" :
break ;
default :
returnBadRequestResponse();
break ;
}
if (String.IsNullOrEmpty(data))
returnBadRequestResponse();
else
{
Response.AddHeader( "Access-Control-Allow-Origin" , "*" );
Response.ContentType = "application/json" ;
Response.Write(returnJSONStr);
}
}
private void returnBadRequestResponse()
{
Response.StatusCode = 400;
Response.ContentType = "application/json" ;
Response.Write( "{\"Error\":\"Bad HTTP request type!\"}" );
}
|
PHP
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 |
if (isset($[ "Data" ]))
{
$method = $_SERVER [ ‘REQUEST_METHOD‘ ];
$data = "" ;
if ( $method == "POST" )
{
$data = $_POST [ "Data" ];
$fakeData = new FakeData();
$fakeData ->Data= "Hi remote friend, you tried to POST some mock data: *" +data+ "* to me." ;
$fakeData ->Time= new DateTime( "now" );
}
elseif ( $method == "GET" )
{
$fakeData = new FakeData();
$fakeData ->Data= "Hi remote friend, you tried to passed me data: *" +data+ "* through HTTP GET." ;
$fakeData ->Time= new DateTime( "now" );
}
else
{
RaiseError();
}
header( ‘Content-type: application/json‘ );
$jsonStr = json_encode( $fakeData );
echo ( $jsonStr );
}
else
{
RaiseError();
}
function RaiseError()
{
http_send_status(405);
header( "Status: 405 Method Not Allowed" );
}
class FakeData
{
public $Data ;
public $Time ;
}
|
客户端AJAXY发起请求代码:
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 |
var cor = null ;
if (window.XMLHttpRequest) {
cor = new XMLHttpRequest();
}
else {
alert( "Your browser does not support Cross-Origin request!" );
return ;
}
cor.onreadystatechange = function () {
if (cor.readyState == 4) {
document.getElementById( ‘lbl‘ ).innerHTML = cor.responseText;
}
};
var data = ‘Some fake data‘ ;
if (method == ‘POST‘ ) {
cor.withCredential = "true" ;
cor.setRequestHeader( ‘Content-Type‘ , ‘application/x-www-form-urlencoded‘ );
cor.send( ‘Data=‘ + data);
}
else if (method == ‘GET‘ ) {
cor.withCredential = "true" ;
cor.send( null );
}
|
JS代码适用于所有主流浏览器(IE8+, FF 3.6+, Chrome
8+),我没有用IE8所采用的XDomainObject,因为 IE8+, FF and
Chrome,
Safari等浏览器支持XMLHTTP请求。而且XDomainObject(XDR)似乎有很多限制(参考: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx)
结论
跨源资源共享为网站开发人员实现跨源通信提供了一个安全,灵活,标准的方案。也许是时候摈弃像JSONP,Flash,Silverlight,server
bridge以及window.name等等并不是很实用的方法。
参考资料
|
[转]AJAX 跨源 HTTP 请求,布布扣,bubuko.com