要記錄好這個問題,首先需要描述清楚這個問題。
有如下的一個場景:我們在實現一個系統中某個功能的時候,可能系統的某些模塊不在一個域名下。用戶中心模塊部署在user.xxx.com下,產品相關的功能部署在product.xxx.com下,在用戶中心的某些頁面,我們或許要請求產品功能模塊的功能,此時就會有問題出現。以Google Chrome(版本 45.0.2454.101 m)瀏覽器為例,當當前的頁面在A域名下,我們在頁面中構造了一個異步請求,我們需要調用位于B域名下的某個API,代碼片段如下:
<script type="text/javascript">
$(function() {
//當前頁面所在域名:http://localhost:9000/
//jQuery版本:jquery-1.11.3.min.js
$.ajax({
url : "http://localhost:9001/order/pindexpost",
type : "POST",
dataType : 'json',
success : function(data) {
alert(data);
}
});
});
</script>
但是請求之后,瀏覽器控制臺給出了如下的錯誤信息
那么這就是標題中所提到的跨域資源共享CORS問題了。我們把這個概念細化一下,首先,為什么會有CORS?簡單點理解就是出于安全考慮,瀏覽器都遵循一個叫同源策略的東西,他約束瀏覽器說“不是你的東西,你問人家要,人家默認不給你,除非人家同意給你”,基于這個策略,就有了跨域資源共享的問題,那么,怎么就叫跨域了呢?上面代碼中的域名不是一樣的嗎,都是指向本地的localhost啊。舉個栗子說明下
http://www.tkk7.com:80,左邊是一個完整的請求,包含了協議,子域名等等若干部分,我們把它理解為域,但凡是在這個域下發生的請求,都無法直接訪問到其他域的資源
- http://www.tkk7.com:80能訪問http://www.tkk7.com:81下的資源嗎?不行!
- http://www.tkk7.com:80能訪問http://abc.blogjava.net:80下的資源嗎?不行!
- http://www.tkk7.com:80能訪問http://www.tkk7.com:80下的資源嗎?不行!
- http://www.tkk7.com:80能訪問http://域名對應的真實IP:80下的資源嗎?不行!
- http://www.tkk7.com:80能訪問http://www.xyz.net:80下的資源嗎?不行!
域哪怕有一點點不一樣,一點點不一樣,一點點不一樣,都是不可以訪問的,那么問題來了,如何實現跨域呢? 目前有三種方案可以打破同源策略所帶來的限制,實現跨域請求。
-
使用JSONP。這種方案是使用HTML的script標簽來實現的,script標簽的src屬性不受同源策略約束,可以訪問任意站點的資源,但是,該方案有著自己的約束。首先,他只能發出GET請求,因為script標簽的初衷就是為了獲取js腳本,所以如果返回的數據內容不是js腳本的話,也不會成功。基于這個約束,我們需要改造一下我們的請求:
1 <script type="text/javascript">
2 $(function () {
3 //當前頁面所在域名:http://localhost:9000/
4 // jQuery版本:jquery-1.11.3.min.js
5 $.ajax({
6 url: "http://localhost:9001/order/pindexpost",
7 type: "POST", dataType: 'jsonp',
8 jsonpCallback: "callback"
9 });
10 })
11 ;
12 function callback(result) {
13 alert("suc:" + result);
14 }
15 </script>
剛才提到,JSONP方式是基于script標簽的,所以我們要對返回的數據做一些處理,使返回的數據是一段js腳本才可以。我們在客戶端預定義好callback函數,使得服務端返回數據的時候可以直接調用這個callback函數,服務端代碼片段如下:
1 response().setContentType("application/javascript");
2 return ok("callback(" + data + ")");
要注明返回的格式,并且data的格式也要符合js的語法。其實說白了,JSONP就是請求目標數據,然后將目標數據和回調函數進行拼接,以javascript的形式返回給瀏覽器,之后瀏覽器執行的這個回調函數。
使用代理服務器。這個比較好理解,不過運維的成分多一些,設置一個代理服務器,根據請求API所在命名空間的不同,轉發到相應的域去,這樣一來就騙過了瀏覽器,讓瀏覽器以為所有的數據都來自一個域。
- 設置請求的相應頭。在目標API的響應頭中添加“Access-Control-Allow-Origin”,設置值為“*”,這就是讓服務器通知瀏覽器,說“我這個API的響應,是面向所有人的,*就代表這個意思啊”,但是,這也會帶來一個安全性的問題,所以你可以設置部分站點可以跨域訪問,比如Access-Control-Allow-Origin: http://www.tkk7.com/,但是這個響應頭存在一定的兼容問題,具體支持情況可以查看caniuse.com