-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Matthias Wessendorf <[email protected]>
- Loading branch information
Showing
8 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Use the official Golang image to create a build artifact. | ||
# This is based on Debian and sets the GOPATH to /go. | ||
FROM golang:latest as builder | ||
|
||
ARG TARGETOS | ||
ARG TARGETARCH | ||
|
||
# Create and change to the app directory. | ||
WORKDIR /app | ||
|
||
# Initialize the Go module inside the Dockerfile. | ||
RUN go mod init mymodule | ||
|
||
# Copy local code to the container image. | ||
COPY . ./ | ||
|
||
# Install dependencies and tidy up the go.mod and go.sum files. | ||
RUN go mod tidy | ||
|
||
# Build the binary. | ||
# -mod=readonly ensures immutable go.mod and go.sum in container builds. | ||
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o server ./cmd/server | ||
|
||
# Use the official Alpine image for a lean production container. | ||
# https://hub.docker.com/_/alpine | ||
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds | ||
FROM alpine:3 | ||
RUN apk add --no-cache ca-certificates | ||
|
||
# Copy the binary to the production image from the builder stage. | ||
COPY --from=builder /app/server /server | ||
|
||
# Run the web service on container startup. | ||
CMD ["/server"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
# 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 "<image>" --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 `<image>` 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://websocket-server.default.svc.cluster.local websocket-server-00001 websocket-server-00001 True | ||
Check failure on line 116 in code-samples/serving/websockets-go/README.md GitHub Actions / style / Golang / Lint
|
||
``` | ||
|
||
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://websocket-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. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
apiVersion: serving.knative.dev/v1 | ||
kind: Service | ||
metadata: | ||
name: websockets-go | ||
namespace: default | ||
spec: | ||
template: | ||
spec: | ||
containers: | ||
- image: ko://github.com/knative/docs/code-samples/serving/websockets-go/cmd/server | ||
Check failure on line 10 in code-samples/serving/websockets-go/config/service.yaml GitHub Actions / style / Golang / Lint
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/knative/docs/code-samples/serving/websockets-go | ||
|
||
go 1.22 | ||
|
||
require github.com/gorilla/websocket v1.5.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= | ||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= |
28 changes: 28 additions & 0 deletions
28
code-samples/serving/websockets-go/pkg/handlers/handlers.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package handlers | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/gorilla/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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters