websocket.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package sites
  2. import (
  3. "net/http"
  4. "sync"
  5. "github.com/0xJacky/Nginx-UI/internal/helper"
  6. "github.com/0xJacky/Nginx-UI/internal/sitecheck"
  7. "github.com/gin-gonic/gin"
  8. "github.com/gorilla/websocket"
  9. "github.com/uozi-tech/cosy/logger"
  10. )
  11. // WebSocket message types
  12. const (
  13. MessageTypeInitial = "initial"
  14. MessageTypeUpdate = "update"
  15. MessageTypeRefresh = "refresh"
  16. MessageTypePing = "ping"
  17. MessageTypePong = "pong"
  18. )
  19. // ClientMessage represents incoming WebSocket messages from client
  20. type ClientMessage struct {
  21. Type string `json:"type"`
  22. }
  23. // ServerMessage represents outgoing WebSocket messages to client
  24. type ServerMessage struct {
  25. Type string `json:"type"`
  26. Data []*sitecheck.SiteInfo `json:"data,omitempty"`
  27. }
  28. // PongMessage represents a pong response
  29. type PongMessage struct {
  30. Type string `json:"type"`
  31. }
  32. var upgrader = websocket.Upgrader{
  33. CheckOrigin: func(r *http.Request) bool {
  34. return true
  35. },
  36. }
  37. // WebSocket connection manager
  38. type WSManager struct {
  39. connections map[*websocket.Conn]bool
  40. mutex sync.RWMutex
  41. }
  42. var wsManager = &WSManager{
  43. connections: make(map[*websocket.Conn]bool),
  44. }
  45. // AddConnection adds a WebSocket connection to the manager
  46. func (wm *WSManager) AddConnection(conn *websocket.Conn) {
  47. wm.mutex.Lock()
  48. defer wm.mutex.Unlock()
  49. wm.connections[conn] = true
  50. }
  51. // RemoveConnection removes a WebSocket connection from the manager
  52. func (wm *WSManager) RemoveConnection(conn *websocket.Conn) {
  53. wm.mutex.Lock()
  54. defer wm.mutex.Unlock()
  55. delete(wm.connections, conn)
  56. }
  57. // BroadcastUpdate sends updates to all connected WebSocket clients
  58. func (wm *WSManager) BroadcastUpdate(sites []*sitecheck.SiteInfo) {
  59. wm.mutex.RLock()
  60. defer wm.mutex.RUnlock()
  61. for conn := range wm.connections {
  62. go func(c *websocket.Conn) {
  63. if err := sendSiteData(c, MessageTypeUpdate, sites); err != nil {
  64. logger.Error("Failed to send broadcast update:", err)
  65. wm.RemoveConnection(c)
  66. c.Close()
  67. }
  68. }(conn)
  69. }
  70. }
  71. // GetManager returns the global WebSocket manager instance
  72. func GetManager() *WSManager {
  73. return wsManager
  74. }
  75. // InitWebSocketNotifications sets up the callback for site check updates
  76. func InitWebSocketNotifications() {
  77. service := sitecheck.GetService()
  78. service.SetUpdateCallback(func(sites []*sitecheck.SiteInfo) {
  79. wsManager.BroadcastUpdate(sites)
  80. })
  81. }
  82. // SiteNavigationWebSocket handles WebSocket connections for real-time site status updates
  83. func SiteNavigationWebSocket(c *gin.Context) {
  84. ctx := c.Request.Context()
  85. conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
  86. if err != nil {
  87. logger.Error("WebSocket upgrade failed:", err)
  88. return
  89. }
  90. defer func() {
  91. wsManager.RemoveConnection(conn)
  92. conn.Close()
  93. }()
  94. logger.Info("Site navigation WebSocket connection established")
  95. // Register connection with manager
  96. wsManager.AddConnection(conn)
  97. service := sitecheck.GetService()
  98. // Send initial data
  99. if err := sendSiteData(conn, MessageTypeInitial, service.GetSites()); err != nil {
  100. logger.Error("Failed to send initial data:", err)
  101. return
  102. }
  103. // Handle incoming messages from client
  104. go handleClientMessages(conn, service)
  105. <-ctx.Done()
  106. logger.Info("Request context cancelled, closing WebSocket")
  107. }
  108. // sendSiteData sends site data via WebSocket
  109. func sendSiteData(conn *websocket.Conn, msgType string, sites []*sitecheck.SiteInfo) error {
  110. message := ServerMessage{
  111. Type: msgType,
  112. Data: sites,
  113. }
  114. return conn.WriteJSON(message)
  115. }
  116. // handleClientMessages handles incoming WebSocket messages
  117. func handleClientMessages(conn *websocket.Conn, service *sitecheck.Service) {
  118. for {
  119. var msg ClientMessage
  120. if err := conn.ReadJSON(&msg); err != nil {
  121. if helper.IsUnexpectedWebsocketError(err) {
  122. logger.Error("WebSocket read error:", err)
  123. }
  124. return
  125. }
  126. switch msg.Type {
  127. case MessageTypeRefresh:
  128. logger.Info("Client requested site refresh")
  129. service.RefreshSites()
  130. case MessageTypePing:
  131. pongMsg := PongMessage{Type: MessageTypePong}
  132. if err := conn.WriteJSON(pongMsg); err != nil {
  133. logger.Error("Failed to send pong:", err)
  134. return
  135. }
  136. }
  137. }
  138. }