跨域問題來源于JavaScript的同源策略,即只有 協(xié)議+主機(jī)名+端口號(hào) (如存在)相同,則允許相互訪問。也就是說JavaScript只能訪問和操作自己域下的資源,不能訪問和操作其他域下的資源。
在以前,前端和后端混雜在一起, 比如JavaScript直接調(diào)用同系統(tǒng)里面的一個(gè)Httphandler,就不存在跨域的問題,但是隨著現(xiàn)代的這種多種客戶端的流行,比如一個(gè)應(yīng)用通常會(huì)有Web端,App端,以及WebApp端,各種客戶端通常會(huì)使用同一套的后臺(tái)處理邏輯,即API, 前后端分離的開發(fā)策略流行起來,前端只關(guān)注展現(xiàn),通常使用JavaScript,后端處理邏輯和數(shù)據(jù)通常使用WebService來提供json數(shù)據(jù)。一般的前端頁面和后端的WebService API通常部署在不同的服務(wù)器或者域名上。這樣,通過ajax請(qǐng)求WebService的時(shí)候,就會(huì)出現(xiàn)同源策略的問題。 需要說明的是,同源策略是JavaScript里面的限制,其他的編程語言,比如在C#,Java或者iOS等其他語言中是可以調(diào)用外部的WebService,也就是說,如果開發(fā)Native應(yīng)用,是不存在這個(gè)問題的,但是如果開發(fā)Web或者Html5如WebApp,通常使用JavaScript ajax對(duì)WebService發(fā)起請(qǐng)求然后解析返回的值,這樣就可能存在跨域的問題。 一般的,很容易想到,將外部的資源搬到同一個(gè)域上就能解決同源策略的限制的。即在Web網(wǎng)站上同時(shí)開發(fā)一個(gè)Http服務(wù)端頁面,所有JavaScript的請(qǐng)求都發(fā)到這個(gè)頁面上來,這個(gè)頁面在內(nèi)部使用其他語言去調(diào)用外部的WebService。即添加一個(gè)代理層。這種方式可以解決問題,但是不夠直接和高效。 目前,比較常見的跨域解決方案包括JSONP (JSON with padding)和CORS (Cross-origin resource sharing )。一些解決方案需要客戶端和服務(wù)端配合如JSOP,一些則只需要服務(wù)端配合處理比如CORS。下面分別介紹這兩種跨域方案,以及服務(wù)端WebService如何支持這兩種跨域方案。 JSONP以及WebService的支持同源策略下,某個(gè)服務(wù)器是無法獲取到服務(wù)器以外的數(shù)據(jù),但是html里面的img,iframe和script等標(biāo)簽是個(gè)例外,這些標(biāo)簽可以通過src屬性請(qǐng)求到其他服務(wù)器上的數(shù)據(jù)。而JSONP就是通過script節(jié)點(diǎn)src調(diào)用跨域的請(qǐng)求。 當(dāng)我們向服務(wù)器提交一個(gè)JSONP的請(qǐng)求時(shí),我們給服務(wù)傳了一個(gè)特殊的參數(shù),告訴服務(wù)端要對(duì)結(jié)果特殊處理一下。這樣服務(wù)端返回的數(shù)據(jù)就會(huì)進(jìn)行一點(diǎn)包裝,客戶端就可以處理。 舉個(gè)例子,服務(wù)端和客戶端約定要傳一個(gè)名為callback的參數(shù)來使用JSONP功能。比如請(qǐng)求的參數(shù)如下: http://www./sample.aspx?callback=mycallback
如果沒有后面的callback參數(shù),即不使用JSONP的模式,該服務(wù)的返回結(jié)果可能是一個(gè)單純的json字符串,比如: { foo : 'bar' }
如果和服務(wù)端約定jsonp格式,那么服務(wù)端就會(huì)處理callback的參數(shù),將返回結(jié)果進(jìn)行一下處理,比如處理成: mycallback({ foo : 'bar' })
可以看到,這其實(shí)是一個(gè)函數(shù)調(diào)用,比如可以實(shí)現(xiàn)在頁面定義一個(gè)名為mycallback的回調(diào)函數(shù): mycallback = function(data)
{
alert(data.foo);
};
現(xiàn)在,請(qǐng)求的返回值回去觸發(fā)回調(diào)函數(shù),這樣就完了了跨域請(qǐng)求。 如果使用ServiceStack創(chuàng)建WebService的話,支持Jsonp方式的調(diào)用很簡(jiǎn)單,只需要在AppHost的Configure函數(shù)里面注冊(cè)一下對(duì)響應(yīng)結(jié)果進(jìn)行過濾處理即可。 /// <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跨域方式比較方便,也支持各種較老的瀏覽器,但是缺點(diǎn)很明顯,他只支持GET的方式提交,不支持其他Post的提交,Get方式對(duì)請(qǐng)求的參數(shù)長(zhǎng)度有限制,在有些情況下可能不滿足要求。所以下面就介紹一下CORS的跨域解決方案。 CORS跨域及WebService的支持先來看一個(gè)例子,我們新建一個(gè)基本的html頁面,在里面編寫一個(gè)簡(jiǎn)單的是否支持跨域的小腳本,如下: <html xmlns="http://www./1999/xhtml"> <head> <title>AJAX跨域請(qǐng)求測(cè)試</title> </head> <body> <input type='button' value='開始測(cè)試' 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 = "不能創(chuàng)建 XMLHttpRequest"; } } function handler(evtXHR) { if (xhr.readyState == 4) { if (xhr.status == 200) { var response = xhr.responseText; document.getElementById("content").innerHTML = "結(jié)果:" + response; } else { document.getElementById("content").innerHTML = "不允許跨域請(qǐng)求。"; } } else { document.getElementById("content").innerHTML += "<br/>執(zhí)行狀態(tài) readyState:" + xhr.readyState; } } //]]> </script> </body> </html> 然后保存為本地html文件,可以看到,這個(gè)腳本中,對(duì)本地的服務(wù)http://localhost:1337/json/Hello 發(fā)起了一個(gè)請(qǐng)求, 如果使用chrome 直接打開,會(huì)看到輸出的結(jié)果,不允許跨域請(qǐng)求。 在javascript控制臺(tái)程序中同樣可以看到錯(cuò)誤提示: 那么如果在返回響應(yīng)頭header中注入Access-Control-Allow-Origin,這樣瀏覽器檢測(cè)到header中的Access-Control-Allow-Origin,則就可以跨域操作了。 同樣,如果使用ServcieStack,在很多地方可以支持CORS的跨域方式。最簡(jiǎn)單的還是在AppHost的Configure函數(shù)里面直接寫入: /// <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()); } 這樣就可以了,相當(dāng)于使用默認(rèn)的CORS配置: CorsFeature(allowedOrigins:"*", allowedMethods:"GET, POST, PUT, DELETE, OPTIONS", allowedHeaders:"Content-Type", allowCredentials:false); 如果僅僅允許GET和POST的請(qǐng)求支持CORS,則只需要改為: Plugins.Add(new CorsFeature(allowedMethods: "GET, POST")); 當(dāng)然也可以在AppHost的Config里面設(shè)置全局的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" }, }, }); } 現(xiàn)在運(yùn)行WebService,使用postman或者Chrome調(diào)用這個(gè)請(qǐng)求,可以看到返回的值頭文件中,已經(jīng)加上了響應(yīng)頭,并且可以正常顯示返回結(jié)果了: CORS使用起來簡(jiǎn)單,不需要客戶端的額外處理,而且支持Post的方式提交請(qǐng)求,但是CORS的唯一一個(gè)缺點(diǎn)是對(duì)客戶端的瀏覽器版本有要求,支持CORS的瀏覽器機(jī)器版本如下:
總結(jié)本文介紹了JavaScript中的跨域基本概念和產(chǎn)生的原因,以及如何解決跨域的兩種方法,一種是JSONP 一種是 CORS,在客戶端Javascript調(diào)用服務(wù)端接口的時(shí)候,如果需要支持跨域的話,需要服務(wù)端支持。JSONP的方式就是服務(wù)端對(duì)返回的值進(jìn)行回調(diào)函數(shù)包裝,他的優(yōu)點(diǎn)是支持眾多的瀏覽器, 缺點(diǎn)是僅支持Get的方式對(duì)服務(wù)端請(qǐng)求。另一種主流的跨域方案是CORS,他僅需要服務(wù)端在返回?cái)?shù)據(jù)的時(shí)候在相應(yīng)頭中加入標(biāo)識(shí)信息。這種方式非常簡(jiǎn)便。唯一的缺點(diǎn)是需要瀏覽器的支持,一些較老的瀏覽器可能不支持CORS特性。 跨域支持是創(chuàng)建WebService時(shí)應(yīng)該考慮的一個(gè)功能點(diǎn),希望本文對(duì)您在這邊面有所幫助,文中是使用ServiceStack來演示跨域支持的,如果您用的WCF的話,知道跨域原理的前提下,實(shí)現(xiàn)跨域應(yīng)該不難。 |
|