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又有意料之外的錯誤,真的相當棘手,所幸網路資料東拼西湊下,釐清不少框架的使用方式
最後小小總結一下本次經驗

  1. 自己對WebSocket不熟悉,邊做邊學。在處理問題時難以直接抓出可能問題點
  2. 後端用 Socket.io ,會綁定前端也得用 Socket.io
  3. Angular SSR使用 Socket.io ,須runOutsideAngular,但監聽事件取到的回傳值,要回到Angular檢測。
  4. 前後端皆使用 Socket.io 的話,配合fromEvent可降低層層嵌套
  5. Socket.io 效能不佳,若要求效能,直接用ws
  6. 若要用ws,得另花心力處理些較基礎的事務,如前端斷線嘗試重連次數、秒數
  7. 由於我是先做好 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)