最近隨著LiveTRA的MAU越來越高,怕Firebase RealtimeDatabase會產生一些額外的支出,開始萌生想把他拔掉的想法。RealtimeDatabase主要是負責傳遞顯示於時刻表最上方的警示訊息,平常不會顯示,但當台鐵發生什麼突發事件時,可以做到立即告知使用者的效果。總覺得拔掉有點可惜,又不想單純只用傳統API去撈,這樣就失去即時性。所以索性最後自己用socket.io刻一個socket server。
Nuxt Module
我的構想是,把socket server寫在跟前台同一個nuxt專案中,且當nuxt啟動(npm start)時,最好socket也能一起被啟動,這樣我就不用再另外去起socket server。
為了可以跟nuxt一起啟動,想到似乎可以用nuxt的serverMiddleware或者是module來做。先說,這絕對不是一個好方法。除了有socket server掛掉時無法單獨重開的缺點外,socket連線過多時造成主機loading過大時,影響到的也不單止是socket server,連同前台也會被影響。會選擇這樣做,單純只是因為這是個省成本的方式。畢竟現在網站沒有收入orz(拜託快幫我點廣告XDD
本來想用serverMiddleware做,因為先前有搭配使用express做API接口很方便。開發環境中一旦程式碼修改也會立即看到效果,不需要重新npm run dev。用express做serverMiddleware時,是export一個express物件給nuxt,而非直接監聽某個port。這個express物件會跟nuxt的web server做類似合併的動作。也就是說express跟nuxt是聽同一個port。
但socket.io沒有辦法這樣做,必須要在serverMiddleware中直接聽某個port。一旦更新程式碼,serverMiddleware被重新執行,再次幀聽的時候,會發現某個port已經佔用了,然後整個nuxt就crash。這是因為第一次聽的那個socket沒有結束。這時就需要重啟nuxt (npm run dev)。若在程式碼中先把目前正在佔用port的process kill掉,那一樣整個nuxt都會停止。因為在serveMiddleware中被運行的socket server跟nuxt是同一個process。
用module做的話,雖然不至於一更新程式碼就讓nuxt掛掉。但更新後還是得重啟npm run dev,其實也算是大同小異。但因為我只是單純想要在nuxt啟動時一起啟動socket.io,也不想一更新socket的程式碼,就直接害nuxt掛掉,最後姑且先選擇用module做。也許之後有找到更方便的方法再更新上來。
開工吧!先開一個modules資料夾,新增一個檔案socket.js,到nuxt.config.js modules新增module config:
// Modules: https://go.nuxtjs.dev/config-modules modules: [ // https://go.nuxtjs.dev/axios '@nuxtjs/axios', // '@nuxtjs/proxy', // https://go.nuxtjs.dev/pwa '@nuxtjs/pwa', // i18n 'nuxt-i18n', // socket '~/modules/socket' // 新增剛剛建立的檔案 ]
安裝socket.io相依套件:
npm install socket.io socket.io-client
開始寫Socket Server,在 socket.js 中透過 nuxt 的 “listen” hook,讓nuxt server啟動時,也一起啟動socket server:
// 引入socket.io import { createServer } from 'http' import { Server } from 'socket.io' export default function (moduleOptions) { // 這個hook就是當nuxt server開始listen port的時候會執行 this.nuxt.hook('listen', () => { const httpServer = createServer() const io = new Server(httpServer, { cors: { // 這邊是跨網域的設定,可設定多組 origins: ['https://xxxxxx.xxx'] } }) io.on('connection', (socket) => { // socket event socket.on('my_event', (data) => { // do something }) // emit a event socket.emit('some_event', 'yoyoyo') }) // 輸入你要聽的port號 io.listen(1234) }) }
前端的部分:
// 引入socket.io client import { io } from 'socket.io-client' export default { data () { return { // 給個變數存socket實體 socket: null } }, mounted () { this.initSocket() }, methods: { // 開始socket連線 initSocket () { // 我這邊用環境變數寫入socket的網址 this.socket = io(this.$config.LIVETRA_SOCKET) this.socket.on('some_event', (data) => { alert(data) }) } } }
前端跟Socket Server的重點大概就這些。接下來要部署到beta機來測試了!
Apache Proxy設定
先把code拉下來到測試站,用pm2啟動後,我們就要來進行apache的設定囉。以下是socket.io官方提供的apache設定:
Listen 80 ServerName example.com LoadModule mpm_event_module modules/mod_mpm_event.so LoadModule authn_file_module modules/mod_authn_file.so LoadModule authn_core_module modules/mod_authn_core.so LoadModule authz_host_module modules/mod_authz_host.so LoadModule authz_groupfile_module modules/mod_authz_groupfile.so LoadModule authz_user_module modules/mod_authz_user.so LoadModule authz_core_module modules/mod_authz_core.so LoadModule headers_module modules/mod_headers.so LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_balancer_module modules/mod_proxy_balancer.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so LoadModule rewrite_module modules/mod_rewrite.so LoadModule slotmem_shm_module modules/mod_slotmem_shm.so LoadModule unixd_module modules/mod_unixd.so User daemon Group daemon ProxyPass / http://localhost:3000/ RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L] ProxyTimeout 3
但因為我是要放在VirtualHost中,並且我對於apache的設定,算是只能複製貼上的那種程度,本來想說應該只要放以下這些吧:
<VirtualHost *:80> ServerAdmin boggyjan@gmail.com ServerName xxx.xxx.xxx ProxyPass / http://localhost:xxxx/ RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/?(.*) "ws://localhost:xxxx/$1" [P,L] ProxyTimeout 30 </VirtualHost>
reload了apache後測了一下,發現http可以連,但ws卻連不到。看了apache的說明,才想到要確認一下mod_proxy_wstunnel module有沒有開。檢查一下 /etc/apache2/mod-enabled發現果然沒有,執行 sudo a2enmod proxy_wstunnel 再執行一次 systemctl reload apache2 即可。
關於這個架構目前還在測試中,因為LiveTRA網站的瞬間流量還很低才敢先這樣搞搞看,之後有什麼心得再來更新。