Categories
程式開發

websocket 是怎麼連接的


背景

最近項目新增了一個websocket 服務,用nginx 做了一個簡單的端口轉發,然後調用的時候發現報錯:

錯誤:意外的服務器響應:426

搜索引擎走一波,找到幾篇相關文章:

https://nginx.org/en/docs/http/websocket.html

https://www.nginx.com/blog/websocket-nginx

解決方式也很簡單,根據第一篇文章的說明,只要增加轉發響應頭的配置:

proxy_set_header升級$ http_upgrade;

proxy_set_header連接“升級”;

因為websocket 的連接建立是基於HTTP/1.1 的,所以有必要製定http 協議:

proxy_http_version:1.1

當然這不是必須的,如果服務默認是HTTP/1.1 的話

具體配置如下:

http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
...

location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

但多年踩坑經驗告訴我,如果不了解某項技術的細節,早晚還要掉進下一個坑。所以打算好好研究下這裡面的執行邏輯。

Websocket 建立連接的過程分析

下圖是websocket 建立連接-數據傳輸-斷開連接的整個過程

websocket 是怎麼連接的 1

Http1.1 支持協議轉換機制,具體說明參考 https://tools.ietf.org/html/rfc2616#section-14.42“。

首先客戶端發起GET 請求

websocket 是怎麼連接的 2

這裡主要關注Connection 和Upgrade 兩個請求頭:

設置Connection 頭的值為Upgrade 來指示這是一個升級請求Upgrade 頭制定要升級到的協議

服務端響應

如果服務端決定升級這次連接,就會返回101 Switching Protocols 響應狀態

websocket 是怎麼連接的 3

經過以上過程,Websocket 的連接就建立成功了

但為什麼nginx 代理需要明確配置Connection 和Upgrade 請求頭呢?

原來,http 協議將消息頭分為了兩類:end-to-end 和hop-by-hop,具體說明參考:https://tools.ietf.org/html/rfc2616#section-13.5.1

他們的特性如下:

end-to-end該類型的消息頭會被代理轉發該類型的消息頭會被緩存hop-to-hop: 該類型的消息頭不會被代理轉發該類型的消息頭不會被緩存

這樣就不難理解,nginx 配置代理轉發後,默認並不能轉發Connection 和Upgrade 消息頭,這樣轉發後的請求到達Websockt 服務後就沒有這兩個頭,因此就不能由http 協議升級為websocket 協議。因此需要在nginx 中進行手動配置。

以下為HTTP/1.1 中hop-to-hop 類型的消息頭:Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailers, Transfer-Encoding, Upgrade

連接為什麼斷開了

從上面的抓包可以看到,最終websocket 建立的連接被自動斷開了,文檔中也做出了解釋,這是nginx 的機制。

默認情況下,如果被代理的服務60 秒內沒有進行數據傳輸,連接就會被斷開。這個超時時間可以通過proxy_read_timeout 指令設置。

如果要保持建立的連接不斷開,就要通過心跳包等手段進行維持。

注意

該升級機制只是HTTP/1.1 有效,HTTP/2 已不支持該機制nginx 會斷開長時間沒有數據傳輸的連接