diff --git a/README.md b/README.md index dd20b590..b6f4d7ac 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ For n.eko room management software visit https://github.com/m1k1o/neko-rooms. - Added `VIDEO_BITRATE` and `AUDIO_BITRATE` in kbit/s to control stream quality (in collaboration with @mbattista). - Added `MAX_FPS`, where you can specify max WebRTC frame rate. When set to `0`, frame rate won't be capped and you can enjoy your real `60fps` experience. Originally, it was constant at `25fps`. - Invite links. You can invite people and they don't need to enter passwords by themselves (and get confused about user accounts that do not exits). You can put your password in URL using `?pwd=` and it will be automatically used when logging in. +- Added `/stats?pwd=` endpoint to get total active connections. ### Bugs - Fixed minor gst pipeline bug. diff --git a/server/internal/http/http.go b/server/internal/http/http.go index 434c0f85..5e4be6de 100644 --- a/server/internal/http/http.go +++ b/server/internal/http/http.go @@ -2,6 +2,7 @@ package http import ( "context" + "encoding/json" "fmt" "net/http" "os" @@ -23,7 +24,7 @@ type Server struct { } func New(conf *config.Server, webSocketHandler types.WebSocketHandler) *Server { - logger := log.With().Str("module", "webrtc").Logger() + logger := log.With().Str("module", "http").Logger() router := chi.NewRouter() // router.Use(middleware.Recoverer) // Recover from panics without crashing server @@ -34,6 +35,31 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler) *Server { webSocketHandler.Upgrade(w, r) }) + router.Get("/stats", func(w http.ResponseWriter, r *http.Request) { + password := r.URL.Query().Get("pwd") + isAdmin, err := webSocketHandler.IsAdmin(password) + if err != nil { + w.WriteHeader(http.StatusForbidden) + fmt.Fprint(w, err) + return + } + + if !isAdmin { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprint(w, "bad authorization") + return + } + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(struct{ + Connections uint32 `json:"connections"` + }{ + Connections: webSocketHandler.TotalConns(), + }); err != nil { + logger.Warn().Err(err).Msg("failed writing json error response") + } + }) + router.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("true")) }) diff --git a/server/internal/types/webscoket.go b/server/internal/types/webscoket.go index d5b63360..e15967b9 100644 --- a/server/internal/types/webscoket.go +++ b/server/internal/types/webscoket.go @@ -12,4 +12,6 @@ type WebSocketHandler interface { Start() error Shutdown() error Upgrade(w http.ResponseWriter, r *http.Request) error + TotalConns() uint32 + IsAdmin(password string) (bool, error) } diff --git a/server/internal/websocket/websocket.go b/server/internal/websocket/websocket.go index b4412b3a..526b84c6 100644 --- a/server/internal/websocket/websocket.go +++ b/server/internal/websocket/websocket.go @@ -3,6 +3,7 @@ package websocket import ( "fmt" "net/http" + "sync/atomic" "time" "github.com/gorilla/websocket" @@ -38,6 +39,7 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty banned: make(map[string]bool), locked: false, }, + conns: 0, } } @@ -51,6 +53,7 @@ type WebSocketHandler struct { remote types.RemoteManager conf *config.WebSocket handler *MessageHandler + conns uint32 shutdown chan bool } @@ -179,18 +182,38 @@ func (ws *WebSocketHandler) Upgrade(w http.ResponseWriter, r *http.Request) erro Str("address", connection.RemoteAddr().String()). Msg("new connection created") + atomic.AddUint32(&ws.conns, uint32(1)) + defer func() { ws.logger. Debug(). Str("session", id). Str("address", connection.RemoteAddr().String()). Msg("session ended") + + atomic.AddUint32(&ws.conns, ^uint32(0)) }() ws.handle(connection, id) return nil } +func (ws *WebSocketHandler) TotalConns() uint32 { + return atomic.LoadUint32(&ws.conns) +} + +func (ws *WebSocketHandler) IsAdmin(password string) (bool, error) { + if password == ws.conf.AdminPassword { + return true, nil + } + + if password == ws.conf.Password { + return false, nil + } + + return false, fmt.Errorf("invalid password") +} + func (ws *WebSocketHandler) authenticate(r *http.Request) (string, string, bool, error) { ip := r.RemoteAddr @@ -208,15 +231,8 @@ func (ws *WebSocketHandler) authenticate(r *http.Request) (string, string, bool, return "", ip, false, fmt.Errorf("no password provided") } - if passwords[0] == ws.conf.AdminPassword { - return id, ip, true, nil - } - - if passwords[0] == ws.conf.Password { - return id, ip, false, nil - } - - return "", ip, false, fmt.Errorf("invalid password: %s", passwords[0]) + isAdmin, err := ws.IsAdmin(passwords[0]) + return id, ip, isAdmin, err } func (ws *WebSocketHandler) handle(connection *websocket.Conn, id string) {