From 3897f10a4dd7337b027566ae9b172d1953ef7578 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 16 Nov 2025 16:32:58 +0100 Subject: [PATCH 1/3] Add api endpoint to return supported schema --- .vscode/launch.json | 19 +++++++ internal/streams/api.go | 10 ++++ internal/streams/api_test.go | 102 +++++++++++++++++++++++++++++++++++ internal/streams/handlers.go | 13 +++++ internal/streams/streams.go | 1 + 5 files changed, 145 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 internal/streams/api_test.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..6242075e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Debug go2rtc", + "type": "go", + "request": "launch", + "mode": "auto", + "env": { + "CGO_ENABLED": "0" + }, + "program": "main.go", + } + ] +} \ No newline at end of file diff --git a/internal/streams/api.go b/internal/streams/api.go index 28f09708..bd9e7f7c 100644 --- a/internal/streams/api.go +++ b/internal/streams/api.go @@ -176,3 +176,13 @@ func apiPreload(w http.ResponseWriter, r *http.Request) { http.Error(w, "", http.StatusMethodNotAllowed) } } + +func apiSchemes(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + http.Error(w, "", http.StatusMethodNotAllowed) + return + } + + schemes := GetSupportedSchemes() + api.ResponseJSON(w, schemes) +} diff --git a/internal/streams/api_test.go b/internal/streams/api_test.go new file mode 100644 index 00000000..0a4b4c06 --- /dev/null +++ b/internal/streams/api_test.go @@ -0,0 +1,102 @@ +package streams + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/AlexxIT/go2rtc/pkg/core" + "github.com/stretchr/testify/require" +) + +func TestApiSchemes(t *testing.T) { + // Setup: Register some test handlers and redirects + HandleFunc("rtsp", func(url string) (core.Producer, error) { return nil, nil }) + HandleFunc("rtmp", func(url string) (core.Producer, error) { return nil, nil }) + RedirectFunc("http", func(url string) (string, error) { return "", nil }) + + t.Run("GET request returns schemes", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/schemes", nil) + w := httptest.NewRecorder() + + apiSchemes(w, req) + + require.Equal(t, http.StatusOK, w.Code) + require.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var schemes []string + err := json.Unmarshal(w.Body.Bytes(), &schemes) + require.NoError(t, err) + require.NotEmpty(t, schemes) + + // Check that our test schemes are in the response + require.Contains(t, schemes, "rtsp") + require.Contains(t, schemes, "rtmp") + require.Contains(t, schemes, "http") + }) + + t.Run("POST request returns method not allowed", func(t *testing.T) { + req := httptest.NewRequest("POST", "/api/schemes", nil) + w := httptest.NewRecorder() + + apiSchemes(w, req) + + require.Equal(t, http.StatusMethodNotAllowed, w.Code) + }) + + t.Run("PUT request returns method not allowed", func(t *testing.T) { + req := httptest.NewRequest("PUT", "/api/schemes", nil) + w := httptest.NewRecorder() + + apiSchemes(w, req) + + require.Equal(t, http.StatusMethodNotAllowed, w.Code) + }) + + t.Run("DELETE request returns method not allowed", func(t *testing.T) { + req := httptest.NewRequest("DELETE", "/api/schemes", nil) + w := httptest.NewRecorder() + + apiSchemes(w, req) + + require.Equal(t, http.StatusMethodNotAllowed, w.Code) + }) + + t.Run("PATCH request returns method not allowed", func(t *testing.T) { + req := httptest.NewRequest("PATCH", "/api/schemes", nil) + w := httptest.NewRecorder() + + apiSchemes(w, req) + + require.Equal(t, http.StatusMethodNotAllowed, w.Code) + }) +} + +func TestApiSchemesNoDuplicates(t *testing.T) { + // Setup: Register a scheme in both handlers and redirects + HandleFunc("duplicate", func(url string) (core.Producer, error) { return nil, nil }) + RedirectFunc("duplicate", func(url string) (string, error) { return "", nil }) + + req := httptest.NewRequest("GET", "/api/schemes", nil) + w := httptest.NewRecorder() + + apiSchemes(w, req) + + require.Equal(t, http.StatusOK, w.Code) + + var schemes []string + err := json.Unmarshal(w.Body.Bytes(), &schemes) + require.NoError(t, err) + + // Count occurrences of "duplicate" + count := 0 + for _, scheme := range schemes { + if scheme == "duplicate" { + count++ + } + } + + // Should only appear once + require.Equal(t, 1, count, "scheme 'duplicate' should appear exactly once") +} diff --git a/internal/streams/handlers.go b/internal/streams/handlers.go index 8922bb8d..91efb975 100644 --- a/internal/streams/handlers.go +++ b/internal/streams/handlers.go @@ -2,7 +2,9 @@ package streams import ( "errors" + "maps" "regexp" + "slices" "strings" "github.com/AlexxIT/go2rtc/pkg/core" @@ -16,6 +18,17 @@ func HandleFunc(scheme string, handler Handler) { handlers[scheme] = handler } +func GetSupportedSchemes() []string { + unique := make(map[string]bool) + for scheme := range handlers { + unique[scheme] = true + } + for scheme := range redirects { + unique[scheme] = true + } + return slices.Collect(maps.Keys(unique)) +} + func HasProducer(url string) bool { if i := strings.IndexByte(url, ':'); i > 0 { scheme := url[:i] diff --git a/internal/streams/streams.go b/internal/streams/streams.go index 633ad2d1..8d5a9fe7 100644 --- a/internal/streams/streams.go +++ b/internal/streams/streams.go @@ -28,6 +28,7 @@ func Init() { api.HandleFunc("api/streams", apiStreams) api.HandleFunc("api/streams.dot", apiStreamsDOT) api.HandleFunc("api/preload", apiPreload) + api.HandleFunc("api/schemes", apiSchemes) if cfg.Publish == nil && cfg.Preload == nil { return From e2b63a4f6c78f6432b938287ed42e1a45cb976b5 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sun, 16 Nov 2025 16:40:04 +0100 Subject: [PATCH 2/3] Remove duplicate code --- internal/streams/api_test.go | 42 +++++++++--------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/internal/streams/api_test.go b/internal/streams/api_test.go index 0a4b4c06..414a9380 100644 --- a/internal/streams/api_test.go +++ b/internal/streams/api_test.go @@ -36,40 +36,18 @@ func TestApiSchemes(t *testing.T) { require.Contains(t, schemes, "http") }) - t.Run("POST request returns method not allowed", func(t *testing.T) { - req := httptest.NewRequest("POST", "/api/schemes", nil) - w := httptest.NewRecorder() + t.Run("non-GET requests return method not allowed", func(t *testing.T) { + methods := []string{"POST", "PUT", "DELETE", "PATCH"} + for _, method := range methods { + t.Run(method, func(t *testing.T) { + req := httptest.NewRequest(method, "/api/schemes", nil) + w := httptest.NewRecorder() - apiSchemes(w, req) + apiSchemes(w, req) - require.Equal(t, http.StatusMethodNotAllowed, w.Code) - }) - - t.Run("PUT request returns method not allowed", func(t *testing.T) { - req := httptest.NewRequest("PUT", "/api/schemes", nil) - w := httptest.NewRecorder() - - apiSchemes(w, req) - - require.Equal(t, http.StatusMethodNotAllowed, w.Code) - }) - - t.Run("DELETE request returns method not allowed", func(t *testing.T) { - req := httptest.NewRequest("DELETE", "/api/schemes", nil) - w := httptest.NewRecorder() - - apiSchemes(w, req) - - require.Equal(t, http.StatusMethodNotAllowed, w.Code) - }) - - t.Run("PATCH request returns method not allowed", func(t *testing.T) { - req := httptest.NewRequest("PATCH", "/api/schemes", nil) - w := httptest.NewRecorder() - - apiSchemes(w, req) - - require.Equal(t, http.StatusMethodNotAllowed, w.Code) + require.Equal(t, http.StatusMethodNotAllowed, w.Code) + }) + } }) } From 0bae158e41a16fe8df037e4f355b226d2bbf60ad Mon Sep 17 00:00:00 2001 From: Alex X Date: Sun, 16 Nov 2025 19:01:06 +0300 Subject: [PATCH 3/3] Code refactoring for #1939 --- .vscode/launch.json | 19 ------------------- internal/streams/api.go | 8 +------- internal/streams/api_test.go | 14 -------------- internal/streams/handlers.go | 16 +++++++++------- 4 files changed, 10 insertions(+), 47 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 6242075e..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - { - "name": "Debug go2rtc", - "type": "go", - "request": "launch", - "mode": "auto", - "env": { - "CGO_ENABLED": "0" - }, - "program": "main.go", - } - ] -} \ No newline at end of file diff --git a/internal/streams/api.go b/internal/streams/api.go index bd9e7f7c..0cc537c3 100644 --- a/internal/streams/api.go +++ b/internal/streams/api.go @@ -178,11 +178,5 @@ func apiPreload(w http.ResponseWriter, r *http.Request) { } func apiSchemes(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { - http.Error(w, "", http.StatusMethodNotAllowed) - return - } - - schemes := GetSupportedSchemes() - api.ResponseJSON(w, schemes) + api.ResponseJSON(w, SupportedSchemes()) } diff --git a/internal/streams/api_test.go b/internal/streams/api_test.go index 414a9380..2cb93d2a 100644 --- a/internal/streams/api_test.go +++ b/internal/streams/api_test.go @@ -35,20 +35,6 @@ func TestApiSchemes(t *testing.T) { require.Contains(t, schemes, "rtmp") require.Contains(t, schemes, "http") }) - - t.Run("non-GET requests return method not allowed", func(t *testing.T) { - methods := []string{"POST", "PUT", "DELETE", "PATCH"} - for _, method := range methods { - t.Run(method, func(t *testing.T) { - req := httptest.NewRequest(method, "/api/schemes", nil) - w := httptest.NewRecorder() - - apiSchemes(w, req) - - require.Equal(t, http.StatusMethodNotAllowed, w.Code) - }) - } - }) } func TestApiSchemesNoDuplicates(t *testing.T) { diff --git a/internal/streams/handlers.go b/internal/streams/handlers.go index 91efb975..9433044b 100644 --- a/internal/streams/handlers.go +++ b/internal/streams/handlers.go @@ -2,9 +2,7 @@ package streams import ( "errors" - "maps" "regexp" - "slices" "strings" "github.com/AlexxIT/go2rtc/pkg/core" @@ -18,15 +16,19 @@ func HandleFunc(scheme string, handler Handler) { handlers[scheme] = handler } -func GetSupportedSchemes() []string { - unique := make(map[string]bool) +func SupportedSchemes() []string { + uniqueKeys := make(map[string]struct{}, len(handlers)+len(redirects)) for scheme := range handlers { - unique[scheme] = true + uniqueKeys[scheme] = struct{}{} } for scheme := range redirects { - unique[scheme] = true + uniqueKeys[scheme] = struct{}{} } - return slices.Collect(maps.Keys(unique)) + resultKeys := make([]string, 0, len(uniqueKeys)) + for key := range uniqueKeys { + resultKeys = append(resultKeys, key) + } + return resultKeys } func HasProducer(url string) bool {