在nuxt裡起一個socket.io server與後續Apache的Proxy設定

最近隨著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網站的瞬間流量還很低才敢先這樣搞搞看,之後有什麼心得再來更新。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料