From 9c79197f3635c405a0099c784b0e0ab8e8650c91 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Tue, 16 May 2023 19:48:13 +0200 Subject: [PATCH] api: add pagination to all /list endpoints (#1808) --- README.md | 10 +- apidocs/openapi.yaml | 165 ++++++++++++++++-- internal/core/api.go | 113 ++++++++++++ internal/core/api_test.go | 132 +++++++------- internal/core/hls_manager.go | 6 +- internal/core/hls_muxer.go | 5 +- ..._serverheader.go => http_server_header.go} | 0 internal/core/metrics.go | 32 ++-- internal/core/path.go | 9 +- internal/core/path_manager.go | 2 +- internal/core/rtmp_server.go | 11 +- internal/core/rtsp_server.go | 22 ++- internal/core/webrtc_manager.go | 11 +- 13 files changed, 388 insertions(+), 130 deletions(-) rename internal/core/{http_serverheader.go => http_server_header.go} (100%) diff --git a/README.md b/README.md index eeae947a..396a8509 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,23 @@ Live streams can be published to the server with: |protocol|variants|video codecs|audio codecs| |--------|--------|------------|------------| -|RTSP clients (FFmpeg, GStreamer)|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-2 Audio (MP3), G722, G711, LPCM and any RTP-compatible codec| -|RTSP servers and cameras|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-2 Audio (MP3), G722, G711, LPCM and any RTP-compatible codec| +|WebRTC|WHIP|AV1, VP9, VP8, H264|Opus, G722, G711| +|RTSP clients|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-2 Audio (MP3), G722, G711, LPCM and any RTP-compatible codec| +|RTSP servers and cameras|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-2 Audio (MP3), G722, G711, LPCM and any RTP-compatible codec| |RTMP clients (OBS Studio)|RTMP, RTMPS, Enhanced RTMP|AV1, H265, H264|MPEG-4 Audio (AAC), MPEG-2 Audio (MP3)| |RTMP servers and cameras|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-2 Audio (MP3)| |HLS servers and cameras|Low-Latency HLS, MP4-based HLS, legacy HLS|H265, H264|Opus, MPEG-4 Audio (AAC)| |UDP/MPEG-TS streams|Unicast, broadcast, multicast|H265, H264|Opus, MPEG-4 Audio (AAC)| -|WebRTC|WHIP|AV1, VP9, VP8, H264|Opus, G722, G711| |Raspberry Pi Cameras||H264|| And can be read from the server with: |protocol|variants|video codecs|audio codecs| |--------|--------|------------|------------| -|RTSP|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-2 Audio (MP3), G722, G711, LPCM and any RTP-compatible codec| +|WebRTC|WHEP|AV1, VP9, VP8, H264|Opus, G722, G711| +|RTSP|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-2 Audio (MP3), G722, G711, LPCM and any RTP-compatible codec| |RTMP|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-2 Audio (MP3)| |HLS|Low-Latency HLS, MP4-based HLS, legacy HLS|H265, H264|Opus, MPEG-4 Audio (AAC)| -|WebRTC|WHEP|AV1, VP9, VP8, H264|Opus, G722, G711| Features: diff --git a/apidocs/openapi.yaml b/apidocs/openapi.yaml index 84421c73..004de41c 100644 --- a/apidocs/openapi.yaml +++ b/apidocs/openapi.yaml @@ -311,6 +311,8 @@ components: Path: type: object properties: + name: + type: string confName: type: string conf: @@ -355,6 +357,8 @@ components: RTSPConn: type: object properties: + id: + type: string created: type: string remoteAddr: @@ -369,6 +373,8 @@ components: RTSPSession: type: object properties: + id: + type: string created: type: string remoteAddr: @@ -386,6 +392,8 @@ components: RTMPConn: type: object properties: + id: + type: string created: type: string remoteAddr: @@ -403,6 +411,8 @@ components: HLSMuxer: type: object properties: + path: + type: string created: type: string lastRequest: @@ -414,46 +424,58 @@ components: HLSMuxersList: type: object properties: + pageCount: + type: integer items: - type: object - additionalProperties: + type: array + items: $ref: '#/components/schemas/HLSMuxer' PathsList: type: object properties: + pageCount: + type: integer items: - type: object - additionalProperties: + type: array + items: $ref: '#/components/schemas/Path' RTMPConnsList: type: object properties: + pageCount: + type: integer items: - type: object - additionalProperties: + type: array + items: $ref: '#/components/schemas/RTMPConn' RTSPConnsList: type: object properties: + pageCount: + type: integer items: - type: object - additionalProperties: + type: array + items: $ref: '#/components/schemas/RTSPConn' RTSPSessionsList: type: object properties: + pageCount: + type: integer items: - type: object - additionalProperties: + type: array + items: $ref: '#/components/schemas/RTSPSession' WebRTCConn: type: object properties: + id: + type: string created: type: string remoteAddr: @@ -477,9 +499,11 @@ components: WebRTCConnsList: type: object properties: + pageCount: + type: integer items: - type: object - additionalProperties: + type: array + items: $ref: '#/components/schemas/WebRTCConn' paths: @@ -596,6 +620,19 @@ paths: operationId: hlsMuxersList summary: returns all HLS muxers. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -613,6 +650,19 @@ paths: operationId: pathsList summary: returns all paths. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -630,6 +680,19 @@ paths: operationId: rtspConnsList summary: returns all RTSP connections. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -647,6 +710,19 @@ paths: operationId: rtspSessionsList summary: returns all RTSP sessions. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -684,6 +760,19 @@ paths: operationId: rtspsConnsList summary: returns all RTSPS connections. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -701,6 +790,19 @@ paths: operationId: rtspsSessionsList summary: returns all RTSPS sessions. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -738,6 +840,19 @@ paths: operationId: rtmpConnsList summary: returns all RTMP connections. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -775,6 +890,19 @@ paths: operationId: rtmpsConnsList summary: returns all RTMPS connections. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. @@ -812,6 +940,19 @@ paths: operationId: webrtcSessionsList summary: returns all WebRTC connections. description: '' + parameters: + - name: page + in: query + description: page number. + schema: + type: number + default: 0 + - name: itemsPerPage + in: query + description: items per page. + schema: + type: number + default: 100 responses: '200': description: the request was successful. diff --git a/internal/core/api.go b/internal/core/api.go index 7cdc4f0b..65cb81b9 100644 --- a/internal/core/api.go +++ b/internal/core/api.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "reflect" + "strconv" "sync" "time" @@ -79,6 +80,55 @@ func loadConfPathData(ctx *gin.Context) (interface{}, error) { return in, err } +func paginate2(itemsPtr interface{}, itemsPerPage int, page int) int { + ritems := reflect.ValueOf(itemsPtr).Elem() + + itemsLen := ritems.Len() + if itemsLen == 0 { + return 0 + } + + pageCount := (itemsLen / itemsPerPage) + 1 + + min := page * itemsPerPage + if min >= itemsLen { + min = itemsLen - 1 + } + + max := (page + 1) * itemsPerPage + if max >= itemsLen { + max = itemsLen + } + + ritems.Set(ritems.Slice(min, max)) + + return pageCount +} + +func paginate(itemsPtr interface{}, itemsPerPageStr string, pageStr string) (int, error) { + itemsPerPage := 100 + + if itemsPerPageStr != "" { + tmp, err := strconv.ParseUint(itemsPerPageStr, 10, 31) + if err != nil { + return 0, err + } + itemsPerPage = int(tmp) + } + + page := 0 + + if pageStr != "" { + tmp, err := strconv.ParseUint(pageStr, 10, 31) + if err != nil { + return 0, err + } + page = int(tmp) + } + + return paginate2(itemsPtr, itemsPerPage, page), nil +} + type apiPathManager interface { apiPathsList() pathAPIPathsListRes } @@ -389,6 +439,13 @@ func (a *api) onPathsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -399,6 +456,13 @@ func (a *api) onRTSPConnsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -409,6 +473,13 @@ func (a *api) onRTSPSessionsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -434,6 +505,13 @@ func (a *api) onRTSPSConnsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -444,6 +522,13 @@ func (a *api) onRTSPSSessionsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -469,6 +554,13 @@ func (a *api) onRTMPConnsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -494,6 +586,13 @@ func (a *api) onRTMPSConnsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -519,6 +618,13 @@ func (a *api) onHLSMuxersList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } @@ -529,6 +635,13 @@ func (a *api) onWebRTCSessionsList(ctx *gin.Context) { return } + pageCount, err := paginate(&res.data.Items, ctx.Query("itemsPerPage"), ctx.Query("page")) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + res.data.PageCount = pageCount + ctx.JSON(http.StatusOK, res.data) } diff --git a/internal/core/api_test.go b/internal/core/api_test.go index 1cbdf7b6..d2eec0cf 100644 --- a/internal/core/api_test.go +++ b/internal/core/api_test.go @@ -189,6 +189,7 @@ func TestAPIPathsList(t *testing.T) { } type path struct { + Name string `json:"name"` Source pathSource `json:"source"` SourceReady bool `json:"sourceReady"` Tracks []string `json:"tracks"` @@ -196,7 +197,8 @@ func TestAPIPathsList(t *testing.T) { } type pathList struct { - Items map[string]path `json:"items"` + PageCount int `json:"pageCount"` + Items []path `json:"items"` } t.Run("rtsp session", func(t *testing.T) { @@ -243,16 +245,16 @@ func TestAPIPathsList(t *testing.T) { err = httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) require.NoError(t, err) require.Equal(t, pathList{ - Items: map[string]path{ - "mypath": { - Source: pathSource{ - Type: "rtspSession", - }, - SourceReady: true, - Tracks: []string{"H264", "MPEG4-audio-gen"}, - BytesReceived: 16, + PageCount: 1, + Items: []path{{ + Name: "mypath", + Source: pathSource{ + Type: "rtspSession", }, - }, + SourceReady: true, + Tracks: []string{"H264", "MPEG4-audio-gen"}, + BytesReceived: 16, + }}, }, out) }) @@ -304,15 +306,15 @@ func TestAPIPathsList(t *testing.T) { err = httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) require.NoError(t, err) require.Equal(t, pathList{ - Items: map[string]path{ - "mypath": { - Source: pathSource{ - Type: "rtspsSession", - }, - SourceReady: true, - Tracks: []string{"H264", "MPEG4-audio-gen"}, + PageCount: 1, + Items: []path{{ + Name: "mypath", + Source: pathSource{ + Type: "rtspsSession", }, - }, + SourceReady: true, + Tracks: []string{"H264", "MPEG4-audio-gen"}, + }}, }, out) }) @@ -329,15 +331,15 @@ func TestAPIPathsList(t *testing.T) { err := httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) require.NoError(t, err) require.Equal(t, pathList{ - Items: map[string]path{ - "mypath": { - Source: pathSource{ - Type: "rtspSource", - }, - SourceReady: false, - Tracks: []string{}, + PageCount: 1, + Items: []path{{ + Name: "mypath", + Source: pathSource{ + Type: "rtspSource", }, - }, + SourceReady: false, + Tracks: []string{}, + }}, }, out) }) @@ -354,15 +356,15 @@ func TestAPIPathsList(t *testing.T) { err := httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) require.NoError(t, err) require.Equal(t, pathList{ - Items: map[string]path{ - "mypath": { - Source: pathSource{ - Type: "rtmpSource", - }, - SourceReady: false, - Tracks: []string{}, + PageCount: 1, + Items: []path{{ + Name: "mypath", + Source: pathSource{ + Type: "rtmpSource", }, - }, + SourceReady: false, + Tracks: []string{}, + }}, }, out) }) @@ -379,15 +381,15 @@ func TestAPIPathsList(t *testing.T) { err := httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) require.NoError(t, err) require.Equal(t, pathList{ - Items: map[string]path{ - "mypath": { - Source: pathSource{ - Type: "hlsSource", - }, - SourceReady: false, - Tracks: []string{}, + PageCount: 1, + Items: []path{{ + Name: "mypath", + Source: pathSource{ + Type: "hlsSource", }, - }, + SourceReady: false, + Tracks: []string{}, + }}, }, out) }) } @@ -589,25 +591,20 @@ func TestAPIProtocolSpecificList(t *testing.T) { } var out struct { - Items map[string]struct { + Items []struct { State string `json:"state"` } `json:"items"` } err = httpRequest(http.MethodGet, "http://localhost:9997/v1/"+pa+"/list", nil, &out) require.NoError(t, err) - var firstID string - for k := range out.Items { - firstID = k - } - if ca != "rtsp conns" && ca != "rtsps conns" { - require.Equal(t, "publish", out.Items[firstID].State) + require.Equal(t, "publish", out.Items[0].State) } case "hls": var out struct { - Items map[string]struct { + Items []struct { Created string `json:"created"` LastRequest string `json:"lastRequest"` } `json:"items"` @@ -615,14 +612,9 @@ func TestAPIProtocolSpecificList(t *testing.T) { err = httpRequest(http.MethodGet, "http://localhost:9997/v1/hlsmuxers/list", nil, &out) require.NoError(t, err) - var firstID string - for k := range out.Items { - firstID = k - } - s := fmt.Sprintf("^%d-", time.Now().Year()) - require.Regexp(t, s, out.Items[firstID].Created) - require.Regexp(t, s, out.Items[firstID].LastRequest) + require.Regexp(t, s, out.Items[0].Created) + require.Regexp(t, s, out.Items[0].LastRequest) case "webrtc": type item struct { @@ -636,18 +628,13 @@ func TestAPIProtocolSpecificList(t *testing.T) { } var out struct { - Items map[string]item `json:"items"` + PageCount int `json:"pageCount"` + Items []item `json:"items"` } err = httpRequest(http.MethodGet, "http://localhost:9997/v1/webrtcsessions/list", nil, &out) require.NoError(t, err) - var firstID string - for k := range out.Items { - firstID = k - } - - itm := out.Items[firstID] - require.Equal(t, true, itm.PeerConnectionEstablished) + require.Equal(t, true, out.Items[0].PeerConnectionEstablished) } }) } @@ -742,21 +729,20 @@ func TestAPIKick(t *testing.T) { } var out1 struct { - Items map[string]struct{} `json:"items"` + Items []struct { + ID string `json:"id"` + } `json:"items"` } err = httpRequest(http.MethodGet, "http://localhost:9997/v1/"+pa+"/list", nil, &out1) require.NoError(t, err) - var firstID string - for k := range out1.Items { - firstID = k - } - - err = httpRequest(http.MethodPost, "http://localhost:9997/v1/"+pa+"/kick/"+firstID, nil, nil) + err = httpRequest(http.MethodPost, "http://localhost:9997/v1/"+pa+"/kick/"+out1.Items[0].ID, nil, nil) require.NoError(t, err) var out2 struct { - Items map[string]struct{} `json:"items"` + Items []struct { + ID string `json:"id"` + } `json:"items"` } err = httpRequest(http.MethodGet, "http://localhost:9997/v1/"+pa+"/list", nil, &out2) require.NoError(t, err) diff --git a/internal/core/hls_manager.go b/internal/core/hls_manager.go index b91e438b..e5f110c8 100644 --- a/internal/core/hls_manager.go +++ b/internal/core/hls_manager.go @@ -17,13 +17,15 @@ func (nilWriter) Write(p []byte) (int, error) { } type hlsManagerAPIMuxersListItem struct { + Path string `json:"path"` Created time.Time `json:"created"` LastRequest time.Time `json:"lastRequest"` BytesSent uint64 `json:"bytesSent"` } type hlsManagerAPIMuxersListData struct { - Items map[string]hlsManagerAPIMuxersListItem `json:"items"` + PageCount int `json:"pageCount"` + Items []hlsManagerAPIMuxersListItem `json:"items"` } type hlsManagerAPIMuxersListRes struct { @@ -285,7 +287,7 @@ func (m *hlsManager) apiMuxersList() hlsManagerAPIMuxersListRes { res := <-req.res res.data = &hlsManagerAPIMuxersListData{ - Items: make(map[string]hlsManagerAPIMuxersListItem), + Items: []hlsManagerAPIMuxersListItem{}, } for _, pa := range res.muxers { diff --git a/internal/core/hls_muxer.go b/internal/core/hls_muxer.go index ecd47ba6..b55d9344 100644 --- a/internal/core/hls_muxer.go +++ b/internal/core/hls_muxer.go @@ -202,11 +202,12 @@ func (m *hlsMuxer) run() { } case req := <-m.chAPIHLSMuxersList: - req.data.Items[m.pathName] = hlsManagerAPIMuxersListItem{ + req.data.Items = append(req.data.Items, hlsManagerAPIMuxersListItem{ + Path: m.pathName, Created: m.created, LastRequest: time.Unix(0, atomic.LoadInt64(m.lastRequestTime)), BytesSent: atomic.LoadUint64(m.bytesSent), - } + }) close(req.res) case <-innerReady: diff --git a/internal/core/http_serverheader.go b/internal/core/http_server_header.go similarity index 100% rename from internal/core/http_serverheader.go rename to internal/core/http_server_header.go diff --git a/internal/core/metrics.go b/internal/core/metrics.go index d90bb0f7..01558156 100644 --- a/internal/core/metrics.go +++ b/internal/core/metrics.go @@ -88,7 +88,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) { res := m.pathManager.apiPathsList() if res.err == nil && len(res.data.Items) != 0 { - for name, i := range res.data.Items { + for _, i := range res.data.Items { var state string if i.SourceReady { state = "ready" @@ -96,7 +96,7 @@ func (m *metrics) onMetrics(ctx *gin.Context) { state = "notReady" } - tags := "{name=\"" + name + "\",state=\"" + state + "\"}" + tags := "{name=\"" + i.Name + "\",state=\"" + state + "\"}" out += metric("paths", tags, 1) out += metric("paths_bytes_received", tags, int64(i.BytesReceived)) } @@ -107,8 +107,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { if !interfaceIsEmpty(m.hlsManager) { res := m.hlsManager.apiMuxersList() if res.err == nil && len(res.data.Items) != 0 { - for name, i := range res.data.Items { - tags := "{name=\"" + name + "\"}" + for _, i := range res.data.Items { + tags := "{name=\"" + i.Path + "\"}" out += metric("hls_muxers", tags, 1) out += metric("hls_muxers_bytes_sent", tags, int64(i.BytesSent)) } @@ -122,8 +122,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { func() { res := m.rtspServer.apiConnsList() if res.err == nil && len(res.data.Items) != 0 { - for id, i := range res.data.Items { - tags := "{id=\"" + id + "\"}" + for _, i := range res.data.Items { + tags := "{id=\"" + i.ID.String() + "\"}" out += metric("rtsp_conns", tags, 1) out += metric("rtsp_conns_bytes_received", tags, int64(i.BytesReceived)) out += metric("rtsp_conns_bytes_sent", tags, int64(i.BytesSent)) @@ -138,8 +138,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { func() { res := m.rtspServer.apiSessionsList() if res.err == nil && len(res.data.Items) != 0 { - for id, i := range res.data.Items { - tags := "{id=\"" + id + "\",state=\"" + i.State + "\"}" + for _, i := range res.data.Items { + tags := "{id=\"" + i.ID.String() + "\",state=\"" + i.State + "\"}" out += metric("rtsp_sessions", tags, 1) out += metric("rtsp_sessions_bytes_received", tags, int64(i.BytesReceived)) out += metric("rtsp_sessions_bytes_sent", tags, int64(i.BytesSent)) @@ -156,8 +156,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { func() { res := m.rtspsServer.apiConnsList() if res.err == nil && len(res.data.Items) != 0 { - for id, i := range res.data.Items { - tags := "{id=\"" + id + "\"}" + for _, i := range res.data.Items { + tags := "{id=\"" + i.ID.String() + "\"}" out += metric("rtsps_conns", tags, 1) out += metric("rtsps_conns_bytes_received", tags, int64(i.BytesReceived)) out += metric("rtsps_conns_bytes_sent", tags, int64(i.BytesSent)) @@ -172,8 +172,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { func() { res := m.rtspsServer.apiSessionsList() if res.err == nil && len(res.data.Items) != 0 { - for id, i := range res.data.Items { - tags := "{id=\"" + id + "\",state=\"" + i.State + "\"}" + for _, i := range res.data.Items { + tags := "{id=\"" + i.ID.String() + "\",state=\"" + i.State + "\"}" out += metric("rtsps_sessions", tags, 1) out += metric("rtsps_sessions_bytes_received", tags, int64(i.BytesReceived)) out += metric("rtsps_sessions_bytes_sent", tags, int64(i.BytesSent)) @@ -189,8 +189,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { if !interfaceIsEmpty(m.rtmpServer) { res := m.rtmpServer.apiConnsList() if res.err == nil && len(res.data.Items) != 0 { - for id, i := range res.data.Items { - tags := "{id=\"" + id + "\",state=\"" + i.State + "\"}" + for _, i := range res.data.Items { + tags := "{id=\"" + i.ID.String() + "\",state=\"" + i.State + "\"}" out += metric("rtmp_conns", tags, 1) out += metric("rtmp_conns_bytes_received", tags, int64(i.BytesReceived)) out += metric("rtmp_conns_bytes_sent", tags, int64(i.BytesSent)) @@ -205,8 +205,8 @@ func (m *metrics) onMetrics(ctx *gin.Context) { if !interfaceIsEmpty(m.webRTCManager) { res := m.webRTCManager.apiSessionsList() if res.err == nil && len(res.data.Items) != 0 { - for id, i := range res.data.Items { - tags := "{id=\"" + id + "\"}" + for _, i := range res.data.Items { + tags := "{id=\"" + i.ID.String() + "\"}" out += metric("webrtc_sessions", tags, 1) out += metric("webrtc_sessions_bytes_received", tags, int64(i.BytesReceived)) out += metric("webrtc_sessions_bytes_sent", tags, int64(i.BytesSent)) diff --git a/internal/core/path.go b/internal/core/path.go index f99a5f49..184c793a 100644 --- a/internal/core/path.go +++ b/internal/core/path.go @@ -159,6 +159,7 @@ type pathAPISourceOrReader struct { } type pathAPIPathsListItem struct { + Name string `json:"name"` ConfName string `json:"confName"` Conf *conf.PathConf `json:"conf"` Source interface{} `json:"source"` @@ -169,7 +170,8 @@ type pathAPIPathsListItem struct { } type pathAPIPathsListData struct { - Items map[string]pathAPIPathsListItem `json:"items"` + PageCount int `json:"pageCount"` + Items []pathAPIPathsListItem `json:"items"` } type pathAPIPathsListRes struct { @@ -897,7 +899,8 @@ func (pa *path) handleReaderAddPost(req pathReaderAddReq) { } func (pa *path) handleAPIPathsList(req pathAPIPathsListSubReq) { - req.data.Items[pa.name] = pathAPIPathsListItem{ + req.data.Items = append(req.data.Items, pathAPIPathsListItem{ + Name: pa.name, ConfName: pa.confName, Conf: pa.conf, Source: func() interface{} { @@ -921,7 +924,7 @@ func (pa *path) handleAPIPathsList(req pathAPIPathsListSubReq) { } return ret }(), - } + }) close(req.res) } diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go index ef33e26f..4ebcbe21 100644 --- a/internal/core/path_manager.go +++ b/internal/core/path_manager.go @@ -492,7 +492,7 @@ func (pm *pathManager) apiPathsList() pathAPIPathsListRes { res := <-req.res res.data = &pathAPIPathsListData{ - Items: make(map[string]pathAPIPathsListItem), + Items: []pathAPIPathsListItem{}, } for _, pa := range res.paths { diff --git a/internal/core/rtmp_server.go b/internal/core/rtmp_server.go index 99e33440..8c976ec3 100644 --- a/internal/core/rtmp_server.go +++ b/internal/core/rtmp_server.go @@ -16,6 +16,7 @@ import ( ) type rtmpServerAPIConnsListItem struct { + ID uuid.UUID `json:"id"` Created time.Time `json:"created"` RemoteAddr string `json:"remoteAddr"` State string `json:"state"` @@ -24,7 +25,8 @@ type rtmpServerAPIConnsListItem struct { } type rtmpServerAPIConnsListData struct { - Items map[string]rtmpServerAPIConnsListItem `json:"items"` + PageCount int `json:"pageCount"` + Items []rtmpServerAPIConnsListItem `json:"items"` } type rtmpServerAPIConnsListRes struct { @@ -217,11 +219,12 @@ outer: case req := <-s.chAPISessionsList: data := &rtmpServerAPIConnsListData{ - Items: make(map[string]rtmpServerAPIConnsListItem), + Items: []rtmpServerAPIConnsListItem{}, } for c := range s.conns { - data.Items[c.uuid.String()] = rtmpServerAPIConnsListItem{ + data.Items = append(data.Items, rtmpServerAPIConnsListItem{ + ID: c.uuid, Created: c.created, RemoteAddr: c.remoteAddr().String(), State: func() string { @@ -236,7 +239,7 @@ outer: }(), BytesReceived: c.conn.BytesReceived(), BytesSent: c.conn.BytesSent(), - } + }) } req.res <- rtmpServerAPIConnsListRes{data: data} diff --git a/internal/core/rtsp_server.go b/internal/core/rtsp_server.go index 181fa4cc..fc72210c 100644 --- a/internal/core/rtsp_server.go +++ b/internal/core/rtsp_server.go @@ -20,6 +20,7 @@ import ( ) type rtspServerAPIConnsListItem struct { + ID uuid.UUID `json:"id"` Created time.Time `json:"created"` RemoteAddr string `json:"remoteAddr"` BytesReceived uint64 `json:"bytesReceived"` @@ -27,7 +28,8 @@ type rtspServerAPIConnsListItem struct { } type rtspServerAPIConnsListData struct { - Items map[string]rtspServerAPIConnsListItem `json:"items"` + PageCount int `json:"pageCount"` + Items []rtspServerAPIConnsListItem `json:"items"` } type rtspServerAPIConnsListRes struct { @@ -36,6 +38,7 @@ type rtspServerAPIConnsListRes struct { } type rtspServerAPISessionsListItem struct { + ID uuid.UUID `json:"id"` Created time.Time `json:"created"` RemoteAddr string `json:"remoteAddr"` State string `json:"state"` @@ -44,7 +47,8 @@ type rtspServerAPISessionsListItem struct { } type rtspServerAPISessionsListData struct { - Items map[string]rtspServerAPISessionsListItem `json:"items"` + PageCount int `json:"pageCount"` + Items []rtspServerAPISessionsListItem `json:"items"` } type rtspServerAPISessionsListRes struct { @@ -381,16 +385,17 @@ func (s *rtspServer) apiConnsList() rtspServerAPIConnsListRes { defer s.mutex.RUnlock() data := &rtspServerAPIConnsListData{ - Items: make(map[string]rtspServerAPIConnsListItem), + Items: []rtspServerAPIConnsListItem{}, } for _, c := range s.conns { - data.Items[c.uuid.String()] = rtspServerAPIConnsListItem{ + data.Items = append(data.Items, rtspServerAPIConnsListItem{ + ID: c.uuid, Created: c.created, RemoteAddr: c.remoteAddr().String(), BytesReceived: c.conn.BytesReceived(), BytesSent: c.conn.BytesSent(), - } + }) } return rtspServerAPIConnsListRes{data: data} @@ -408,11 +413,12 @@ func (s *rtspServer) apiSessionsList() rtspServerAPISessionsListRes { defer s.mutex.RUnlock() data := &rtspServerAPISessionsListData{ - Items: make(map[string]rtspServerAPISessionsListItem), + Items: []rtspServerAPISessionsListItem{}, } for _, s := range s.sessions { - data.Items[s.uuid.String()] = rtspServerAPISessionsListItem{ + data.Items = append(data.Items, rtspServerAPISessionsListItem{ + ID: s.uuid, Created: s.created, RemoteAddr: s.remoteAddr().String(), State: func() string { @@ -429,7 +435,7 @@ func (s *rtspServer) apiSessionsList() rtspServerAPISessionsListRes { }(), BytesReceived: s.session.BytesReceived(), BytesSent: s.session.BytesSent(), - } + }) } return rtspServerAPISessionsListRes{data: data} diff --git a/internal/core/webrtc_manager.go b/internal/core/webrtc_manager.go index b3d3a02a..5e11a4c0 100644 --- a/internal/core/webrtc_manager.go +++ b/internal/core/webrtc_manager.go @@ -65,6 +65,7 @@ func linkHeaderToIceServers(link []string) []webrtc.ICEServer { } type webRTCManagerAPISessionsListItem struct { + ID uuid.UUID `json:"id"` Created time.Time `json:"created"` RemoteAddr string `json:"remoteAddr"` PeerConnectionEstablished bool `json:"peerConnectionEstablished"` @@ -76,7 +77,8 @@ type webRTCManagerAPISessionsListItem struct { } type webRTCManagerAPISessionsListData struct { - Items map[string]webRTCManagerAPISessionsListItem `json:"items"` + PageCount int `json:"pageCount"` + Items []webRTCManagerAPISessionsListItem `json:"items"` } type webRTCManagerAPISessionsListRes struct { @@ -308,7 +310,7 @@ outer: case req := <-m.chAPISessionsList: data := &webRTCManagerAPISessionsListData{ - Items: make(map[string]webRTCManagerAPISessionsListItem), + Items: []webRTCManagerAPISessionsListItem{}, } for sx := range m.sessions { @@ -327,7 +329,8 @@ outer: bytesSent = pc.bytesSent() } - data.Items[sx.uuid.String()] = webRTCManagerAPISessionsListItem{ + data.Items = append(data.Items, webRTCManagerAPISessionsListItem{ + ID: sx.uuid, Created: sx.created, RemoteAddr: sx.req.remoteAddr, PeerConnectionEstablished: peerConnectionEstablished, @@ -341,7 +344,7 @@ outer: }(), BytesReceived: bytesReceived, BytesSent: bytesSent, - } + }) } req.res <- webRTCManagerAPISessionsListRes{data: data}