跨域問題來源於JavaScript的同源策略,即只有 協議+主機名+端口號 (如存在)相同,則容許相互訪問。也就是說JavaScript只能訪問和操做本身域下的資源,不能訪問和操做其餘域下的資源。javascript
在之前,前端和後端混雜在一塊兒, 好比JavaScript直接調用同系統裏面的一個Httphandler,就不存在跨域的問題,可是隨着現代的這種多種客戶端的流行,好比一個應用一般會有Web端,App端,以及WebApp端,各類客戶端一般會使用同一套的後臺處理邏輯,即API, 先後端分離的開發策略流行起來,前端只關注展示,一般使用JavaScript,後端處理邏輯和數據一般使用WebService來提供json數據。通常的前端頁面和後端的WebService API一般部署在不一樣的服務器或者域名上。這樣,經過ajax請求WebService的時候,就會出現同源策略的問題。html
須要說明的是,同源策略是JavaScript裏面的限制,其餘的編程語言,好比在C#,Java或者iOS等其餘語言中是能夠調用外部的WebService,也就是說,若是開發Native應用,是不存在這個問題的,可是若是開發Web或者Html5如WebApp,一般使用JavaScript ajax對WebService發起請求而後解析返回的值,這樣就可能存在跨域的問題。前端
通常的,很容易想到,將外部的資源搬到同一個域上就能解決同源策略的限制的。即在Web網站上同時開發一個Http服務端頁面,全部JavaScript的請求都發到這個頁面上來,這個頁面在內部使用其餘語言去調用外部的WebService。即添加一個代理層。這種方式能夠解決問題,可是不夠直接和高效。java
目前,比較常見的跨域解決方案包括JSONP (JSON with padding)和CORS (Cross-origin resource sharing )。一些解決方案須要客戶端和服務端配合如JSOP,一些則只須要服務端配合處理好比CORS。下面分別介紹這兩種跨域方案,以及服務端WebService如何支持這兩種跨域方案。web
同源策略下,某個服務器是沒法獲取到服務器之外的數據,可是html裏面的img,iframe和script等標籤是個例外,這些標籤能夠經過src屬性請求到其餘服務器上的數據。而JSONP就是經過script節點src調用跨域的請求。ajax
當咱們向服務器提交一個JSONP的請求時,咱們給服務傳了一個特殊的參數,告訴服務端要對結果特殊處理一下。這樣服務端返回的數據就會進行一點包裝,客戶端就能夠處理。chrome
舉個例子,服務端和客戶端約定要傳一個名爲callback的參數來使用JSONP功能。好比請求的參數以下:編程
http://www.example.net/sample.aspx?callback=mycallback
若是沒有後面的callback參數,即不使用JSONP的模式,該服務的返回結果多是一個單純的json字符串,好比:json
{ foo : 'bar' }
若是和服務端約定jsonp格式,那麼服務端就會處理callback的參數,將返回結果進行一下處理,好比處理成:後端
mycallback({ foo : 'bar' })
能夠看到,這實際上是一個函數調用,好比能夠實如今頁面定義一個名爲mycallback的回調函數:
mycallback = function(data) { alert(data.foo); };
如今,請求的返回值回去觸發回調函數,這樣就完了了跨域請求。
若是使用ServiceStack建立WebService的話,支持Jsonp方式的調用很簡單,只須要在AppHost的Configure函數裏面註冊一下對響應結果進行過濾處理便可。
/// <summary> /// Application specific configuration /// This method should initialize any IoC resources utilized by your web service classes. /// </summary> /// <param name="container"></param> public override void Configure(Container container) { ResponseFilters.Add((req, res, dto) => { var func = req.QueryString.Get("callback"); if (!func.isNullOrEmpty()) { res.AddHeader("Content-Type", ContentType.Html); res.Write("<script type='text/javascript'>{0}({1});</script>" .FormatWith(func, dto.ToJson())); res.Close(); } }); }
JSONP跨域方式比較方便,也支持各類較老的瀏覽器,可是缺點很明顯,他只支持GET的方式提交,不支持其餘Post的提交,Get方式對請求的參數長度有限制,在有些狀況下可能不知足要求。因此下面就介紹一下CORS的跨域解決方案。
先來看一個例子,咱們新建一個基本的html頁面,在裏面編寫一個簡單的是否支持跨域的小腳本,以下:
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>AJAX跨域請求測試</title> </head> <body> <input type='button' value='開始測試' onclick='crossDomainRequest()' /> <div id="content"></div> <script type="text/javascript"> //<![CDATA[ var xhr = new XMLHttpRequest(); var url = 'http://localhost:8078/json/ShopUserLogin'; function crossDomainRequest() { document.getElementById("content").innerHTML = "開始……"; if (xhr) { xhr.open('POST', url, true); xhr.onreadystatechange = handler; xhr.send(); } else { document.getElementById("content").innerHTML = "不能建立 XMLHttpRequest"; } } function handler(evtXHR) { if (xhr.readyState == 4) { if (xhr.status == 200) { var response = xhr.responseText; document.getElementById("content").innerHTML = "結果:" + response; } else { document.getElementById("content").innerHTML = "不容許跨域請求。"; } } else { document.getElementById("content").innerHTML += "<br/>執行狀態 readyState:" + xhr.readyState; } } //]]> </script> </body> </html>
而後保存爲本地html文件,能夠看到,這個腳本中,對本地的服務http://localhost:1337/json/Hello 發起了一個請求, 若是使用chrome 直接打開,會看到輸出的結果,不容許跨域請求。 在javascript控制檯程序中一樣能夠看到錯誤提示:
那麼若是在返回響應頭header中注入Access-Control-Allow-Origin,這樣瀏覽器檢測到header中的Access-Control-Allow-Origin,則就能夠跨域操做了。
一樣,若是使用ServcieStack,在不少地方能夠支持CORS的跨域方式。最簡單的仍是在AppHost的Configure函數裏面直接寫入:
/// <summary> /// Application specific configuration/// This method should initialize any IoC resources utilized by your web service classes./// </summary> /// <param name="container"></param>public override void Configure(Container container) { this.AddPlugin(new CorsFeature()); }
這樣就能夠了,至關於使用默認的CORS配置:
CorsFeature(allowedOrigins:"*", allowedMethods:"GET, POST, PUT, DELETE, OPTIONS", allowedHeaders:"Content-Type", allowCredentials:false);
若是僅僅容許GET和POST的請求支持CORS,則只須要改成:
Plugins.Add(new CorsFeature(allowedMethods: "GET, POST"));
固然也能夠在AppHost的Config裏面設置全局的CORS,以下:
/// <summary> /// Application specific configuration/// This method should initialize any IoC resources utilized by your web service classes./// </summary> /// <param name="container"></param>public override void Configure(Container container) { base.SetConfig(new EndpointHostConfig { GlobalResponseHeaders = { { "Access-Control-Allow-Origin", "*" }, { "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" }, { "Access-Control-Allow-Headers", "Content-Type" }, }, }); }
如今運行WebService,使用postman或者Chrome調用這個請求,能夠看到返回的值頭文件中,已經加上了響應頭,而且能夠正常顯示返回結果了:
CORS使用起來簡單,不須要客戶端的額外處理,並且支持Post的方式提交請求,可是CORS的惟一一個缺點是對客戶端的瀏覽器版本有要求,支持CORS的瀏覽器機器版本以下:
本文介紹了JavaScript中的跨域基本概念和產生的緣由,以及如何解決跨域的兩種方法,一種是JSONP 一種是 CORS,在客戶端Javascript調用服務端接口的時候,若是須要支持跨域的話,須要服務端支持。JSONP的方式就是服務端對返回的值進行回調函數包裝,他的優勢是支持衆多的瀏覽器, 缺點是僅支持Get的方式對服務端請求。另外一種主流的跨域方案是CORS,他僅須要服務端在返回數據的時候在相應頭中加入標識信息。這種方式很是簡便。惟一的缺點是須要瀏覽器的支持,一些較老的瀏覽器可能不支持CORS特性。
跨域支持是建立WebService時應該考慮的一個功能點,但願本文對您在這邊面有所幫助,文中是使用ServiceStack來演示跨域支持的,若是您用的WCF的話,知道跨域原理的前提下,實現跨域應該不難。