iT邦幫忙

2024 iThome 鐵人賽

DAY 22
0
Odoo

Odoo 部署策略系列 第 22

odoo LiveChat 實時更新:使用 Nginx 反向代理處理 WebSocket

  • 分享至 

  • xImage
  •  

如果大家在我們現在的佈署層(WebApp-Deployment)上測試過 LiveChat 功能,可能會發現它跟直接在整合層(odooBundle-Codebase)測試的行為不太一樣。在佈署的環境中,LiveChat 無法看到對方正在打字,也無法即時收到訊息,需要重新整理網頁才看的到。這是因為原本在 odoo 容器上直接曝露的 8072 埠口現在被擋在反向代理(reverse proxy)後面了,這一章就要來處理這個問題。

https://ithelp.ithome.com.tw/upload/images/20241006/20168935F3eDRNWnFM.png

題外話一: 這就是為什麼佈署層還要再有一個 odoo-dev 的容器來完整複製真實環境做測試,而不是都在整合層做測試就好。像是這種問題如果沒有先在佈署層做測試,就無法被發現。

題外話二: 其實我搞不太懂為什麼直接在整合層運行的時候這個功能可以正常。基本上在整合層運行時,我的 odoo.conf 幾乎都是預設的,也就是運行在 Multi-Thread 模式,那它的 8072 埠口理論上也沒有人在監聽(實測也沒有),但訊息還是可以即時傳遞。我查看瀏覽器的開發者資訊也看不出個所以然,這就請專家再來補充了。我下面還是針對佈署層做的設定。


WebSocket vs. HTTP

在進入設定之前,先簡單介紹一下 WebSocketHTTP 的差異。

HTTP(HyperText Transfer Protocol):

  • 請求-回應模式: HTTP 是一種基於請求-回應模式的協議。客戶端發送請求到伺服器,伺服器處理後回傳回應,連線隨即結束。
  • 無狀態協議: 每一次的請求都是獨立的,伺服器不會主動與客戶端保持連線,也不會主動推送資料。

WebSocket:

  • 全雙工通信: WebSocket 是一種在單一 TCP 連線上進行全雙工通信的協議。這意味著客戶端和伺服器可以同時傳送和接收資料。
  • 持久連線: 一旦 WebSocket 連線建立後,連線將一直保持,直到主動關閉。這使得伺服器可以即時地將資料推送給客戶端。
  • 效率高: 相較於輪詢(polling)或長輪詢(long polling),WebSocket 減少了網路開銷和延遲,適合於需要即時更新的應用。

為什麼 odoo 的 LiveChat 需要 WebSocket?

  • 即時性: LiveChat 需要即時傳遞訊息、打字狀態等資訊,WebSocket 能夠滿足這種即時通信的需求。
  • 節省資源: 使用 WebSocket 可以減少頻繁建立 HTTP 連線的開銷,提高應用的效率。

Nginx 反向代理設定

在多行程模式下,odoo 會自動啟動一個專用的 LiveChat worker,並在 --gevent-port 上監聽。預設情況下,HTTP 請求會繼續由普通的 HTTP workers 處理,而非 LiveChat worker。因此,我們必須在 odoo 前端部署一個代理伺服器,將路徑以 /websocket/ 開頭的請求重定向到 LiveChat worker。

以下是最終的 Nginx 反向代理設定,這是為了讓 LiveChat 正確處理 WebSocket 請求:

http{
    # websocket upgrade
    map $http_upgrade $connection_upgrade {
        default upgrade; # 如果有 upgrade 標頭,則允許升級為 WebSocket
        ''      close;   # 如果沒有 upgrade 標頭,則關閉連線
    }

    # odoo-dev.internal.example.com and odoo-dev.internal.example.local - Internal Network Only (Development)
    server {
        listen 443 ssl;
        server_name odoo-dev.internal.example.com odoo-dev.internal.example.local;

        ssl_certificate /etc/nginx/certs/odoo_Certificate.crt;
        ssl_certificate_key /etc/nginx/certs/odoo_Certificate.key;

        location /websocket {
            set $upstream odoo-dev:8072; # 定義上游伺服器為 odoo-dev,並指定 8072 埠(用於 WebSocket)
            proxy_pass http://$upstream;
            proxy_set_header Upgrade $http_upgrade; # 將 Upgrade 標頭傳遞給上游,以進行 WebSocket 升級
            proxy_set_header Connection $connection_upgrade; # 設定連線標頭,處理升級或關閉連線
            proxy_set_header X-Forwarded-Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;

            # Handle upstream being down
            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
            proxy_connect_timeout 720s; # 連線超時設定為 720 秒
            proxy_read_timeout 720s;
            proxy_send_timeout 720s;
            proxy_intercept_errors on; # 啟用錯誤攔截,以便 Nginx 顯示自訂錯誤頁面
        }

        location / {
            set $upstream odoo-dev:8069; # 設定上游伺服器為 odoo-dev,指定 8069 埠(HTTP 請求)
            proxy_pass http://$upstream;
            proxy_redirect off; # 關閉自動重定向功能(讓 odoo 自己處理)
            proxy_set_header X-Forwarded-Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;

            # Handle upstream being down
            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
            proxy_connect_timeout 1s; # 連線超時設定為 1 秒
            proxy_read_timeout 1s;
            proxy_send_timeout 1s;
            proxy_intercept_errors on;
        }

        error_page 502 503 504 /50x.html; # 定義當 502、503、504 錯誤時顯示的錯誤頁面
        location = /50x.html {
            root /usr/share/nginx/html;
            internal; # 標記這個位置為內部使用,外部無法直接訪問
        }
    }
}

