###### tags: `Angular` `SignalR` # 🌝[T]SignalR ## 前言 SignalR適用開發任何即時(real-time)類型的應用程式。 但在HTML5之前,開發者需要採用Client端主動發出請求,Server端被動處理的方式 : >Polling * 傳統HTTP作法。 * 若知道伺服器資料更新的頻率,能使用此方法實現同步更新,但若是在不知道伺服器資料何時更新的情況下,為達到同步更新的效果不斷傳送Request,將造成網路資源浪費。 >Comet * 為[解決Polling的弊端](https://www.iteye.com/blog/sp42-141335)。而出現 * Client端會發出一個長時間等待的Request,當伺服器有資料,就立即斷線再發送新的Request,主要兩種實作方式: 1.long-polling 長時間輪詢 * **特點** : 等到 拿到資料/過請求時間 再重新發送,能改善效率並減少流量浪費 * **缺點** : 若傳送量大,可能會傳送不完全 ![long-polling](https://i.imgur.com/zwVl2Zx.png) 2.streaming 串流 * **特點** : Server端與Client端建立一條持續的連線,為了保持連線,Server端會隔斷時間發送Response給Client。 Server端會將資料傳入[iframe](https://www.webdesigns.com.tw/html_iframe.asp),藉由iframe內的JavaScript更新Client端的頁面。 * **缺點** : 傳送資料時,訊息會被包起來,道防火牆前會被放進暫存器(Buffer),傳輸的速率會降低。 ![streaming](https://i.imgur.com/3ZEbVZ6.png) * Comet 雖然解決雙向溝通的問題,但為了達到長時間的連線,花費太多效能控制連線的生命週期,導致資料傳輸的效率降低。 ![evolution](https://i.imgur.com/SWfJJS3.png) `💡HTML5開始提供瀏覽器與伺服器全雙工通訊的WebSocket(與HTTP不同的協定)` >WebSocket * HTML5標準中的新網頁傳輸方式,可在一條連線上提供全雙工、雙向資料傳輸,也解決Comet架構容易出錯的問題(例如:Client端網路不穩,連線突然中斷,傳送中的訊息就會不見),架構也比以往簡單。 ![HTTP vs WebSocket](https://i.imgur.com/fJvlOvv.png) [淺談 Polling, Comet, Websocket](http://f2e-veru.com/%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98/%E6%B7%BA%E8%AB%87%20Polling,%20Comet,%20Websocket/) [Best practices of building data streaming API](https://www.slideshare.net/kslisenko/best-practices-of-building-server-to-client-data-streaming) [WebSocket新一代網路傳輸技術](http://www.syscom.com.tw/ePaper_New_Content.aspx?id=368&EPID=194&TableName=sgEPArticle) [iframe介紹 深入了解/iframe有那些缺點/iframe傳值問題](https://medium.com/@small2883/iframe%E4%BB%8B%E7%B4%B9-%E6%B7%B1%E5%85%A5%E4%BA%86%E8%A7%A3-iframe%E6%9C%89%E9%82%A3%E4%BA%9B%E7%BC%BA%E9%BB%9E-iframe%E5%82%B3%E5%80%BC%E5%95%8F%E9%A1%8C-579e6c113436) [網頁設計之HTML iframe框架](https://www.webdesigns.com.tw/html_iframe.asp) --- ## 什麼是SignalR? [SignalR](https://github.com/signalr)是ASP.NET平台,提供能簡化新增即時web功能的應用程式開放原始碼程式庫。 SignalR提供一個簡單使用的高階API,實現伺服器與瀏覽器間的遠程程序呼叫(RPC,Remote Procedure Call),在這架構下伺服器端以.NET開發,瀏覽器主以JavaSricpt開發。 SignalR會針對目前執行的瀏覽器進行判斷,找到與伺服器間最適合的建立連線方式,SignalR 會優先的選用 WebSocket 技術與伺服器溝通,當然伺服器上的通訊協定管理方式也內含在 ASP.NET SignalR 的組件庫中,開發人員就不需要針對目前走的是 WebSocket、Comet 還是 Polling 進行特別的處理,所有的 code 都透過 ASP.NET SignalR高階的 API 進行訊息傳遞。 ![image alt](https://i.imgur.com/uv6EJjG.png) 應用面: 遊戲、買賣(股票、拍賣)、地圖(GPS)、監控(儀表板、天氣、旅遊警告)、聊天室、 [SignalR 再次超越你對 Web 的想像 – 建立即時互動的 Web](https://blogs.msdn.microsoft.com/msdntaiwan/2013/09/09/signalr-web-web/) --- ## 如何實現SignalR? 後端會實作一個SignalR Hub類別,Hub負責產生與前端JavaScript綁定的相關定義。 後端要傳送訊息到瀏覽器,自訂Hub類別方法ex:Send,用Clients屬性決定訊息發布範圍(全部、群組...), ![推送訊息概念圖](https://i.imgur.com/u3A5YEs.png) 前端也必須要實作與後端建立連線,以下部分皆是與前端相關的實作。 [A1 SignalR 實作參考文章(SignalR Clients的部分)](https://blog.johnwu.cc/article/asp-net-core-angular-4-%E6%95%99%E5%AD%B8-signalr.html) ### step1. 安裝signalr套件 在Angular CLI 輸入 `npm install signalr` ![npm install signalr](https://i.imgur.com/RNRDKa4.png) 安裝完,node_modules底下就可以看到signalr套件了(若沒看到`ng build` 就有了) ![node_modules\signalr](https://i.imgur.com/wjCcLa8.png) 再到angular.json內添加signalr套件路徑 ```json "scripts": [ "node_modules/jquery/dist/jquery.min.js", ] ``` ### step2. 新增SignalrService 因為signalr是透過jQuery調用,為了方便使Angular使用新增一個service來包裝,下面只放了一些最基本的方法`開啟連線/接收訊息/傳送訊息`,有其他的需求要自己來。 #程式碼需依自己需求調整 signalr.service.ts ```typescript= import { Injectable, Inject } from "@angular/core"; import { Subject, Observable } from "rxjs"; declare var $: any; export enum ConnectionStatus { Connected = 1, Disconnected = 2, Error = 3 } @Injectable() export class SignalRService { private hubConnection: any; private hubProxy: any; error: Observable<any>; constructor() { if ($ === undefined || $.hubConnection === undefined) { throw new Error('The "$" or the "$.hubConnection" are not defined...'); } /* #A1 ---str*/ // this.hubConnection = $.hubConnection(); // this.hubConnection.url = `//${window.location.host}/signalr`; this.hubConnection = $.hubConnection('https://a1notice-staging.azurewebsites.net/'); /* #A1 ---end*/ } /**開啟連線*/ start(hubName: string, debug: boolean = false): Observable<ConnectionStatus> { this.hubConnection.logging = debug; this.hubProxy = this.hubConnection.createHubProxy(hubName); let errorSubject = new Subject<any>(); this.error = errorSubject.asObservable(); this.hubConnection.error((error: any) => errorSubject.next(error)); let subject = new Subject<ConnectionStatus>(); let observer = subject.asObservable(); this.hubConnection.start({ withCredentials: false }) .done(() => { // 成功連線 // #可以在這邊用invoke給後端token之類的,做訊息推送群組 /* #A1 ---str*/ this.hubProxy.invoke('registered', this.test.TenantID + '-' + this.test.ID, 'A1Web-testing').done(() => { this.hubProxy.invoke('joinGroup', this.test.TenantID + '-' + this.test.ID, this.test.Name).done(() => { this.hubProxy.invoke('UpdateUserInfo', '').done(() => {}); }); }); /* #A1 ---end*/ subject.next(ConnectionStatus.Connected); }) .fail((error: any) => {subject.error(error) // 錯誤處理 }); return observer; } /**監聽是否有回傳值*/ addEventListener(eventName: string): Observable<any> { let subject = new Subject<any>(); let observer = subject.asObservable(); this.hubProxy.on(eventName, (data: any) => subject.next(data)); return observer; } /**傳送訊息*/ invoke(eventName: string, data: any): void { this.hubProxy.invoke(eventName, data); } } ``` 接下來設定$(jquery) 1. 在Angular CLI輸入`npm i @types/jquery`,並在tsconfig.json檔案的types加上jquery ```json "types":[ "jquery" ] ``` 2. Angular CLI輸入`npm install jquery`,並在angular.json內添加jquery套件路徑 ```json "scripts": [ "node_modules/jquery/dist/jquery.min.js", "node_modules/signalr/jquery.signalR.js", ] ``` > 以上步驟都完成後,現在就可以在Angular裡使用signalR了! ### step3. 使用SignalR 這部分我是寫在AppComponent裡面,看個人需求調整。 ```typescript constructor( private signalrService: SignalrService, ) { // 啟動 SignalR Client 跟 Server 連線 名稱#A1('spokesman')要跟後端一致 this.signalrService.start('spokesman', true).subscribe( (connectionStatus: ConnectionStatus) => { console.log(`[signalr] start() - done - ${connectionStatus}`); }, (error: any) => { console.log(`[signalr] start() - fail - ${error}`); }); // 監聽發生錯誤時的事件 this.signalrService.error.subscribe((error: any) => { console.log(`[signalr] error - ${error}`); }); // 監聽 Server 送來的事件,名稱#A1('addMessage')要跟 ChatHub 對應 this.signalrService.addEventListener('addMessage').subscribe( (message) => { // 收到事件 // #處理訊息,message是後端傳送的訊息 }); } ``` > 接下來就是來查看是否成功接送訊息。 ### step4. 透過開發者工具觀察連線 透過DevTool(F12)可以看到webSocket建立好的連線 **`💡 紅色的箭頭是伺服器端傳送的資料、綠色是使用者端傳送的資料`** ![連線建立](https://i.imgur.com/CMuGp5f.png) 如果接送的訊息無誤恭喜你實作成功啦~~ :::success 💡上面的程式碼內註解有用#A1是我使用A1專案現有的signalr來測試的 ::: ## 錯誤集 ### #jQuery問題 :::warning **⚡Cannot find name '$' / $ is not defined /** ::: :::info ⛏ 補給站 [Why does JQuery have dollar signs everywhere?](https://stackoverflow.com/questions/10787342/why-does-jquery-have-dollar-signs-everywhere) ```javascript // These are the same barring your using noConflict (more below) var divs = $("div"); // Find all divs var divs = jQuery("div"); // Also find all divs, because console.log($ === jQuery); // "true" ``` >$是jQuery的簡寫 ::: 會出現此錯誤是因為$是js的語法,所以我們必須做一些設定 `npm i @types/jquery` : package.json > dependencies 會自動添加`"@types/jquery": "^3.3.31",` 此外,在tsconfig.json檔案的types加上jquery(此步驟還要再查是什麼意義) ```json "types":[ "jquery" ] ``` 設定完成後,會發現angular中她還會報紅,但她是可以正常使用的,那想在angular中想要他不報紅可以加上`declare var $: any`。 ### #hubConnection錯誤 :::warning **⚡$.hubConnection' are not defined / Property 'hubConnection' does not exist on type 'JQueryStatic'** ::: 因為hubConnection是使用/node_modules/signalr/jquery.signalR.js裡的function,所以出現此$.hubConnenction的錯誤表示還沒有下載signalR的程式庫。 `npm install jquery` : package.json > deoendencies會自動添加`"jquery": "^3.4.1"` (這不太確定是否必要,我目前在都先有安裝,之後在多次測試) `npm install signalr` : package.json > deoendencies會自動添加`"signalr": "^2.4.1"` cli只有幫我們在package.json添加依賴,要打包使用的.JS檔還是要自己手動添加進angular-cli.json>scripts ```json "scripts": [ "../node_modules/jquery/dist/jquery.min.js", "../node_modules/signalr/jquery.signalR.js", ] ``` scripts添加時若出現此錯誤,依下面修正 :::warning ⚡[ENOENT: no such file or directory](https://stackoverflow.com/questions/50366935/enoent-no-such-file-or-directory-for-node-modules-jquery-dist-jquery-min-js) ::: > ```json > "scripts": [ > "node_modules/jquery/dist/jquery.min.js", > "node_modules/signalr/jquery.signalR.js", > ] > ``` > * 先添加"node_modules/signalr/jquery.signalR.js", `ng build` > * 再添加"node_modules/jquery/dist/jquery.min.js", `ng build` > * `注意!` jquery.min.js必須在jquery.signalR.js上面。 > > 我也不知道為什麼不能一次加上去就好QAQ[name=Ruby Lin] ### #CORS policy問題 :::warning **⚡XMLHttpRequest at `'https://...'` from origin `'http://localhost:4200'` has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.** ::: 將start的寫法做點修正: `在start()裡加{withCredentials: false}` `🤔不過不確定這方法是否有其他影響` ```typescript start(hubName: string, debug: boolean = false): Observable<ConnectionStatus>{ this.hubProxy = this.hubConnection.createHubProxy('spokesman'); console.log('signalR start', this.hubProxy); const errorSubject = new Subject<any>(); this.error = errorSubject.asObservable(); this.hubConnection.error((error: any) => errorSubject.next(error)); const subject = new Subject<ConnectionStatus>(); const observer = subject.asObservable(); /*start()裡加{withCredentials: false} ---str*/ //this.hubConnection.start() this.hubConnection.start({ withCredentials: false }) /*start()裡加{withCredentials: false} ---end*/ .done(() => subject.next(ConnectionStatus.Connected)) .fail((error: any) => subject.error(error)); return observer; } ``` :::spoiler 補給包 [CORS policy issue with angular 7 and ASP.NET core 2.2 using SIGNAL R](https://stackoverflow.com/questions/54808822/cors-policy-issue-with-angular-7-and-asp-net-core-2-2-using-signal-r) [CORS and the Access-Control-Allow-Origin response header](https://portswigger.net/web-security/cors/access-control-allow-origin) [跨來源資源共用(CORS)](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS) 此篇超推薦👍🏻 [輕鬆理解Ajax與跨來源請求](https://blog.techbridge.cc/2017/05/20/api-ajax-cors-and-jsonp/) :::