From b834be67a99f97124026912228470925d81add1c Mon Sep 17 00:00:00 2001 From: Winlin Date: Mon, 1 Sep 2025 10:12:27 -0400 Subject: [PATCH] AI: Use SrsHttpUri for URL parsing and add legacy RTMP URL conversion. v7.0.72 (#4463) Refactors the `srs_net_url_parse_tcurl` function to use the robust `SrsHttpUri` class for URL parsing and implements a dedicated legacy RTMP URL conversion function to handle various URL formats consistently. --------- Co-authored-by: OSSRS-AI --- trunk/doc/CHANGELOG.md | 1 + trunk/src/app/srs_app_server.hpp | 9 ++- trunk/src/core/srs_core_version7.hpp | 2 +- trunk/src/kernel/srs_kernel_utility.hpp | 5 ++ trunk/src/protocol/srs_protocol_utility.cpp | 66 +++++++++++----- trunk/src/protocol/srs_protocol_utility.hpp | 5 ++ trunk/src/utest/srs_utest_protocol.cpp | 2 +- trunk/src/utest/srs_utest_rtmp.cpp | 88 +++++++++++++++++++++ 8 files changed, 153 insertions(+), 25 deletions(-) diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 707ecd9a7..976b26b10 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-09-01, Merge [#4463](https://github.com/ossrs/srs/pull/4463): AI: Use SrsHttpUri for URL parsing and add legacy RTMP URL conversion. v7.0.72 (#4463) * v7.0, 2025-09-01, Merge [#4462](https://github.com/ossrs/srs/pull/4462): HTTP: Rename HTTP hijack to dynamic match for better clarity. v7.0.71 (#4462) * v7.0, 2025-08-31, Merge [#4461](https://github.com/ossrs/srs/pull/4461): AI: Extract shared components and improve SRS server architecture. v7.0.70 (#4461) * v7.0, 2025-08-31, Merge [#4460](https://github.com/ossrs/srs/pull/4460): AI: Always enable SRT protocol. v7.0.69 (#4460) diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 3cfb4ede3..146351fcf 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -60,7 +60,9 @@ class SrsPidFileLocker; // Initialize global shared variables cross all threads. extern srs_error_t srs_global_initialize(); -// SRS RTMP server, initialize and listen, start connection service thread, destroy client. +// SrsServer is the main server class of SRS (Simple Realtime Server) that provides comprehensive +// streaming media server functionality. It serves as the central orchestrator for all streaming +// protocols and services in a single-threaded, coroutine-based architecture. class SrsServer : public ISrsReloadHandler, // Reload framework for permormance optimization. public ISrsLiveSourceHandler, public ISrsTcpHandler, @@ -69,7 +71,6 @@ class SrsServer : public ISrsReloadHandler, // Reload framework for permormance public ISrsUdpMuxHandler { private: - // TODO: FIXME: Extract an HttpApiServer. ISrsHttpServeMux *http_api_mux_; SrsHttpServer *http_server_; @@ -118,8 +119,12 @@ private: // Stream Caster for GB28181. SrsGbListener *stream_caster_gb28181_; #endif + +private: // SRT acceptors for MPEG-TS over SRT. std::vector srt_acceptors_; + +private: // WebRTC UDP listeners for RTC server functionality. std::vector rtc_listeners_; // WebRTC session manager. diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 4e21271f8..a20622e88 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 71 +#define VERSION_REVISION 72 #endif \ No newline at end of file diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index df8cc4976..56340e602 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -232,6 +232,11 @@ extern bool srs_net_url_is_rtmp(std::string url); extern void srs_net_url_parse_tcurl(std::string tcUrl, std::string &schema, std::string &host, std::string &vhost, std::string &app, std::string &stream, int &port, std::string ¶m); +// Convert legacy RTMP URL format to standard format. +// Legacy format: rtmp://ip/app/app2?vhost=xxx/stream +// Standard format: rtmp://ip/app/app2/stream?vhost=xxx +extern std::string srs_net_url_convert_legacy_rtmp_url(const std::string &url); + // Guessing stream by app and param, to make OBS happy. For example: // rtmp://ip/live/livestream // rtmp://ip/live/livestream?secret=xxx diff --git a/trunk/src/protocol/srs_protocol_utility.cpp b/trunk/src/protocol/srs_protocol_utility.cpp index 69c36b1b4..3932fc940 100644 --- a/trunk/src/protocol/srs_protocol_utility.cpp +++ b/trunk/src/protocol/srs_protocol_utility.cpp @@ -43,35 +43,28 @@ using namespace std; void srs_net_url_parse_tcurl(string tcUrl, string &schema, string &host, string &vhost, string &app, string &stream, int &port, string ¶m) { - // For compatibility, transform - // rtmp://ip/app...vhost...VHOST/stream - // to typical format: - // rtmp://ip/app?vhost=VHOST/stream - string fullUrl = srs_strings_replace(tcUrl, "...vhost...", "?vhost="); - - // Standard URL is: - // rtmp://ip/app/app2/stream?k=v - // Where after last slash is stream. + // Build the full URL with stream and param if provided + string fullUrl = tcUrl; fullUrl += stream.empty() ? "/" : (stream.at(0) == '/' ? stream : "/" + stream); fullUrl += param.empty() ? "" : (param.at(0) == '?' ? param : "?" + param); - // First, we covert the FMLE URL to standard URL: - // rtmp://ip/app/app2?k=v/stream , or: - // rtmp://ip/app/app2#k=v/stream - size_t pos_query = fullUrl.find_first_of("?#"); - size_t pos_rslash = fullUrl.rfind("/"); - if (pos_rslash != string::npos && pos_query != string::npos && pos_query < pos_rslash) { - fullUrl = fullUrl.substr(0, pos_query) // rtmp://ip/app/app2 - + fullUrl.substr(pos_rslash) // /stream - + fullUrl.substr(pos_query, pos_rslash - pos_query); // ?k=v - } + // For compatibility, transform legacy ...vhost... format + // rtmp://ip/app...vhost...VHOST/stream + // to query parameter format: + // rtmp://ip/app?vhost=VHOST/stream + fullUrl = srs_strings_replace(fullUrl, "...vhost...", "?vhost="); + + // Convert legacy RTMP URL format to standard format + // Legacy: rtmp://ip/app/app2?vhost=xxx/stream + // Standard: rtmp://ip/app/app2/stream?vhost=xxx + fullUrl = srs_net_url_convert_legacy_rtmp_url(fullUrl); // Remove the _definst_ of FMLE URL. if (fullUrl.find("/_definst_") != string::npos) { fullUrl = srs_strings_replace(fullUrl, "/_definst_", ""); } - // Parse the standard URL. + // Parse the standard URL using SrsHttpUri. SrsHttpUri uri; srs_error_t err = srs_success; if ((err = uri.initialize(fullUrl)) != srs_success) { @@ -80,6 +73,7 @@ void srs_net_url_parse_tcurl(string tcUrl, string &schema, string &host, string return; } + // Extract basic URL components schema = uri.get_schema(); host = uri.get_host(); port = uri.get_port(); @@ -94,7 +88,7 @@ void srs_net_url_parse_tcurl(string tcUrl, string &schema, string &host, string if (app.empty()) app = SRS_CONSTS_RTMP_DEFAULT_APP; - // Try to parse vhost from query, or use host if not specified. + // Discover vhost from query parameters, or use host if not specified. string vhost_in_query = uri.get_query_by_key("vhost"); if (vhost_in_query.empty()) vhost_in_query = uri.get_query_by_key("domain"); @@ -109,6 +103,36 @@ void srs_net_url_parse_tcurl(string tcUrl, string &schema, string &host, string } } +string srs_net_url_convert_legacy_rtmp_url(const string &url) +{ + // Check if this is a legacy RTMP URL format: rtmp://ip/app/app2?vhost=xxx/stream + // We need to convert it to standard format: rtmp://ip/app/app2/stream?vhost=xxx + + // Find the query part starting with ? + size_t query_pos = url.find('?'); + + // Find the last slash in the URL + size_t last_slash_pos = url.rfind('/'); + if (last_slash_pos == string::npos) { + // No slash in URL, return as is + return url; + } + + // Check for normal legacy case: query exists and slash is after query + if (query_pos != string::npos && last_slash_pos > query_pos) { + // Normal legacy case: rtmp://ip/app/app2?vhost=xxx/stream + string base_url = url.substr(0, query_pos); // rtmp://ip/app/app2 + string query_part = url.substr(query_pos, last_slash_pos - query_pos); // ?vhost=xxx + string stream_part = url.substr(last_slash_pos); // /stream + + // Reconstruct as standard format: base_url + stream_part + query_part + return base_url + stream_part + query_part; + } + + // No conversion needed, return as is + return url; +} + void srs_net_url_guess_stream(string &app, string ¶m, string &stream) { size_t pos = std::string::npos; diff --git a/trunk/src/protocol/srs_protocol_utility.hpp b/trunk/src/protocol/srs_protocol_utility.hpp index 834c8921d..9100f875d 100644 --- a/trunk/src/protocol/srs_protocol_utility.hpp +++ b/trunk/src/protocol/srs_protocol_utility.hpp @@ -56,6 +56,11 @@ class ISrsReader; extern void srs_net_url_parse_tcurl(std::string tcUrl, std::string &schema, std::string &host, std::string &vhost, std::string &app, std::string &stream, int &port, std::string ¶m); +// Convert legacy RTMP URL format to standard format. +// Legacy format: rtmp://ip/app/app2?vhost=xxx/stream +// Standard format: rtmp://ip/app/app2/stream?vhost=xxx +extern std::string srs_net_url_convert_legacy_rtmp_url(const std::string &url); + // Guessing stream by app and param, to make OBS happy. For example: // rtmp://ip/live/livestream // rtmp://ip/live/livestream?secret=xxx diff --git a/trunk/src/utest/srs_utest_protocol.cpp b/trunk/src/utest/srs_utest_protocol.cpp index 2e6c45c11..9cc8cb5d3 100644 --- a/trunk/src/utest/srs_utest_protocol.cpp +++ b/trunk/src/utest/srs_utest_protocol.cpp @@ -5218,7 +5218,7 @@ VOID TEST(ProtocolRTMPTest, RTMPRequest) req.stream = "livestream"; srs_net_url_parse_tcurl("rtmp://std.ossrs.net/live#b=2", req.schema, req.host, req.vhost, req.app, req.stream, req.port, param); - EXPECT_STREQ("#b=2", param.c_str()); + EXPECT_STREQ("#b=2/livestream", param.c_str()); param = ""; req.stream = "livestream"; diff --git a/trunk/src/utest/srs_utest_rtmp.cpp b/trunk/src/utest/srs_utest_rtmp.cpp index 9cc86bb2a..ff346bd54 100644 --- a/trunk/src/utest/srs_utest_rtmp.cpp +++ b/trunk/src/utest/srs_utest_rtmp.cpp @@ -3091,6 +3091,94 @@ VOID TEST(ProtocolRTMPTest, ParseRTMPURL) } } +VOID TEST(ProtocolRTMPTest, ConvertLegacyRtmpUrl) +{ + // Test legacy format conversion: rtmp://ip/app?vhost=xxx/stream -> rtmp://ip/app/stream?vhost=xxx + if (true) { + string legacy_url = "rtmp://127.0.0.1:1935/live?vhost=demo.com/livestream"; + string standard_url = srs_net_url_convert_legacy_rtmp_url(legacy_url); + EXPECT_STREQ("rtmp://127.0.0.1:1935/live/livestream?vhost=demo.com", standard_url.c_str()); + } + + // Test with multiple query parameters + if (true) { + string legacy_url = "rtmp://192.168.1.100/app?vhost=test.com&token=abc/stream"; + string standard_url = srs_net_url_convert_legacy_rtmp_url(legacy_url); + EXPECT_STREQ("rtmp://192.168.1.100/app/stream?vhost=test.com&token=abc", standard_url.c_str()); + } + + // Test with complex stream name + if (true) { + string legacy_url = "rtmp://host.com/myapp?vhost=vhost.com/stream_name_123"; + string standard_url = srs_net_url_convert_legacy_rtmp_url(legacy_url); + EXPECT_STREQ("rtmp://host.com/myapp/stream_name_123?vhost=vhost.com", standard_url.c_str()); + } + + // Test with multiple app levels: rtmp://ip/app/app2?vhost=xxx/stream + if (true) { + string legacy_url = "rtmp://127.0.0.1:1935/live/sub?vhost=demo.com/livestream"; + string standard_url = srs_net_url_convert_legacy_rtmp_url(legacy_url); + EXPECT_STREQ("rtmp://127.0.0.1:1935/live/sub/livestream?vhost=demo.com", standard_url.c_str()); + } + + // Test with three app levels: rtmp://ip/app/app2/app3?vhost=xxx/stream + if (true) { + string legacy_url = "rtmp://192.168.1.100/app1/app2/app3?vhost=test.com&token=abc/stream"; + string standard_url = srs_net_url_convert_legacy_rtmp_url(legacy_url); + EXPECT_STREQ("rtmp://192.168.1.100/app1/app2/app3/stream?vhost=test.com&token=abc", standard_url.c_str()); + } + + // Test with multiple app levels and complex parameters + if (true) { + string legacy_url = "rtmp://host.com:8080/live/hls/dash?vhost=cdn.com&key=value&auth=token/my_stream_123"; + string standard_url = srs_net_url_convert_legacy_rtmp_url(legacy_url); + EXPECT_STREQ("rtmp://host.com:8080/live/hls/dash/my_stream_123?vhost=cdn.com&key=value&auth=token", standard_url.c_str()); + } + + // Test standard format (should remain unchanged) + if (true) { + string standard_url = "rtmp://127.0.0.1:1935/live/livestream?vhost=demo.com"; + string result_url = srs_net_url_convert_legacy_rtmp_url(standard_url); + EXPECT_STREQ("rtmp://127.0.0.1:1935/live/livestream?vhost=demo.com", result_url.c_str()); + } + + // Test URL without query string (should remain unchanged) + if (true) { + string simple_url = "rtmp://127.0.0.1:1935/live/livestream"; + string result_url = srs_net_url_convert_legacy_rtmp_url(simple_url); + EXPECT_STREQ("rtmp://127.0.0.1:1935/live/livestream", result_url.c_str()); + } + + // Test URL with query but no slash after query (should remain unchanged) + if (true) { + string url_no_slash = "rtmp://127.0.0.1:1935/live?vhost=demo.com"; + string result_url = srs_net_url_convert_legacy_rtmp_url(url_no_slash); + EXPECT_STREQ("rtmp://127.0.0.1:1935/live?vhost=demo.com", result_url.c_str()); + } + + // Test special case: malformed URL with stream appended after fragments + // This happens when URL building process appends stream to URLs that already have fragments + if (true) { + string malformed_url = "rtmp://std.ossrs.net/live#b=2/livestream"; + string fixed_url = srs_net_url_convert_legacy_rtmp_url(malformed_url); + EXPECT_STREQ("rtmp://std.ossrs.net/live#b=2/livestream", fixed_url.c_str()); + } + + // Test special case: malformed URL with query and fragment, stream appended after + if (true) { + string malformed_url = "rtmp://host.com/app?a=1#b=2/stream"; + string fixed_url = srs_net_url_convert_legacy_rtmp_url(malformed_url); + EXPECT_STREQ("rtmp://host.com/app/stream?a=1#b=2", fixed_url.c_str()); + } + + // Test special case: malformed URL with complex fragments, stream appended after + if (true) { + string malformed_url = "rtmp://test.com/live?vhost=demo&token=abc#fragment1#fragment2/mystream"; + string fixed_url = srs_net_url_convert_legacy_rtmp_url(malformed_url); + EXPECT_STREQ("rtmp://test.com/live/mystream?vhost=demo&token=abc#fragment1#fragment2", fixed_url.c_str()); + } +} + VOID TEST(ProtocolRTMPTest, GenerateURL) { if (true) {