feat(ws-probe): тестовый WS echo-сервер для проверки wss на платформе Nubes (v0.1.0, образ naeel/iot-ws-probe)
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
/kafka-consumer
|
/kafka-consumer
|
||||||
/sqs-consumer
|
/sqs-consumer
|
||||||
bin/
|
bin/
|
||||||
|
ws-probe/ws-probe
|
||||||
|
|
||||||
# Go
|
# Go
|
||||||
*.exe
|
*.exe
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# ws-probe — проверка WebSocket через платформенный ingress Nubes.
|
||||||
|
# Образ: naeel/iot-ws-probe (Docker Hub, публичный — платформа тянет только публичные).
|
||||||
|
|
||||||
|
FROM golang:1.25 AS builder
|
||||||
|
WORKDIR /workspace
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY main.go ./
|
||||||
|
ARG VERSION=dev
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags "-X main.version=${VERSION}" -o ws-probe .
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/static:nonroot
|
||||||
|
WORKDIR /
|
||||||
|
COPY --from=builder /workspace/ws-probe .
|
||||||
|
USER 65532:65532
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["/ws-probe"]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# ws-probe — тестовый WS echo-сервер для проверки wss на платформе Nubes.
|
||||||
|
|
||||||
|
VERSION ?= v0.1.0
|
||||||
|
IMAGE ?= naeel/iot-ws-probe
|
||||||
|
|
||||||
|
.PHONY: build tidy docker-build docker-push
|
||||||
|
|
||||||
|
build:
|
||||||
|
CGO_ENABLED=0 go build -ldflags "-X main.version=$(VERSION)" -o ws-probe .
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
docker build --build-arg VERSION=$(VERSION) -t $(IMAGE):$(VERSION) -t $(IMAGE):latest .
|
||||||
|
|
||||||
|
docker-push:
|
||||||
|
docker push $(IMAGE):$(VERSION)
|
||||||
|
docker push $(IMAGE):latest
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module ws-probe
|
||||||
|
|
||||||
|
go 1.25
|
||||||
|
|
||||||
|
require github.com/gorilla/websocket v1.5.3
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// ws-probe — тестовый контейнер для проверки WebSocket (wss) через платформенный ingress Nubes.
|
||||||
|
//
|
||||||
|
// Назначение: подтвердить, что «Простой HTTP контейнер» платформы пропускает
|
||||||
|
// WebSocket upgrade (101 Switching Protocols) и держит долгоживущие соединения.
|
||||||
|
//
|
||||||
|
// Эндпоинты:
|
||||||
|
//
|
||||||
|
// GET / — инфо-страница (HTML)
|
||||||
|
// GET /health — {"status":"ok","version":...}
|
||||||
|
// GET /ws — WebSocket echo: отвечает на сообщения, шлёт ping каждые 20с
|
||||||
|
//
|
||||||
|
// Без кредов, без внешних зависимостей — только echo.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
|
const pageHTML = `<!DOCTYPE html>
|
||||||
|
<html><head><meta charset="utf-8"><title>ws-probe</title></head>
|
||||||
|
<body style="font-family:system-ui,sans-serif;padding:2rem">
|
||||||
|
<h1>ws-probe</h1>
|
||||||
|
<p>WebSocket echo: <code>/ws</code></p>
|
||||||
|
<p>Health: <code>/health</code></p>
|
||||||
|
</body></html>`
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok", "version": version})
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/ws", wsEcho)
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
_, _ = w.Write([]byte(pageHTML))
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("ws-probe %s listening on :%s", version, port)
|
||||||
|
if err := http.ListenAndServe(":"+port, mux); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wsEcho — апгрейд до WebSocket, echo сообщений, ping каждые 20с.
|
||||||
|
func wsEcho(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("upgrade failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
log.Printf("ws connected from %s (host=%s)", conn.RemoteAddr(), r.Host)
|
||||||
|
|
||||||
|
// Read deadline сбрасывается pong'ами — мёртвые соединения закрываются через 60с.
|
||||||
|
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||||
|
conn.SetPongHandler(func(string) error {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
pingTicker := time.NewTicker(20 * time.Second)
|
||||||
|
defer pingTicker.Stop()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
mt, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ws read error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("ws recv %d bytes (type %d)", len(msg), mt)
|
||||||
|
if err := conn.WriteMessage(mt, msg); err != nil {
|
||||||
|
log.Printf("ws write error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("ws echo sent")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
log.Printf("ws closed from %s", conn.RemoteAddr())
|
||||||
|
return
|
||||||
|
case t := <-pingTicker.C:
|
||||||
|
deadline := time.Now().Add(10 * time.Second)
|
||||||
|
if err := conn.WriteControl(websocket.PingMessage, []byte(t.Format(time.RFC3339)), deadline); err != nil {
|
||||||
|
log.Printf("ws ping error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user