淺談跨域以及WebService對跨域的支持

跨域問題來源於JavaScript的同源策略,即只有 協議+主機名+端口號 (如存在)相同,則允許相互訪問。也就是說JavaScript只能訪問和操作自己域下的資源,不能訪問和操作其他域下的資源。

    在以前,前端和後端混雜在一起, 比如JavaScript直接調用同系統裏面的一個Httphandler,就不存在跨域的問題,但是隨着現代的這種多種客戶端的流行,比如一個應用通常會有Web端,App端,以及WebApp端,各種客戶端通常會使用同一套的後臺處理邏輯,即API, 前後端分離的開發策略流行起來,前端只關注展現,通常使用JavaScript,後端處理邏輯和數據通常使用WebService來提供json數據。一般的前端頁面和後端的WebService API通常部署在不同的服務器或者域名上。這樣,通過ajax請求WebService的時候,就會出現同源策略的問題。

    需要說明的是,同源策略是JavaScript裏面的限制,其他的編程語言,比如在C#,Java或者iOS等其他語言中是可以調用外部的WebService,也就是說,如果開發Native應用,是不存在這個問題的,但是如果開發Web或者Html5如WebApp,通常使用JavaScript ajax對WebService發起請求然後解析返回的值,這樣就可能存在跨域的問題。

一般的,很容易想到,將外部的資源搬到同一個域上就能解決同源策略的限制的。即在Web網站上同時開發一個Http服務端頁面,所有JavaScript的請求都發到這個頁面上來,這個頁面在內部使用其他語言去調用外部的WebService。即添加一個代理層。這種方式可以解決問題,但是不夠直接和高效。

    目前,比較常見的跨域解決方案包括JSONP (JSON with padding)和CORS (Cross-origin resource sharing )。一些解決方案需要客戶端和服務端配合如JSOP,一些則只需要服務端配合處理比如CORS。下面分別介紹這兩種跨域方案,以及服務端WebService如何支持這兩種跨域方案。

JSONP以及WebService的支持

    同源策略下,某個服務器是無法獲取到服務器以外的數據,但是html裏面的img,iframe和script等標籤是個例外,這些標籤可以通過src屬性請求到其他服務器上的數據。而JSONP就是通過script節點src調用跨域的請求。

    當我們向服務器提交一個JSONP的請求時,我們給服務傳了一個特殊的參數,告訴服務端要對結果特殊處理一下。這樣服務端返回的數據就會進行一點包裝,客戶端就可以處理。

舉個例子,服務端和客戶端約定要傳一個名爲callback的參數來使用JSONP功能。比如請求的參數如下:

http://www.example.net/sample.aspx?callback=mycallback

   如果沒有後面的callback參數,即不使用JSONP的模式,該服務的返回結果可能是一個單純的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的跨域解決方案。

CORS跨域及WebService的支持

    先來看一個例子,我們新建一個基本的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 直接打開,會看到輸出的結果,不允許跨域請

Not allowed CORS

   那麼如果在返回響應頭header中注入Access-Control-Allow-Origin,這樣瀏覽器檢測到header中的Access-Control-Allow-Origin,則就可以跨域操作了。

求。 在javascript控制檯程序中同樣可以看到錯誤提示:

同樣,如果使用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調用這個請求,可以看到返回的值頭文件中,已經加上了響應頭,並且可以正常顯示返回結果了:

   allowed cors

   CORS使用起來簡單,不需要客戶端的額外處理,而且支持Post的方式提交請求,但是CORS的唯一一個缺點是對客戶端的瀏覽器版本有要求,支持CORS的瀏覽器機器版本如下:

CORS Support

 

總結

本文介紹了JavaScript中的跨域基本概念和產生的原因,以及如何解決跨域的兩種方法,一種是JSONP 一種是 CORS,在客戶端Javascript調用服務端接口的時候,如果需要支持跨域的話,需要服務端支持。JSONP的方式就是服務端對返回的值進行回調函數包裝,他的優點是支持衆多的瀏覽器, 缺點是僅支持Get的方式對服務端請求。另一種主流的跨域方案是CORS,他僅需要服務端在返回數據的時候在相應頭中加入標識信息。這種方式非常簡便。唯一的缺點是需要瀏覽器的支持,一些較老的瀏覽器可能不支持CORS特性。

跨域支持是創建WebService時應該考慮的一個功能點,希望本文對您在這邊面有所幫助,文中是使用ServiceStack來演示跨域支持的,如果您用的WCF的話,知道跨域原理的前提下,實現跨域應該不難。




發佈了23 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章