緣起
由於瀏覽器的同源策略,非同源不可請求。
但是,在實踐當中,經常會出現需要跨域請求資源的情況,比較典型的例如某個子域名向負責進行用戶驗證的子域名請求用戶信息等應用。
以前要實現跨域訪問,可以通過JSONP、Flash或者服務器中轉的方式來實現,但是現在我們有了CORS。
CORS與JSONP相比,無疑更為先進、方便和可靠。
1、 JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求。 2、 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理。 3、 JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS
JSONP的最基本的原理
動態添加一個<script>標簽,而script標簽的src屬性是沒有跨域的限制的。這樣說來,這種跨域方式其實與ajax XmlHttpRequest協議無關了。
這樣其實"jQuery AJAX跨域問題"就成了個偽命題,jquery $.ajax方法名有誤導人之嫌。
如果設為dataType: 'jsonp',這個$.ajax方法就和ajax XmlHttpRequest沒什么關系了,取而代之的則是JSONP協議。JSONP是一個非官方的協議,它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問。
JSONP即JSON with Padding。由於同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、端口)的資源。如果要進行跨域請求, 我們可以通過使用html的script標記來進行跨域請求,並在響應中返回要執行的script代碼,其中可以直接使用JSON傳遞 javascript對象。 這種跨域的通訊方式稱為JSONP。
Cross-Origin Resource sharing
這是W3C 新出的一個標准,簡單的講就是通過服務器/客戶端 一些Headers的設置及確認 來實現跨域請求,這些包頭有
Cross-Origin Resource sharing
這是W3C 新出的一個標准,簡單的講就是通過服務器/客戶端 一些Headers的設置及確認 來實現跨域請求,這些包頭有
- 5.1
Access-Control-Allow-Origin
Response Header - 5.2
Access-Control-Allow-Credentials
Response Header - 5.3
Access-Control-Expose-Headers
Response Header - 5.4
Access-Control-Max-Age
Response Header - 5.5
Access-Control-Allow-Methods
Response Header - 5.6
Access-Control-Allow-Headers
Response Header - 5.7
Origin
Request Header - 5.8
Access-Control-Request-Method
Request Header - 5.9
Access-Control-Request-Headers
Request Header
例如
- Access-Control-Allow-Origin: http://www.test.com
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- Access-Control-Allow-Headers: POWERED-BY-MENGXIANHUI
- Access-Control-Max-Age: 30
可以參考 http://www.w3.org/TR/access-control/#access-control-allow-origin-response-header w3c的網站
Access-Control-Allow-Origin: 允許跨域訪問的域,可以是一個域的列表,也可以是通配符"*"。這里要注意Origin規則只對域名有效,並不會對子目錄有效。 即http://www.test/test/是無效的。但是不同子域名需要分開設置,這里的規則可以參照那篇同源策略 Access-Control-Allow-Credentials: 是否允許請求帶有驗證信息,這部分將會在下面詳細解釋 Access-Control-Expose-Headers: 允許腳本訪問的返回頭,請求成功后,腳本可以在XMLHttpRequest中訪問這些頭的信息(貌似webkit沒有實現這個) Access-Control-Max-Age: 緩存此次請求的秒數。在這個時間范圍內,所有同類型的請求都將不再發送預檢請求而是直接使用此次返回的頭作為判斷依據,非常有用,大幅優化請求次數 Access-Control-Allow-Methods: 允許使用的請求方法,以逗號隔開 Access-Control-Allow-Headers: 允許自定義的頭部,以逗號隔開,大小寫不敏感
Access-Control-Allow-Credentials
在跨域請求中,默認情況下,HTTP Authentication信息,Cookie頭以及用戶的SSL證書無論在預檢請求中或是在實際請求都是不會被發送的。
但是,通過設置XMLHttpRequest的credentials為true,就會啟用認證信息機制。
雖然簡單請求還是不需要發送預檢請求,但是此時判斷請求是否成功需要額外判斷Access-Control-Allow-Credentials,如果Access-Control-Allow-Credentials為false,請求失敗。
十分需要注意的的一點就是此時Access-Control-Allow-Origin不能為通配符"*"(真是便宜了一幫偷懶的程序員),如果Access-Control-Allow-Origin是通配符"*"的話,仍將認為請求失敗
即便是失敗的請求,如果返回頭中有Set-Cookie的頭,瀏覽器還是會照常設置Cookie
客戶端頁面test.php
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>crossDomainRequest</title> </head> <body> <input type='button' value='開始測試' onclick="crossDomainRequest()" /> <div id="content"></div> <script type="text/javascript"> function createXHR(){ return window.XMLHttpRequest? new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP"); } function getappkey(url){ xmlHttp = createXHR(); xmlHttp.open("GET",url,false); xmlHttp.send(); result = xmlHttp.responseText; return result; } function crossDomainRequest(){ var content =getappkey('http://127.0.0.10/gettest.php'); document.getElementById("content").innerHTML=content; } </script> </body> </html>
服務端頁面gettest.php
<?php header("Access-Control-Allow-Origin: http://127.0.0.1"); echo "test success!"; ?>
如果不允許的話
所以跨域有以下幾種方法
通過webserver【nginx】配置來跨域
# # Wide-open CORS config for nginx # location / { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; # # Om nom nom cookies # add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # # Custom headers and headers various browsers *should* be OK with but aren't # add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; # # Tell client that this pre-flight info is valid for 20 days # add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; } }
示例代碼
location /{ add_header 'Access-Control-Allow-Origin' 'http://www.test.com'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET'; ... ... }
第一條指令:授權從http://www.test.com的請求
第二條指令:當該標志為真時,響應於該請求是否可以被暴露
第三天指令:指定請求的方法,可以是GET,POST等
如果需要允許來自任何域的訪問,可以這樣配置
Access-Control-Allow-Origin: *
通過后端程序來跨域
<?php header("Access-Control-Allow-Origin:http://www.test.com"); header("Access-Control-Allow-Origin:*"); echo json_encode($_POST); ?> <script type="text/javascript"> $("#ajax").click(function(){ $.ajax({ type: "POST", url: "http://www.test.com/test2.php", data: 'name=test', dataType:"json", success: function(data){ $('#Result').text(data.name); } }); }); </script>
JSONP
<?php if(isset($_GET['name']) && isset($_GET['callback'])) //callback根js端要對應,不然會報錯的 { echo $_GET['callback']. '(' . json_encode($_GET) . '); } ?> <script type="text/javascript"> $("#jsonp").click(function(){ $.ajax({ url: 'http://www.test.com/test1.php', data: {name: 'jsonp'}, dataType: 'jsonp', jsonp: 'callback', //為服務端准備的參數 jsonpCallback: 'getdata', //回調函數 success: function(){ alert("success"); } }); }); function getdata(data){ $('#Result').text(data.name); } </script>
getJSON
<script type="text/javascript"> $("#getjson").click(function(){ $.getJSON('http://www.test.com/test1.php?name=getjson&callback=?', function(data){ //沒有回調函數,直接處理 $('#Result').text(data.name); }) }) </script>
getScript
<script type="text/javascript"> $("#getscript").click(function(){ $.getScript('http://www.test.com/test1.php?name=getscript&callback=getdata'); }); </script>