參考文章:http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
什么是跨域
JavaScript出於安全方面的考慮,不允許跨域調用其他頁面的對象。但在安全限制的同時也給注入iframe或是ajax應用上帶來了不少麻煩。這里把涉及到跨域的一些問題簡單地整理一下:
首先什么是跨域,簡單地理解就是因為JavaScript同源策略的限制,a.com 域名下的js無法操作b.com或是c.a.com域名下的對象。更詳細的說明可以看下表:
URL | 說明 | 是否允許通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js |
同一域名下 | 允許 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js |
同一域名下不同文件夾 | 允許 |
http://www.a.com:8000/a.js http://www.a.com/b.js |
同一域名,不同端口 | 不允許 |
http://www.a.com/a.js https://www.a.com/b.js |
同一域名,不同協議 | 不允許 |
http://www.a.com/a.js http://70.32.92.74/b.js |
域名和域名對應ip | 不允許 |
http://www.a.com/a.js http://script.a.com/b.js |
主域相同,子域不同 | 不允許 |
http://www.a.com/a.js http://a.com/b.js |
同一域名,不同二級域名(同上) | 不允許(cookie這種情況下也不允許訪問) |
http://www.cnblogs.com/a.js http://www.a.com/b.js |
不同域名 | 不允許 |
- 特別注意兩點:
- 第一,如果是協議和端口造成的跨域問題“前台”是無能為力的,
-
第二:在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。
接下來簡單地總結一下在“前台”一般處理跨域的辦法,后台proxy這種方案牽涉到后台配置,這里就不闡述了,有興趣的可以看看yahoo的這篇文章:《JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls》
一、WebApi:
// 為測試JSONP跨域而寫 [HttpGet] public void GetAll(string callback) { List<User> retList = new List<User>(); Context context = new Context(); var users = context.Users.ToList(); if (users != null && users.Count > 0) { retList = users; } context.Dispose(); //return "callback(" + JsonConvert.SerializeObject(retList) + ")"; HttpContext.Current.Response.Write(callback + "(" + JsonConvert.SerializeObject(retList) + ")"); HttpContext.Current.Response.End(); }
以項目A中的一個WebApi方法作為實驗中的接口,接口地址http://localhost:616/api/UserService/GetAll,訪問方式為GET
二、測試頁面:
<html> <head> <title>test</title> <script src="~/Scripts/jquery-1.7.1.min.js"></script> <script type="text/javascript"> function callAPI() { $.ajax({ type: "GET", url: "http://localhost:616/api/UserService/GetAll", dataType: "application/json;charset=utf-8", success: function (result) { alert(result); }, error: function (e) { var test = e; } }); } function callAPIByJSONP() { var test = 1; $.ajax({ type: "GET", async: false, url: "http://localhost:616/api/UserService/GetAll", dataType: "jsonp", jsonp: "callback", jsonpCallback:"handler", success: function (result) { alert(result[0].UserName); }, error: function (e) { var test = e; } }); } </script> </head> <body> <input type="button" value="普通調用" onclick="callAPI();" /> <input type="button" value="JSONP調用" onclick="callAPIByJSONP();" /> </body> </html>
測試頁面只包括兩個按鈕,一個是普通的ajax調用WebApi接口,另一個是用JSONP調用WebApi接口。
當使用一般的Ajax調用時會提示下面信息:
No Access-Control-Allow-Orgin,此時不能通過ajax獲取其他服務器上的數據。
三、使用JSONP跨域:
JSONP的大致原理是前台請求跨域服務器上的腳本時是不會受跨域的影響的,比如:<script type=text/javascript src='www.abc.com' />。因此只需要跨域服務根據前端的需求動態生成js,這個js包括了執行的方法以及數據,這樣就實現了類似於ajax的效果。不過JSONP和ajax有着本質的區別:ajax的核心是通過XmlHttpRequest獲取非本頁面的內容,而JSONP的核心是動態添加<script>標簽來調用服務器動態生成的js腳本。
function callAPIByJSONP() { var test = 1; $.ajax({ type: "GET", async: false, url: "http://localhost:616/api/UserService/GetAll", dataType: "jsonp", jsonp: "callback", jsonpCallback:"handler", success: function (result) { alert(result[0].UserName); }, error: function (e) { var test = e; } }); }
注意點:
剛開始后台方法返回值為string類型,返回值為:return callback(回調方法名)+(數據),這樣返回的Code為200,statusText為success,但是不會進入到success回調函數里,而是進入error中。
后來在網上查找原因,很多回答都是說返回的數據格式有問題,但是按照他們提供的格式和上面我寫的是一樣的。最后修改后台方法,使用HttpContext.Current.Response.Write的方式返回數據,問題解決。
// 為測試JSONP跨域而寫 [HttpGet] public void GetAll(string callback) { List<User> retList = new List<User>(); Context context = new Context(); var users = context.Users.ToList(); if (users != null && users.Count > 0) { retList = users; } context.Dispose(); //return "callback(" + JsonConvert.SerializeObject(retList) + ")"; HttpContext.Current.Response.Write(callback + "(" + JsonConvert.SerializeObject(retList) + ")"); HttpContext.Current.Response.End(); }
前台獲取的數據:
另外,由於JSONP的原理是動態添加<script>,因此JSONP的請求方式是GET,即使指定了type:"POST"最后JSONP向后台服務請求的方式還是GET得方式