使用 Golang 在 Knative 上建構 WebSocket 應用程式 ¶
發佈於:2024-09-11
使用 Golang 在 Knative 上建構 WebSocket 應用程式¶
作者:Matthias Weßendorf,Red Hat 資深首席軟體工程師
Knative Serving 支援多種協定來執行無伺服器工作負載,例如 HTTP
、HTTP/2
、gRPC
或 WebSocket
。 WebSocket 協定允許客戶端應用程式和伺服器之間透過單一 TCP 連線進行全雙工通訊。 WebSocket 協定以 HTTP「升級」請求開始,然後將連線從 HTTP 切換到 WebSocket 協定,允許對等點之間進行即時雙向通訊。 在本文中,我們將展示如何使用 Knative 建構可擴展的 WebSocket 伺服器。
注意
此處討論的範例應用程式可以在Knative 文件儲存庫中找到。
HTTP 升級¶
由於每個 WebSocket 應用程式都從 HTTP 升級請求開始,我們需要定義一個基於 Golang 的 Web 伺服器來處理傳統的 HTTP 請求。 對於 WebSocket 實作,我們使用 gorilla/websocket
套件,它為 Golang 提供了一個快速、經過良好測試且廣泛使用的 WebSocket 實作。 以下是我們範例應用程式的 HTTP 部分
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
"github.com/knative/docs/code-samples/serving/websockets-go/pkg/handlers"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading to websocket: %v", err)
return
}
handlers.OnOpen(conn)
go func() {
defer handlers.OnClose(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
handlers.OnError(conn, err)
break
}
handlers.OnMessage(conn, messageType, message)
}
}()
}
func main() {
http.HandleFunc("/ws", handleWebSocket)
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v", err)
}
}
main
函式在 /ws
上下文中啟動 Web 伺服器,並註冊 handleWebSocket
處理函式
func main() {
http.HandleFunc("/ws", handleWebSocket)
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v", err)
}
}
handleWebSocket
執行協定升級並分配各種 websocket 處理函式,例如 OnOpen
或 OnMessage
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading to websocket: %v", err)
return
}
handlers.OnOpen(conn)
go func() {
defer handlers.OnClose(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
handlers.OnError(conn, err)
break
}
handlers.OnMessage(conn, messageType, message)
}
}()
}
注意
為了更清楚地分離關注點,不同的 On...
處理程式位於單獨的 handlers
套件中。
WebSocket 處理程式¶
WebSocket 應用程式邏輯位於 pkg/handlers/handlers.go
檔案中,其中包含每個 WebSocket 事件的回呼
func OnOpen(conn *websocket.Conn) {
log.Printf("WebSocket connection opened: %v", conn.RemoteAddr())
}
func OnMessage(conn *websocket.Conn, messageType int, message []byte) {
log.Printf("Received message from %v: %s", conn.RemoteAddr(), string(message))
if err := conn.WriteMessage(messageType, message); err != nil {
log.Printf("Error sending message: %v", err)
}
}
func OnClose(conn *websocket.Conn) {
log.Printf("WebSocket connection closed: %v", conn.RemoteAddr())
conn.Close()
}
func OnError(conn *websocket.Conn, err error) {
log.Printf("WebSocket error from %v: %v", conn.RemoteAddr(), err)
}
在每個處理程式中,我們記錄已連線對等點的遠端位址,並且透過 OnMessage
處理程式記錄任何傳入訊息,我們會將其回顯給發送者。
測試 WebSocket 伺服器¶
一旦 Knative Serving 應用程式部署到 Kubernetes 叢集中,您就可以對其進行測試。為此,您需要取得 WebSocket 應用程式服務的實際 URL
kubectl get ksvc
NAME URL LATESTCREATED LATESTREADY READY REASON
websocket-server http://websocket-server.default.svc.cluster.local websocket-server-00001 websocket-server-00001 True
現在我們需要一個客戶端來連線到我們的服務。 為此,我們在 Kubernetes 內部啟動一個 Linux 容器,並執行 wscat 以連線到我們的 WebSocket 伺服器應用程式
kubectl run --rm -i --tty wscat --image=monotykamary/wscat --restart=Never -- -c ws://websocket-server.default.svc.cluster.local/ws
之後,您可以像這樣與 WebSocket 伺服器聊天
```If you don't see a command prompt, try pressing enter.
```connected (press CTRL+C to quit)
```> Hello
```< Hello
```>
上述內容會擴展到正好一個 Pod,因為只有一個客戶端連線。 由於 Knative Serving 允許您動態擴展,因此一定數量的並行連線會導致一定數量的 Pod。
以下是在對其執行大量並行請求時,擴展應用程式的範例
k get pods
NAME READY STATUS RESTARTS AGE
websocket-server-00001-deployment-f785cbd65-42mrn 2/2 Running 0 16s
websocket-server-00001-deployment-f785cbd65-76bjr 2/2 Running 0 14s
websocket-server-00001-deployment-f785cbd65-98cwb 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-9fdbl 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-bpvjj 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-c6f47 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-fl6kk 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-gnffw 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-hqpfx 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-j4v9d 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-j72vk 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-k856w 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-kmmng 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-l4f2v 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-lpfr9 2/2 Running 0 14s
websocket-server-00001-deployment-f785cbd65-mn26w 2/2 Running 0 16s
websocket-server-00001-deployment-f785cbd65-mr98h 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-prjmj 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-v696v 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-ws5k9 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-xmszx 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-znhrr 2/2 Running 0 20s
注意
根據您擁有的目標註解(autoscaling.knative.dev/target
),您可以根據連線數進行擴展。