2025-05-20 09:40:15 +00:00
|
|
|
// Copyright 2025 Cloudbase Solutions SRL
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
|
|
|
// not use this file except in compliance with the License. You may obtain
|
|
|
|
|
// a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
|
// License for the specific language governing permissions and limitations
|
|
|
|
|
// under the License.
|
2022-10-21 02:49:53 +03:00
|
|
|
package websocket
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2024-07-02 22:26:12 +00:00
|
|
|
"log/slog"
|
2024-01-06 14:05:38 +00:00
|
|
|
"sync"
|
2022-10-21 02:49:53 +03:00
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func NewHub(ctx context.Context) *Hub {
|
|
|
|
|
return &Hub{
|
2024-07-02 22:26:12 +00:00
|
|
|
clients: map[string]*Client{},
|
|
|
|
|
broadcast: make(chan []byte, 100),
|
|
|
|
|
ctx: ctx,
|
|
|
|
|
closed: make(chan struct{}),
|
|
|
|
|
quit: make(chan struct{}),
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Hub struct {
|
|
|
|
|
ctx context.Context
|
|
|
|
|
closed chan struct{}
|
|
|
|
|
quit chan struct{}
|
|
|
|
|
// Registered clients.
|
|
|
|
|
clients map[string]*Client
|
|
|
|
|
|
|
|
|
|
// Inbound messages from the clients.
|
|
|
|
|
broadcast chan []byte
|
|
|
|
|
|
2025-05-14 15:22:27 +00:00
|
|
|
mux sync.Mutex
|
|
|
|
|
running bool
|
|
|
|
|
once sync.Once
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Hub) run() {
|
2025-05-14 15:22:27 +00:00
|
|
|
defer close(h.closed)
|
|
|
|
|
defer h.Stop()
|
|
|
|
|
|
2022-10-21 02:49:53 +03:00
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-h.quit:
|
|
|
|
|
return
|
|
|
|
|
case <-h.ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
case message := <-h.broadcast:
|
2024-01-06 14:05:38 +00:00
|
|
|
staleClients := []string{}
|
2022-10-21 02:49:53 +03:00
|
|
|
for id, client := range h.clients {
|
|
|
|
|
if client == nil {
|
2024-01-06 14:05:38 +00:00
|
|
|
staleClients = append(staleClients, id)
|
2022-10-21 02:49:53 +03:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-02 22:26:12 +00:00
|
|
|
if _, err := client.Write(message); err != nil {
|
2024-01-06 14:05:38 +00:00
|
|
|
staleClients = append(staleClients, id)
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-06 14:05:38 +00:00
|
|
|
if len(staleClients) > 0 {
|
|
|
|
|
h.mux.Lock()
|
|
|
|
|
for _, id := range staleClients {
|
|
|
|
|
if client, ok := h.clients[id]; ok {
|
|
|
|
|
if client != nil {
|
2025-05-14 15:22:27 +00:00
|
|
|
client.Stop()
|
2024-01-06 14:05:38 +00:00
|
|
|
}
|
|
|
|
|
delete(h.clients, id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
h.mux.Unlock()
|
|
|
|
|
}
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Hub) Register(client *Client) error {
|
2024-07-02 22:26:12 +00:00
|
|
|
if client == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
h.mux.Lock()
|
|
|
|
|
defer h.mux.Unlock()
|
|
|
|
|
cli, ok := h.clients[client.ID()]
|
|
|
|
|
if ok {
|
|
|
|
|
if cli != nil {
|
|
|
|
|
return fmt.Errorf("client already registered")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
slog.DebugContext(h.ctx, "registering client", "client_id", client.ID())
|
|
|
|
|
h.clients[client.id] = client
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Hub) Unregister(client *Client) error {
|
|
|
|
|
if client == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
h.mux.Lock()
|
|
|
|
|
defer h.mux.Unlock()
|
|
|
|
|
cli, ok := h.clients[client.ID()]
|
|
|
|
|
if ok {
|
|
|
|
|
cli.Stop()
|
|
|
|
|
slog.DebugContext(h.ctx, "unregistering client", "client_id", cli.ID())
|
|
|
|
|
delete(h.clients, cli.ID())
|
|
|
|
|
slog.DebugContext(h.ctx, "current client count", "count", len(h.clients))
|
|
|
|
|
}
|
2022-10-21 02:49:53 +03:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Hub) Write(msg []byte) (int, error) {
|
2024-01-06 14:05:38 +00:00
|
|
|
tmp := make([]byte, len(msg))
|
|
|
|
|
copy(tmp, msg)
|
2025-08-24 08:35:17 +00:00
|
|
|
|
2022-10-21 02:49:53 +03:00
|
|
|
select {
|
2024-01-06 14:05:38 +00:00
|
|
|
case h.broadcast <- tmp:
|
2025-08-24 08:35:17 +00:00
|
|
|
return len(tmp), nil
|
|
|
|
|
case <-h.quit:
|
|
|
|
|
return 0, fmt.Errorf("websocket hub is shutting down")
|
|
|
|
|
default:
|
|
|
|
|
return 0, fmt.Errorf("failed to broadcast over websocket")
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Hub) Start() error {
|
2025-05-14 15:22:27 +00:00
|
|
|
h.mux.Lock()
|
|
|
|
|
defer h.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
if h.running {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.running = true
|
|
|
|
|
|
2022-10-21 02:49:53 +03:00
|
|
|
go h.run()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-06 14:15:52 +00:00
|
|
|
func (h *Hub) Close() error {
|
|
|
|
|
h.once.Do(func() {
|
|
|
|
|
close(h.quit)
|
|
|
|
|
})
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 02:49:53 +03:00
|
|
|
func (h *Hub) Stop() error {
|
2025-05-14 15:22:27 +00:00
|
|
|
h.mux.Lock()
|
|
|
|
|
defer h.mux.Unlock()
|
|
|
|
|
|
|
|
|
|
if !h.running {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.running = false
|
2024-01-06 14:15:52 +00:00
|
|
|
h.Close()
|
2024-01-12 19:53:27 +00:00
|
|
|
return h.Wait()
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
|
|
|
|
|
2024-01-12 19:53:27 +00:00
|
|
|
func (h *Hub) Wait() error {
|
2025-05-14 15:22:27 +00:00
|
|
|
if !h.running {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2024-07-05 10:48:27 +00:00
|
|
|
timer := time.NewTimer(60 * time.Second)
|
|
|
|
|
defer timer.Stop()
|
2022-10-21 02:49:53 +03:00
|
|
|
select {
|
|
|
|
|
case <-h.closed:
|
2024-07-05 10:48:27 +00:00
|
|
|
case <-timer.C:
|
2024-01-12 19:53:27 +00:00
|
|
|
return fmt.Errorf("timed out waiting for hub stop")
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|
2024-01-12 19:53:27 +00:00
|
|
|
return nil
|
2022-10-21 02:49:53 +03:00
|
|
|
}
|