SignalR的跨網域支援
9 |
在SignalR JS Client Hubs文件Cross Domain Support一節看到SignalR可利用JSONP實現跨網域呼叫。簡單的說,就是將SignalR Hub架在A網站,卻從B網站的網頁執行$.connection.hub.start()連上A網站的SignalR接收資料或傳送指令。乍看稀鬆平常,但網站寫多一點的老鳥都知道這個副本裡有個XHR跨網站存取的小王要解決,所幸SignalR已內建跨網域支援,所以我們就來寫幾行程式玩看看。
以下是一個獨立網頁,目前在扮演先前利用SignalR實現遠端程式遙控功能文章裡SignalRClient.exe的角色:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SignalR Cross-Site Web Page Client</title>
<base href="http://localhost:19636" />
<script src="/Scripts/jquery-1.6.4.js" type="text/javascript"></script>
<script src="/Scripts/jquery.signalR-0.5.2.js" ></script>
<script src="/SignalR/hubs" ></script>
<script>
$(function () {
//與SignalR Hub建立連線
var commHub = $.connection.commHub;
//實做ShowMessage(msg)
commHub.ShowMessage = function(msg) {
$("body").append("<div>" + msg + "</div>");
};
//跨網域引用SignalR時,要設定hub.url, 並hub.start({ jsonp: true });
$.connection.hub.url = "http://127.0.0.1:19636/signalr";
$.connection.hub.start({ jsonp: true })
.done(function() {
commHub.register("XSS");
});
});
</script>
</head>
<body>
</body>
</html>
將以上網頁SignalRClient.htm存在本機隨意資料夾下(或放到其他網站也行,只要不在CommHub所在的ASP.NET MVC網站上就會產生跨網域障礙),使用IE等瀏覽器開啟該網頁進行測試,則可在主控網頁看到其所註冊的名稱--XSS,也能傳送訊息顯示在該網頁上,實現了與SignalRClient.exe相似的功能。檢視該網頁的HTML原始碼,可發現SignalR JS Client是以<script>方式建立連線[見圖標(1)],其src為httq://127.0.0.1:19636/signalr/connect?transport=longPolling&connectionId=81b9...略...62b98&connectionData=%5B%7B%22name%22%3A%22CommHub%22%7D%5D&tid=4&callback=jQuery16407549852419734552_1341978125354&_=1341978125642,看到callback=jQuery164075…參數,判斷是透過jQuery.ajax()的jsonp dataType進行跨網域呼叫,而由[圖標(2)]的回傳結果也可驗證此點。最後,主控端送來的文字就順利呈現在網頁上囉! (見圖標(3),測試時我按了兩次Send,故文字出現兩次) 驗證了SignalR JavaScript Client的跨網域支援能力。
【後記】
測試過程發現IE9即使不啟甪{ jsonp: true }也能跨網域運作,但在Chrome上卻不然。追進原始碼,發現SignalR支援webSocket及longPolling兩種傳輸模式,而start()中有一小段邏輯,當發現URL跨網域而瀏覽器的XHR不支援Cross-Original Request Sharing時(簡稱CORS, SignalR透過$.support.cors偵測,IE9被判定不支援),會自動切換為jsonp=true,改用longPolling + JSONP克服障礙;而在不啟用JSONP的前題下,要做到跨網域XHR,則需要從Server端額外加入一些Access-Control-* Header[參考]。
由此可解釋未指定{ json: true }時,IE9因被強制切成jsonp=true而成功,Chrome未被切成jsonp模式,又因SignalR端又未加入CORS需要Header,故無法順利運作。
Comments
# by didkwaab
公交车上,忽然感到屁股上被谁摸了一下,回头一看,两个漂亮小姑娘。 其中一个见回头,冲我甜甜一笑,我也报以微笑,心里美滋滋的。 只听另一姑娘说:“妹妹,你能不能改掉随处擦鼻屎的坏习惯。 === 文章不错,已经收藏 ===
# by Akira
請問一下SignalR 1.2.2 Client for Window Form, 如何在new Connection的時候代Cross Domain的參數?
# by Jeffrey
to Akira,我不太理解非網頁應用情境下的跨網域存取需求,能再進一步說明嗎?
# by Akira
在SignalR Server啟用EnableCrossDomain,SignalR採用Persistence Connection的方式,SignalR版本為1.2.2 protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapConnection<EchoConnection>("EchoConnection", "/realtime/echo", new ConnectionConfiguration { EnableCrossDomain = true }); } 在ASP.NET Web Client跨網域的寫法 connection = $.connection("http://localhost:6684/realtime/echo", '', false, { xdomain: true }); 而在Windows Form我不知道是否有類似Web Client的CrossDomain的寫法? persistentConnection = new Connection("http://localhost:6684/realtime/echo"); try { persistentConnection.Received += ReceivedMessage; persistentConnection.Start().Wait(); } catch (Exception ex) { Debug.WriteLine("Exception = " + ex.ToString()); } 因為我的Windows Form專案很奇怪,完全用localhost測試的時候,都沒有問題,但是一旦發佈到正式的主機後,連線都要試過五六次才會成功,一直找不到原因,IIS上的應用程式集我是選DefaultAppPool。SignalR Persistence的資源比較少,有時候想改寫為Hub,但是要動到的程式很多。
# by Jeffrey
to Akira, 依我的理解,WinForm 不應受到 Cross Domain 的限制,建議開 Fiddler 觀察連線封包找線索。
# by Akira
Cross Domain是SignalR Server與SignalR Web with Javascript在不同電腦才需要的嗎? 請問一下我這樣理解對嗎?
# by Jeffrey
to Akira, Cross-Domain 限制可簡單想成:瀏覽器基於安全理由,禁止來自A網站的網頁透過程式存取B網站的內容。參考:https://developer.mozilla.org/zh-TW/docs/Web/Security/Same-origin_policy
# by Akira
我的專案從Signal 1升級到Signal 2.4.0。我是用persistent connection,請問一下SignalR Server端的CORS應該要如何設定? [assembly: OwinStartup(typeof(SignalRServer.Startup))] namespace SignalRServer { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR<EchoConnection>("/realtime/echo", new ConnectionConfiguration() { EnableJSONP = true }); } } }
# by Jeffrey
to Akira, Windows Form Client背後應是透過WebClient或HttpClient連上SignalR主機,CORS是在瀏覽器透過XHR存取才會遇到的問題,建議透過Fiddler或Wireshark去查要重試多次才連上的原因。