优化结构
This commit is contained in:
@@ -1,19 +1,23 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
melody "github.com/olahol/melody"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
platformws "kra/pkg/websocket"
|
||||
)
|
||||
|
||||
// Server is the system-owned WebSocket endpoint. Business modules can attach
|
||||
// handlers and publish messages without depending on Gin or Melody directly.
|
||||
const ProviderMelody = "melody"
|
||||
|
||||
// Server owns the database-configured WebSocket endpoint.
|
||||
type Server struct {
|
||||
mu sync.RWMutex
|
||||
current *platformws.Server
|
||||
@@ -22,24 +26,25 @@ type Server struct {
|
||||
binaryHandlers []func(*melody.Session, []byte)
|
||||
connectHandlers []func(*melody.Session)
|
||||
disconnectHandlers []func(*melody.Session)
|
||||
stop func()
|
||||
closed bool
|
||||
}
|
||||
|
||||
func New(runtime *conf.Runtime) (*Server, func(), error) {
|
||||
func New(store *runtimeconfig.Store) (*Server, func(), error) {
|
||||
s := &Server{}
|
||||
s.Replace(runtime)
|
||||
var unsubscribe func()
|
||||
if runtime != nil {
|
||||
unsubscribe = runtime.Subscribe(func(_ *conf.Data, _ *conf.AdminBackend) { s.Replace(runtime) })
|
||||
if store != nil {
|
||||
s.apply(storeConfig(store))
|
||||
s.stop = store.Subscribe("websocket", ProviderMelody, func(config runtimeconfig.Config) { s.apply(config) })
|
||||
}
|
||||
return s, func() {
|
||||
if unsubscribe != nil {
|
||||
unsubscribe()
|
||||
if s.stop != nil {
|
||||
s.stop()
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.closed = true
|
||||
current := s.current
|
||||
s.current = nil
|
||||
s.path = ""
|
||||
s.mu.Unlock()
|
||||
if current != nil {
|
||||
_ = current.Close()
|
||||
@@ -47,13 +52,20 @@ func New(runtime *conf.Runtime) (*Server, func(), error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Replace(runtime *conf.Runtime) {
|
||||
var config *conf.AdminBackend_WebSocket
|
||||
if runtime != nil && runtime.Admin() != nil {
|
||||
config = runtime.Admin().Websocket
|
||||
func storeConfig(store *runtimeconfig.Store) runtimeconfig.Config {
|
||||
config, _ := store.Get("websocket", ProviderMelody)
|
||||
return config
|
||||
}
|
||||
|
||||
func (s *Server) apply(config runtimeconfig.Config) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if config == nil {
|
||||
config = &conf.AdminBackend_WebSocket{}
|
||||
values := map[string]any{}
|
||||
if len(config.Values) > 0 {
|
||||
if err := json.Unmarshal(config.Values, &values); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.closed {
|
||||
@@ -70,14 +82,21 @@ func (s *Server) Replace(runtime *conf.Runtime) {
|
||||
}
|
||||
return
|
||||
}
|
||||
path := text(values, "path")
|
||||
if path == "" {
|
||||
path = "/ws"
|
||||
}
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
next := platformws.New(platformws.Config{
|
||||
WriteWait: duration(config.WriteWait, 10*time.Second),
|
||||
PongWait: duration(config.PongWait, 60*time.Second),
|
||||
PingPeriod: duration(config.PingPeriod, 54*time.Second),
|
||||
MaxMessageSize: config.MaxMessageSize,
|
||||
MessageBufferSize: int(config.MessageBufferSize),
|
||||
ConcurrentMessageHandling: config.ConcurrentMessageHandling,
|
||||
AllowOrigins: config.AllowOrigins,
|
||||
WriteWait: durationValue(values, "write_wait", 10*time.Second),
|
||||
PongWait: durationValue(values, "pong_wait", 60*time.Second),
|
||||
PingPeriod: durationValue(values, "ping_period", 54*time.Second),
|
||||
MaxMessageSize: int64Value(values, "max_message_size"),
|
||||
MessageBufferSize: int(intValue(values, "message_buffer_size")),
|
||||
ConcurrentMessageHandling: boolValue(values, "concurrent_message_handling"),
|
||||
AllowOrigins: stringList(values, "allow_origins"),
|
||||
})
|
||||
for _, handler := range s.messageHandlers {
|
||||
next.OnMessage(handler)
|
||||
@@ -91,27 +110,65 @@ func (s *Server) Replace(runtime *conf.Runtime) {
|
||||
for _, handler := range s.disconnectHandlers {
|
||||
next.OnDisconnect(handler)
|
||||
}
|
||||
s.path = strings.TrimSpace(config.Path)
|
||||
if s.path == "" {
|
||||
s.path = "/ws"
|
||||
} else if !strings.HasPrefix(s.path, "/") {
|
||||
s.path = "/" + s.path
|
||||
}
|
||||
s.current = next
|
||||
s.path = path
|
||||
s.mu.Unlock()
|
||||
if previous != nil {
|
||||
_ = previous.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func duration(value interface{ AsDuration() time.Duration }, fallback time.Duration) time.Duration {
|
||||
if value == nil {
|
||||
return fallback
|
||||
func text(values map[string]any, key string) string {
|
||||
value, ok := values[key]
|
||||
if !ok || value == nil {
|
||||
return ""
|
||||
}
|
||||
if result := value.AsDuration(); result > 0 {
|
||||
return strings.TrimSpace(fmt.Sprint(value))
|
||||
}
|
||||
|
||||
func intValue(values map[string]any, key string) int64 {
|
||||
switch value := values[key].(type) {
|
||||
case float64:
|
||||
return int64(value)
|
||||
case int:
|
||||
return int64(value)
|
||||
case json.Number:
|
||||
parsed, _ := strconv.ParseInt(string(value), 10, 64)
|
||||
return parsed
|
||||
default:
|
||||
parsed, _ := strconv.ParseInt(text(values, key), 10, 64)
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
func int64Value(values map[string]any, key string) int64 { return intValue(values, key) }
|
||||
func boolValue(values map[string]any, key string) bool { value, _ := values[key].(bool); return value }
|
||||
func stringList(values map[string]any, key string) []string {
|
||||
value, ok := values[key].([]any)
|
||||
if ok {
|
||||
result := make([]string, 0, len(value))
|
||||
for _, item := range value {
|
||||
if item != nil && strings.TrimSpace(fmt.Sprint(item)) != "" {
|
||||
result = append(result, strings.TrimSpace(fmt.Sprint(item)))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return fallback
|
||||
if value, ok := values[key].([]string); ok {
|
||||
return append([]string(nil), value...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func durationValue(values map[string]any, key string, fallback time.Duration) time.Duration {
|
||||
value := text(values, key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := time.ParseDuration(value)
|
||||
if err != nil || parsed <= 0 {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func (s *Server) Enabled() bool {
|
||||
@@ -131,14 +188,9 @@ func (s *Server) Path() string {
|
||||
return s.path
|
||||
}
|
||||
func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) error {
|
||||
if s == nil {
|
||||
return errors.New("websocket server is disabled")
|
||||
}
|
||||
s.mu.RLock()
|
||||
current := s.current
|
||||
s.mu.RUnlock()
|
||||
if current == nil {
|
||||
return errors.New("websocket server is disabled")
|
||||
current, err := s.active()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return current.HandleRequest(w, r)
|
||||
}
|
||||
@@ -150,26 +202,16 @@ func (s *Server) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, k
|
||||
return current.HandleRequestWithKeys(w, r, keys)
|
||||
}
|
||||
func (s *Server) Broadcast(message []byte) error {
|
||||
if s == nil {
|
||||
return errors.New("websocket server is disabled")
|
||||
}
|
||||
s.mu.RLock()
|
||||
current := s.current
|
||||
s.mu.RUnlock()
|
||||
if current == nil {
|
||||
return errors.New("websocket server is disabled")
|
||||
current, err := s.active()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return current.Broadcast(message)
|
||||
}
|
||||
func (s *Server) BroadcastBinary(message []byte) error {
|
||||
if s == nil {
|
||||
return errors.New("websocket server is disabled")
|
||||
}
|
||||
s.mu.RLock()
|
||||
current := s.current
|
||||
s.mu.RUnlock()
|
||||
if current == nil {
|
||||
return errors.New("websocket server is disabled")
|
||||
current, err := s.active()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return current.BroadcastBinary(message)
|
||||
}
|
||||
@@ -231,8 +273,9 @@ func (s *Server) OnBinaryMessage(handler func(*melody.Session, []byte)) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.binaryHandlers = append(s.binaryHandlers, handler)
|
||||
if s.current != nil {
|
||||
s.current.OnBinaryMessage(handler)
|
||||
current := s.current
|
||||
if current != nil {
|
||||
current.OnBinaryMessage(handler)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@@ -242,8 +285,9 @@ func (s *Server) OnConnect(handler func(*melody.Session)) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.connectHandlers = append(s.connectHandlers, handler)
|
||||
if s.current != nil {
|
||||
s.current.OnConnect(handler)
|
||||
current := s.current
|
||||
if current != nil {
|
||||
current.OnConnect(handler)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@@ -253,8 +297,9 @@ func (s *Server) OnDisconnect(handler func(*melody.Session)) {
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.disconnectHandlers = append(s.disconnectHandlers, handler)
|
||||
if s.current != nil {
|
||||
s.current.OnDisconnect(handler)
|
||||
current := s.current
|
||||
if current != nil {
|
||||
current.OnDisconnect(handler)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user