Files
mediamtx/internal/api/api_hls_test.go
T
Alessandro RosandGitHub f87d9e659e hls: track sessions (#962) (#5683)
sessions are now tracked through cookies or query parameters.

This provides the ability to inspect sessions through logs, metrics and
API, allows more precise tracking of outbound bytes, decreases load on
external HTTP authentication URLs since they are now called once per
session and not once per request.
2026-04-25 21:10:34 +02:00

337 lines
9.0 KiB
Go

package api //nolint:revive
import (
"fmt"
"net/http"
"sort"
"testing"
"time"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/servers/hls"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
type testHLSServer struct {
muxers map[string]*defs.APIHLSMuxer
sessions map[string]*defs.APIHLSSession
}
var testTime = time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
func (s *testHLSServer) APIMuxersList() (*defs.APIHLSMuxerList, error) {
items := make([]defs.APIHLSMuxer, 0, len(s.muxers))
for _, muxer := range s.muxers {
items = append(items, *muxer)
}
return &defs.APIHLSMuxerList{Items: items}, nil
}
func (s *testHLSServer) APIMuxersGet(name string) (*defs.APIHLSMuxer, error) {
muxer, ok := s.muxers[name]
if !ok {
return nil, hls.ErrMuxerNotFound
}
return muxer, nil
}
func (s *testHLSServer) APISessionsList() (*defs.APIHLSSessionList, error) {
items := make([]defs.APIHLSSession, 0, len(s.sessions))
for _, session := range s.sessions {
items = append(items, *session)
}
sort.Slice(items, func(i, j int) bool {
return items[i].Created.Before(items[j].Created)
})
return &defs.APIHLSSessionList{Items: items}, nil
}
func (s *testHLSServer) APISessionsGet(id uuid.UUID) (*defs.APIHLSSession, error) {
session, ok := s.sessions[id.String()]
if !ok {
return nil, hls.ErrSessionNotFound
}
return session, nil
}
func (s *testHLSServer) APISessionsKick(id uuid.UUID) error {
_, ok := s.sessions[id.String()]
if !ok {
return hls.ErrSessionNotFound
}
delete(s.sessions, id.String())
return nil
}
func TestHLSMuxersList(t *testing.T) {
now := testTime
hlsServer := &testHLSServer{
muxers: map[string]*defs.APIHLSMuxer{
"test1": {
Path: "test1",
Created: now,
LastRequest: now.Add(5 * time.Second),
OutboundBytes: 1234,
OutboundFramesDiscarded: 10,
BytesSent: 1234,
},
"test2": {
Path: "test2",
Created: now.Add(time.Minute),
LastRequest: now.Add(time.Minute + 10*time.Second),
OutboundBytes: 5678,
OutboundFramesDiscarded: 20,
BytesSent: 5678,
},
},
}
api := API{
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
AuthManager: test.NilAuthManager,
HLSServer: hlsServer,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
var out defs.APIHLSMuxerList
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/hlsmuxers/list", nil, &out)
require.Equal(t, 2, out.ItemCount)
require.Equal(t, 1, out.PageCount)
require.Len(t, out.Items, 2)
}
func TestHLSMuxersGet(t *testing.T) {
now := testTime
hlsServer := &testHLSServer{
muxers: map[string]*defs.APIHLSMuxer{
"mypath": {
Path: "mypath",
Created: now,
LastRequest: now.Add(5 * time.Second),
OutboundBytes: 9999,
OutboundFramesDiscarded: 12,
BytesSent: 9999,
},
},
}
api := API{
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
AuthManager: test.NilAuthManager,
HLSServer: hlsServer,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
var out defs.APIHLSMuxer
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/hlsmuxers/get/mypath", nil, &out)
require.Equal(t, "mypath", out.Path)
require.Equal(t, uint64(9999), out.OutboundBytes)
require.Equal(t, uint64(12), out.OutboundFramesDiscarded)
require.Equal(t, uint64(9999), out.BytesSent)
}
func TestHLSSessionsList(t *testing.T) {
now := testTime
hlsServer := &testHLSServer{
sessions: map[string]*defs.APIHLSSession{
"session1": {
ID: uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb41"),
Created: now,
RemoteAddr: "192.168.1.1:5000",
Path: "stream1",
Query: "key=val1",
User: "user1",
OutboundBytes: 111,
},
"session2": {
ID: uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb42"),
Created: now.Add(time.Minute),
RemoteAddr: "192.168.1.2:5001",
Path: "stream2",
Query: "key=val2",
User: "user2",
OutboundBytes: 222,
},
},
}
api := API{
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
AuthManager: test.NilAuthManager,
HLSServer: hlsServer,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
var out defs.APIHLSSessionList
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/hlssessions/list", nil, &out)
require.Equal(t, 2, out.ItemCount)
require.Equal(t, 1, out.PageCount)
require.Len(t, out.Items, 2)
require.Equal(t, []defs.APIHLSSession{
{
ID: uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb41"),
Created: now,
RemoteAddr: "192.168.1.1:5000",
Path: "stream1",
Query: "key=val1",
User: "user1",
OutboundBytes: 111,
},
{
ID: uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb42"),
Created: now.Add(time.Minute),
RemoteAddr: "192.168.1.2:5001",
Path: "stream2",
Query: "key=val2",
User: "user2",
OutboundBytes: 222,
},
}, out.Items)
}
func TestHLSSessionsGet(t *testing.T) {
now := testTime
hlsServer := &testHLSServer{
sessions: map[string]*defs.APIHLSSession{
"18294761-f9d1-4ea9-9a35-fe265b62eb41": {
ID: uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb41"),
Created: now,
RemoteAddr: "192.168.1.100:5000",
Path: "mystream",
Query: "key=val",
User: "myuser",
OutboundBytes: 345,
},
},
}
api := API{
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
AuthManager: test.NilAuthManager,
HLSServer: hlsServer,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
sessionID := "18294761-f9d1-4ea9-9a35-fe265b62eb41"
var out defs.APIHLSSession
httpRequest(t, hc, http.MethodGet, fmt.Sprintf("http://localhost:9997/v3/hlssessions/get/%s", sessionID), nil, &out)
require.Equal(t, uuid.MustParse(sessionID), out.ID)
require.Equal(t, "192.168.1.100:5000", out.RemoteAddr)
require.Equal(t, "mystream", out.Path)
require.Equal(t, "key=val", out.Query)
require.Equal(t, "myuser", out.User)
require.Equal(t, uint64(345), out.OutboundBytes)
}
func TestHLSSessionsKick(t *testing.T) {
now := testTime
sessionID := uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb41")
hlsServer := &testHLSServer{
sessions: map[string]*defs.APIHLSSession{
sessionID.String(): {
ID: sessionID,
Created: now,
RemoteAddr: "192.168.1.100:5000",
Path: "mystream",
OutboundBytes: 345,
},
},
}
api := API{
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
AuthManager: test.NilAuthManager,
HLSServer: hlsServer,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
httpRequest(t, hc, http.MethodPost, fmt.Sprintf("http://localhost:9997/v3/hlssessions/kick/%s", sessionID), nil, nil)
_, ok := hlsServer.sessions[sessionID.String()]
require.False(t, ok)
}
func TestHLSSessionsKickNotFound(t *testing.T) {
hlsServer := &testHLSServer{}
api := API{
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
WriteTimeout: conf.Duration(10 * time.Second),
AuthManager: test.NilAuthManager,
HLSServer: hlsServer,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
req, err := http.NewRequest(http.MethodPost,
fmt.Sprintf("http://localhost:9997/v3/hlssessions/kick/%s", uuid.New()), nil)
require.NoError(t, err)
res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusNotFound, res.StatusCode)
checkError(t, res.Body, "session not found")
}