Building WebSocket Applications with Knative using Golang¶
Author: Matthias Weßendorf, Senior Principal Software Engineer @ Red Hat
Knative Serving supports multiple protocols for running serverless workloads, such as HTTP, HTTP/2, gRPC or WebSocket. The WebSocket protocol allows a full-duplex communication over one single TCP connection between client applications and servers. The WebSocket protocol starts as an HTTP "Upgrade" request and then switchs the connection from HTTP over to the WebSocket protocol, allowing real-time and bidirectional communication between the peers. In this article we show how to build a scalable WebSocket server with Knative.
Note
The sample application discussed here can be found in the Knative docs repository.
HTTP Upgrade¶
Since every WebSocket application starts with an HTTP Upgrade request, we need a
Go web server that handles classic HTTP requests. The sample uses
gorilla/websocket for the WebSocket
implementation. The complete server is checked in at
cmd/server/main.go;
use that file rather than recreating it from snippets. Its upgrader accepts any
origin for demonstration purposes; restrict allowed origins for production.
The main function starts the web server on the /ws context and registers a handleWebSocket handle function:
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)
}
}
The handleWebSocket performs the protocol upgrade and assigns various websocket handler functions, such as OnOpen or 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)
}
}()
}
Note
For a cleaner separation of concerns the different On... handlers are located in separate handlers package.
WebSocket handlers¶
The callbacks for each WebSocket event are checked in at
pkg/handlers/handlers.go.
OnOpen, OnClose and OnError log the connected peer's remote address and
connection events. OnMessage logs each incoming message and echoes it back with
the original text or binary message type. The deferred close in the server
ensures the connection is released when its read loop exits.
Testing the WebSocket server¶
Note
The easiest way to build and deploy Golang applications is using ko, as described on the actual sample application.
Once the Knative Serving application was deployed to the Kubernetes cluster, you can test it. For that you need to receive the actual URL for the service of the WebSocket application:
kubectl get ksvc websockets-server
The reported URL depends on your cluster's domain configuration. To connect from
outside the cluster, change http:// to ws:// (or https:// to wss:// for TLS)
and append /ws.
Alternatively, launch a container inside Kubernetes and run wscat against the cluster-local address:
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:
If you don't see a command prompt, try pressing enter.
Connected (press CTRL+C to quit)
> Hello
< Hello
>
Knative can scale the application based on concurrent connections. The number of pods depends on autoscaling settings and traffic, not just the number of clients.
Below is an example of a scaled out application when running a larger number of concurrent requests against it:
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
Note
Depending on the target annotation you have (autoscaling.knative.dev/target) you can scale based on num of connections.