跨域問題出現原因和解決方案
出現原因
- 【出現原因】什麼是跨域以及產生原因
解決方案
- 【策略一】Jsonp 需要目標伺服器配合一個callback函式
JSONP(JSON with Padding)是一個非官方的協議,它允許在伺服器端整合Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。
Json+padding(內填充),顧名思義,就是把JSON填充到一個盒子裡,它的基本思想是,網頁通過新增一個
<script>
元素,向伺服器請求JSON資料,這種做法不受同源政策限制;伺服器收到請求後,將資料放在一個指定名字的回撥函式裡傳回來。
首先,網頁動態插入<script>
元素,由它向跨源網址發出請求。
//Js 客戶端 方法一
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script type="text/javascript">
function jsonpCallback(result) {
//alert(result);
for(var i in result) {
alert(i+":"+result[i]);//迴圈輸出a:1,b:2,etc.
}
}
var JSONP=document.createElement("script");
JSONP.type="text/javascript";
JSONP.src="http://crossdomain.com/services.php?callback=jsonpCallback";
document.getElementsByTagName("head")[0].appendChild(JSONP);
</script>
//Js 客戶端 方案二
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script type="text/javascript">
function jsonpCallback(result) {
alert(result.a);
alert(result.b);
alert(result.c);
for(var i in result) {
alert(i+":"+result[i]);//迴圈輸出a:1,b:2,etc.
}
}
</script>
<script type="text/javascript" src="http://crossdomain.com/services.php?callback=jsonpCallback"></script>
//Jq 客戶端 方案一
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$.getJSON("http://crossdomain.com/services.php?callback=?",
function(result) {
for(var i in result) {
alert(i+":"+result[i]);//迴圈輸出a:1,b:2,etc.
}
});
</script>
//Jq 客戶端 方案二
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$.ajax({
url:"http://crossdomain.com/services.php",
dataType:'jsonp',
data:'',
jsonp:'callback',
success:function(result) {
for(var i in result) {
alert(i+":"+result[i]);//迴圈輸出a:1,b:2,etc.
}
},
timeout:3000
});
</script>
//Jq 客戶端 方案三
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$.get('http://crossdomain.com/services.php?callback=?', {name: encodeURIComponent('tester')}, function (json) { for(var i in json) alert(i+":"+json[i]); }, 'jsonp');
</script>
<?php
//服務端返回JSON資料
$arr=array('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);
$result=json_encode($arr);
//echo $_GET['callback'].'("Hello,World!")';
//echo $_GET['callback']."($result)";
//動態執行回撥函式
$callback=$_GET['callback'];
echo $callback."($result)";
- 【策略二】通過修改document.domain來跨子域
將子域和主域的document.domain設為同一個主域.前提條件:這兩個域名必須屬於同一個基礎域名!而且所用的協議,埠都要一致,否則無法利用document.domain進行跨域
主域相同的使用document.domain
- 【策略三】使用window.name來進行跨域
.window.name+iframe 需要目標伺服器響應window.name,window物件有個name屬性,該屬性有個特徵:即在一個視窗(window)的生命週期內,視窗載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的許可權,window.name是持久存在一個視窗載入過的所有頁面中的!
javascript
window.name = data;
//接著,子視窗跳回一個與主視窗同域的網址。
location = 'http://parent.url.com/xxx.html';
//然後,主視窗就可以讀取子視窗的window.name了
var data = document.getElementById('iframe').contentWindow.name;
優點:window.name容量很大,可以防止非常長的字串;
缺點:必須監聽子視窗window.name屬性的變化,會影響網頁效能。
- 【策略四】跨文件訊息傳輸window.postMessage上面兩種方法都屬於破解,HTML5為解決這個問題,引入一個全新的API:跨文件訊息傳輸Cross Document Messaging。
下一代瀏覽器都將支援這個功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。
Facebook已經使用了這個功能,用postMessage支援基於web的實時訊息傳遞。使用方法:otherWindow.postMessage(message, targetOrigin);
otherWindow: 對接收資訊頁面的window的引用。可以是頁面中iframe的contentWindow屬性;window.open的返回值;
通過name或下標從window.frames取到的值。
message: 具體的資訊內容,string型別。
targetOrigin: 接受訊息的視窗的源(origin),即“協議+域名+埠”。也可以設為“*”,表示不限制域名,向所有視窗傳送。
message事件的事件物件event,提供一下三個屬性:
(1).event.source:傳送訊息的視窗
(2).event.origin:訊息發向的網站
(3).event.data:訊息內容
// a.com/index.html中的程式碼
<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com'; // 若寫成'http://b.com/c/proxy.html'效果一樣
// 若寫成'http://c.com'就不會執行postMessage了
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
// b.com/index.html中的程式碼
<script type="text/javascript">
window.addEventListener('message', function(event){
// 通過origin屬性判斷訊息來源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 彈出"I was there!"
alert(event.source); // 對a.com、index.html中window物件的引用
// 但由於同源策略,這裡event.source不可以訪問window物件
}
}, false);
</script>
【策略五】通過CORS解決AJAX跨域
CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。它是W3C標準,是跨源AJAX請求的根本解決方法。相比JSONP只能發GET請求,CORS允許任何型別的請求。
定義:CORS其實出現時間不短了,它在維基百科上的定義是:跨域資源共享(CORS)是一種網路瀏覽器的技術規範,它為Web伺服器定義了一種方式,允許網頁從不同的域訪問其資源。而這種訪問是被同源策略所禁止的。CORS系統定義了一種瀏覽器和伺服器互動的方式來確定是否允許跨域請求。 它是一個妥協,有更大的靈活性,但比起簡單地允許所有這些的要求來說更加安全。而W3C的官方文件目前還是工作草案,但是正在朝著W3C推薦的方向前進。簡言之,CORS就是為了讓AJAX可以實現可控的跨域訪問而生的。
以往的解決方案:
以前要實現跨域訪問,可以通過JSONP、Flash或者伺服器中轉的方式來實現,但是現在我們有了CORS。
CORS與JSONP相比,無疑更為先進、方便和可靠。
1、 JSONP只能實現GET請求,而CORS支援所有型別的HTTP請求。
2、 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得資料,比起JSONP有更好的錯誤處理。
3、 JSONP主要被老的瀏覽器支援,它們往往不支援CORS,而絕大多數現代瀏覽器都已經支援了CORS(這部分會在後文瀏覽器支援部分介紹)。
【策略六】通過設定Access-Control-Allow-Origin
在被請求的Response header中加入 :
// 指定允許其他域名訪問 header('Access-Control-Allow-Origin:*'); // 響應型別 header('Access-Control-Allow-Methods:POST'); // 響應頭設定 header('Access-Control-Allow-Headers:x-requested-with,content-type');
就可以實現ajax POST跨域訪問了。
客戶端請求:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title> 跨域測試 </title> <script src="//code.jquery.com/jquery-1.11.3.min.js"></script> </head> <body> <div id="show"></div> <script type="text/javascript"> $.post("http://www.server.com/server.php",{name:"fdipzone",gender:"male"}) .done(function(data){ document.getElementById("show").innerHTML = data.name + ' ' + data.gender; }); </script> </body> </html>
伺服器端處理:
<?php $ret = array( 'name' => isset($_POST['name'])? $_POST['name'] : '', 'gender' => isset($_POST['gender'])? $_POST['gender'] : '' ); header('content-type:application:json;charset=utf8'); header('Access-Control-Allow-Origin:*'); header('Access-Control-Allow-Methods:POST'); header('Access-Control-Allow-Headers:x-requested-with,content-type'); echo json_encode($ret); ?>
Access-Control-Allow-Origin:* 表示允許任何域名跨域訪問
如果需要指定某域名才允許跨域訪問,只需把Access-Control-Allow-Origin:*改為Access-Control-Allow-Origin:允許的域名
例如:header(‘Access-Control-Allow-Origin:http://www.client.com‘);如果需要設定多個域名允許訪問,這裡需要用PHP處理一下
例如允許 www.client.com 與 www.client2.com 可以跨域訪問伺服器端處理:
<?php $ret = array( 'name' => isset($_POST['name'])? $_POST['name'] : '', 'gender' => isset($_POST['gender'])? $_POST['gender'] : '' ); header('content-type:application:json;charset=utf8'); $origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : ''; $allow_origin = array( 'http://www.client.com', 'http://www.client2.com' ); if(in_array($origin, $allow_origin)){ header('Access-Control-Allow-Origin:'.$origin); header('Access-Control-Allow-Methods:POST'); header('Access-Control-Allow-Headers:x-requested-with,content-type'); } echo json_encode($ret); ?>
之前我們跨域是藉助了瀏覽器對 Access-Control-Allow-Origin 的支援。但有些瀏覽器是不支援的,所以這並非是最佳方案,現在我們來利用nginx 通過反向代理 滿足瀏覽器的同源策略實現跨域!
然後我們回到nginx.conf 配置一個反向代理路徑(location /apis部分):
server {
listen 8094;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /apis {
rewrite ^.+apis/?(.*)$ /$1 break;
include uwsgi_params;
proxy_pass http://localhost:1894;
}
}