設定完成後,回到我們的 LiveChat,就可以看到對方正在打字的資訊,也可以即時收到訊息了。

LiveChat 即時更新示例

收到即時訊息示例

另外,觀察瀏覽器的開發者工具,也可以看到 websocket_worker_bundle 正在運作。(看時間軸應該這個就是我們建立的 websocket 啦,我其實不是很確定為什麼不是 /websocket/

瀏覽器開發者工具示例


設定沒成功的疑難排解

  1. 測試 WebSocket 是否能成功建立連線

    可以在本地的 Python 3 環境中安裝 websocket-client 套件,並執行以下腳本來測試:

     import websocket
     import ssl
    
     def on_message(ws, message):
         print(f"Received: {message}")
    
     def on_error(ws, error):
         print(f"Error: {error}")
    
     def on_close(ws, close_status_code, close_msg):
         print("### Connection closed ###")
    
     def on_open(ws):
         print("### Connection opened ###")
         ws.send("Hello WebSocket!")
    
     if __name__ == "__main__":
         websocket.enableTrace(True)
         ws = websocket.WebSocketApp("wss://odoo-dev.internal.example.local/websocket/",
                                     on_open=on_open,
                                     on_message=on_message,
                                     on_error=on_error,
                                     on_close=on_close)
         ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
    

    正常情況下,應該可以在輸出觀察到 101 SWITCHING PROTOCOLS

     --- request header ---
     GET /websocket/ HTTP/1.1
     Upgrade: websocket
     Host: odoo-dev.internal.example.local
     Origin: https://odoo-dev.internal.example.local
     Sec-WebSocket-Key: 1/L6VxSHiSmCIuZLQ9YR6g==
     Sec-WebSocket-Version: 13
     Connection: Upgrade
    
     -----------------------
     --- response header ---
     HTTP/1.1 101 SWITCHING PROTOCOLS
     Server: nginx/1.27.1
     Date: Sat, 05 Oct 2024 18:45:08 GMT
     Content-Type: text/html; charset=utf-8
     Connection: upgrade
     Upgrade: websocket
     Sec-WebSocket-Accept: ZHFyepPy7WSNka7h1LfJds2w3tM=
     Access-Control-Allow-Origin: *
     Access-Control-Allow-Methods: GET, POST
     Set-Cookie: session_id=781e5c5782382ec91170975915de08be4de44de6; Expires=Sun, 05 Oct 2025 18:45:08 GMT; Max-Age=604800; HttpOnly; Path=/
     X-Content-Type-Options: nosniff
     -----------------------
     Websocket connected
     ### Connection opened ###
     ++Sent raw: b'\x81\x90\x1dK\xf9/U.\x95Crk\xaeJ\x7f\x18\x96Lv.\x8d\x0e'
     ++Sent decoded: fin=1 opcode=1 data=b'Hello WebSocket!'
    
  2. 確認 odoo.conf 的 --proxy-mode 是否設定

    確保在 odoo.conf 中啟用了 proxy_mode = True,讓 odoo 使用真正的客戶端標頭(如 hostname、scheme 和 IP)來取代代理的標頭。

  3. 檢查瀏覽器對憑證的信任

    確認您的瀏覽器已信任使用的 SSL 憑證。如果瀏覽器不信任該憑證,可能會導致 WebSocket 建立連線失敗。可以查看瀏覽器的 Console 是否有相關錯誤訊息。


接下來的章節也是繼續關於安全跟效能的內容。


上一篇
odoo.conf 效能調整實驗:多行程與資料庫連線設定
下一篇
勘誤:Timeout 設定與資料庫自動初始化邏輯修正
系列文
Odoo 部署策略30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言