Angular SSR + NestJs + Socket.io + WebSocket踩坑記
架構說明
使用Nest.Js作者提供的Universal-Nest為基礎做Side project
前端是Angular SSR (Angular Universal)
後端為Nest.Js
並使用 Socket.io 串連部份前後端的互動邏輯
SSR問題
後端部份照著Nest.Js官方文件,基本上就可以輕鬆搞定
而在前端Angular使用 Socket.io ,一執行就馬上出現錯誤!
但從網路上查到的範例,幾乎都沒發生類似問題!後來猜測是SSR的關係,加上關鍵字 Socket.io SSR後,順利找到解法( socket.io with ssr )
配合ngZone.runOutsideAngular在外圍執行 Socket.io ,在on監聽事件時,再用ngZone.run讓回傳資料可以回到Angular的檢測範圍
功能是可以運作了,若是有相當多的監聽事件,會發現這樣的寫法其實頗冗長。
於是開始試著以正規的Angular方式改寫…
嘗試使用npm套件
查詢了網路上寫法,會發現Angular是直接使用RxJs-WebSocket,並沒有針對WebSocket有其他的現成封裝。
不幸的是,網路上的相關示例幾乎都是RxJs5的寫法,要直接使用還得改成6的寫法。
第一步就讓人有點受挫
嘗試搜尋npm看是否有現成Module可以使用,降低自己改錯的機會。引入後就會發現一直遇到WebSocket is not defined。
一時之間也搞不清楚是套件有問題還是自己用錯方式…於是再去追查解法:
WebSocket is not defined
怎麼查就也只有這篇,在全局定義WebSocket,還得另外安裝另個套件ws。
在polyfill.ts定義後,仍是持續噴此錯誤,試到最後總是不行。
難以斷定是套件問題還是SSR關係
最後暫時放棄其他Module,嘗試自己開發…
嘗試用rxjs-websocket改寫
於是開始研究RxJs-WebSocket官方文件。運氣不錯有中文的教學文,而且也寫的相當清晰: 浅淡 RxJS WebSocket
但照著做之後,還是一樣會遇到前面的遇過的WebSocket is not defined。不死心再從polyfill.ts再定義一次!
這次則噴不同的錯誤了XMLHttpRequest is not defined。
雖然還是不能運作,但至少換了個錯誤訊息!往前邁進一步了!
再去追查,解法也跟前一篇一樣,全局定義此參數就好:
XMLHttpRequest is not defined
但後來就一直卡在這一步,做不下去。查不到任何其他解法…
大約又卡了2天,突發奇想試了個關鍵字:Angular Connect to Socket.io
居然發現 socket.io 與原生WebSocket無法界接!!!
Stack Overflow/RxJs webSocket not able to connect to a websocket server implemented with socket.io
這下就說的通了!為何前面怎麼嘗試都會卡關!除了SSR的問題之外,自身的後端已綁定了 Socket.io
為了驗證自己的想法正確,於是改寫後端…
嘗試後端改用ws
所幸Nest.Js除了 Socket.io 之外,亦提供了ws套件可直接使用,不必自己從Adaptor開始兜
小改一下後,後端就順利運作了!
再回到Angular去接,果然順利對接上!解決問題!
雖然讓前端寫的比較正規了,但整體使用起來,就沒有 socket.io 那樣無腦,除了得讓自己的交換格式直接對上{event, data}
外,前端也得額外自行開發重連、監聽事件的部份。後端有些判斷也得自行處理。
結論
籍著這次經驗,更加了解WebSocket的運作方式,也對於Nest.Js與Angular的掌握度提高了不少
而 Socket.io 最大缺點就是效能相對低落
若是內部自用,或評估使用量不會太大的小服務
用 socket.io 會讓前後端使用簡單。
若追求效能,則後端直接使用ws吧!
而ws自己也是主打高效能WebSocket的套件!
由於我的Side project是屬於自用的小服務,基本上不會超過十個人,最後仍回歸到 Socket.io 的作法
後來發現可以配合RxJs的fromEvent來監聽 Socket.io ,降低了Angular配合ngZone的層層嵌套複雜感
也將自己的經驗反饋回GitHub討論區的socket.io with ssr
若有需要RxJs的fromEvent
參考程式碼,可參閱上方連結
後記
也許是因為Nest.Js算是滿新的框架,大規模應用還不算太多。這方面的相關資訊仍屬稀少。於是記錄並分享自己在WebSocket的踩坑經驗
而像這種配合SSR又有意料之外的錯誤,真的相當棘手,所幸網路資料東拼西湊下,釐清不少框架的使用方式
最後小小總結一下本次經驗
- 自己對WebSocket不熟悉,邊做邊學。在處理問題時難以直接抓出可能問題點
- 後端用 Socket.io ,會綁定前端也得用 Socket.io
- Angular SSR使用 Socket.io ,須runOutsideAngular,但監聽事件取到的回傳值,要回到Angular檢測。
- 前後端皆使用 Socket.io 的話,配合fromEvent可降低層層嵌套
- Socket.io 效能不佳,若要求效能,直接用ws
- 若要用ws,得另花心力處理些較基礎的事務,如前端斷線嘗試重連次數、秒數
- 由於我是先做好 Socket.io 版本並運行一段時間後再改寫。所以格式仍以 Socket.io 的樣子為主。雖沒繼續實作下去,但推測後端直接採ws的話,應該是不會綁定對接格式!
參考資料
浅淡 RxJS WebSocket
GitHub/socket.io with ssr
GitHub/ReferenceError: WebSocket is not defined when using RxJs 6.2
stack overflow/RxJs webSocket not able to connect to a websocket server implemented with socket.io
GitHub/Angular 4.3 HttpClient throws ‘XMLHttpRequest is not defined’ in Node environment (Angular Universal)