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 <winlinam@gmail.com>
This commit is contained in:
Winlin 2025-09-01 10:12:27 -04:00 committed by GitHub
parent 0d6d36d1fb
commit b834be67a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 153 additions and 25 deletions

View File

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>
## 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)

View File

@ -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<SrsSrtAcceptor *> srt_acceptors_;
private:
// WebRTC UDP listeners for RTC server functionality.
std::vector<SrsUdpMuxListener *> rtc_listeners_;
// WebRTC session manager.

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 71
#define VERSION_REVISION 72
#endif

View File

@ -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 &param);
// 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

View File

@ -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 &param)
{
// 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 &param, string &stream)
{
size_t pos = std::string::npos;

View File

@ -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 &param);
// 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

View File

@ -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";

View File

@ -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) {