# WebSocket - Go A simple [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) server that performs the HTTP upgrade and prints log messages on all standardized WebSocket events, such as `open`, `message`, `close` and `error`. The server is written in Golang and uses the [Gorilla WebSocket](github.com/gorilla/websocket) library. ## Before you begin - A Kubernetes cluster with Knative installed and DNS configured. See [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). - [ko](https://github.com/ko-build/ko) or [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). ## The sample code. 1. If you look in `cmd/server/main.go`, you will the `main` function setting a `handleWebSocket` function and starting the web server on the `/ws` context: ```go 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) } } ``` 2. The `handleWebSocket` performs the protocol upgrade and assigns various websocket handler functions, such as `OnOpen` or `OnMessage`: ```go 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) } }() } ``` 3. The WebSocket application logic is located in the `pkg/handlers/handlers.go` file and contains callbacks for each WebSocket event: ```go 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) } ``` ## Build the application ### Dockerfile * If you look in `Dockerfile`, you will see a method for pulling in the dependencies and building a small Go container based on Alpine. You can build and push this to your registry of choice via: ```bash # Build and push the container on your local machine. docker buildx build --platform linux/arm64,linux/amd64 -t "" --push . ``` ### ko * You can use `ko` to build and push just the image with: ```bash ko publish github.com/knative/docs/code-samples/serving/websockets-go ``` However, if you use `ko` for the next step, this is not necessary. ## Deploy the application ### yaml (with Dockerfile) * If you look in `service.yaml`, take the `` name you used earlier and insert it into the `image:` field, then run: ```bash kubectl apply -f config/service.yaml ``` ### yaml (with ko) * If using `ko` to build and push: ```bash ko apply -f config/service.yaml ``` ## Testing the WebSocket server Get the URL for your Service with: ```bash kubectl get ksvc NAME URL LATESTCREATED LATESTREADY READY REASON websocket-server http://websockets-server.default.svc.cluster.local websockets-server-00001 websocket-server-00001 True ``` Now run a container with the [wscat](https://github.com/websockets/wscat) CLI and point it to the WebSocket application `ws://websocket-server.default.svc.cluster.local/ws`, like: ```bash kubectl run --rm -i --tty wscat --image=monotykamary/wscat --restart=Never -- -c ws://websockets-server.default.svc.cluster.local/ws ``` Afterward you can chat with the WebSocket server like: ```bash ```If you don't see a command prompt, try pressing enter. ```connected (press CTRL+C to quit) ```> Hello ```< Hello ```> ``` The above is scaling to exactly one pod, since only one client was connected. Since Knative Serving allows you a dynamic scalling, a certain number of concurrent connections lead to a number of pods. >> **NOTE:** Depending on the target annotation you have ([`autoscaling.knative.dev/target`](https://knative.dev/docs/serving/autoscaling/autoscaling-targets/)) you can scale based on num of connections.