如果大家在我們現在的佈署層(WebApp-Deployment)上測試過 LiveChat 功能,可能會發現它跟直接在整合層(odooBundle-Codebase)測試的行為不太一樣。在佈署的環境中,LiveChat 無法看到對方正在打字,也無法即時收到訊息,需要重新整理網頁才看的到。這是因為原本在 odoo 容器上直接曝露的 8072 埠口現在被擋在反向代理(reverse proxy)後面了,這一章就要來處理這個問題。
題外話一: 這就是為什麼佈署層還要再有一個 odoo-dev 的容器來完整複製真實環境做測試,而不是都在整合層做測試就好。像是這種問題如果沒有先在佈署層做測試,就無法被發現。
題外話二: 其實我搞不太懂為什麼直接在整合層運行的時候這個功能可以正常。基本上在整合層運行時,我的 odoo.conf
幾乎都是預設的,也就是運行在 Multi-Thread 模式,那它的 8072 埠口理論上也沒有人在監聽(實測也沒有),但訊息還是可以即時傳遞。我查看瀏覽器的開發者資訊也看不出個所以然,這就請專家再來補充了。我下面還是針對佈署層做的設定。
在進入設定之前,先簡單介紹一下 WebSocket 與 HTTP 的差異。
HTTP(HyperText Transfer Protocol):
WebSocket:
為什麼 odoo 的 LiveChat 需要 WebSocket?
在多行程模式下,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,就可以看到對方正在打字的資訊,也可以即時收到訊息了。
另外,觀察瀏覽器的開發者工具,也可以看到 websocket_worker_bundle
正在運作。(看時間軸應該這個就是我們建立的 websocket 啦,我其實不是很確定為什麼不是 /websocket/
)
測試 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!'
確認 odoo.conf 的 --proxy-mode
是否設定
確保在 odoo.conf
中啟用了 proxy_mode = True
,讓 odoo 使用真正的客戶端標頭(如 hostname、scheme 和 IP)來取代代理的標頭。
檢查瀏覽器對憑證的信任
確認您的瀏覽器已信任使用的 SSL 憑證。如果瀏覽器不信任該憑證,可能會導致 WebSocket 建立連線失敗。可以查看瀏覽器的 Console 是否有相關錯誤訊息。
接下來的章節也是繼續關於安全跟效能的內容。