1
0

websocket.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. package controller
  2. import (
  3. "net"
  4. "net/http"
  5. "net/url"
  6. "strings"
  7. "github.com/mhsanaei/3x-ui/v2/logger"
  8. "github.com/mhsanaei/3x-ui/v2/web/service"
  9. "github.com/mhsanaei/3x-ui/v2/web/session"
  10. "github.com/gin-gonic/gin"
  11. ws "github.com/gorilla/websocket"
  12. )
  13. var upgrader = ws.Upgrader{
  14. ReadBufferSize: 32768,
  15. WriteBufferSize: 32768,
  16. EnableCompression: true,
  17. CheckOrigin: checkSameOrigin,
  18. }
  19. // checkSameOrigin allows requests with no Origin header (same-origin or non-browser
  20. // clients) and otherwise requires the Origin hostname to match the request hostname.
  21. // Comparison is case-insensitive (RFC 7230 §2.7.3) and ignores port differences
  22. // (the panel often sits behind a reverse proxy on a different port).
  23. func checkSameOrigin(r *http.Request) bool {
  24. origin := r.Header.Get("Origin")
  25. if origin == "" {
  26. return true
  27. }
  28. u, err := url.Parse(origin)
  29. if err != nil || u.Hostname() == "" {
  30. return false
  31. }
  32. host, _, err := net.SplitHostPort(r.Host)
  33. if err != nil {
  34. // IPv6 literals without a port arrive as "[::1]"; net.SplitHostPort
  35. // fails in that case while url.Hostname() returns the address without
  36. // brackets. Strip them so same-origin checks pass for bare IPv6 hosts.
  37. host = r.Host
  38. if len(host) >= 2 && host[0] == '[' && host[len(host)-1] == ']' {
  39. host = host[1 : len(host)-1]
  40. }
  41. }
  42. return strings.EqualFold(u.Hostname(), host)
  43. }
  44. // WebSocketController handles the HTTP→WebSocket upgrade for real-time updates.
  45. // All per-connection lifecycle (pumps, hub registration) lives in
  46. // service.WebSocketService — this controller is HTTP-layer only.
  47. type WebSocketController struct {
  48. BaseController
  49. service *service.WebSocketService
  50. }
  51. // NewWebSocketController creates a controller wired to the given service.
  52. func NewWebSocketController(svc *service.WebSocketService) *WebSocketController {
  53. return &WebSocketController{service: svc}
  54. }
  55. // HandleWebSocket authenticates the request, upgrades the HTTP connection, and
  56. // hands ownership of the connection off to the service.
  57. func (w *WebSocketController) HandleWebSocket(c *gin.Context) {
  58. if !session.IsLogin(c) {
  59. logger.Warningf("Unauthorized WebSocket connection attempt from %s", getRemoteIp(c))
  60. c.AbortWithStatus(http.StatusUnauthorized)
  61. return
  62. }
  63. conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
  64. if err != nil {
  65. logger.Error("Failed to upgrade WebSocket connection:", err)
  66. return
  67. }
  68. w.service.HandleConnection(conn, getRemoteIp(c))
  69. }