This commit addresses issue #4502 by implementing proper HTTP error handling for WHIP endpoints, allowing clients to receive detailed error information instead of empty responses. Before this change: - WHIP clients received "Empty reply from server" when publish failed - No way to distinguish between different failure reasons After this change: - WHIP clients receive proper HTTP status codes (400/401/409/500) - Error responses include error code and description - Clients can distinguish between SDP errors, stream busy, auth failures, etc. If success: ``` < HTTP/1.1 201 Created < Content-Type: application/sdp < Location: /rtc/v1/whip/?action=delete&token=77h5570j1&app=live&stream=livestream&session=x209e499:TKxW < Content-Length: 1376 < Server: SRS/7.0.120(Kai) < v=0 ...... ``` If request without SDP: ``` curl 'http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream' -v < HTTP/1.1 400 Bad Request < Content-Type: text/plain; charset=utf-8 < Content-Length: 13 < Server: SRS/7.0.120(Kai) < 5043: RtcInvalidSdp ``` If request with corrupt SDP: ``` curl 'http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream' --data-raw $'invalidsdp' -v < HTTP/1.1 400 Bad Request < Content-Type: text/plain; charset=utf-8 < Content-Length: 18 < Server: SRS/7.0.120(Kai) < 5012: RtcSdpDecode ``` If request with insufficient SDP: ``` curl 'http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream' --data-raw $'v=0' -v < HTTP/1.1 400 Bad Request < Content-Type: text/plain; charset=utf-8 < Content-Length: 21 < Server: SRS/7.0.120(Kai) < 5018: RtcSdpNegotiate ``` If publish to a exists stream: ``` < HTTP/1.1 409 Conflict < Content-Type: text/plain; charset=utf-8 < Content-Length: 16 < Server: SRS/7.0.120(Kai) < 1028: StreamBusy ``` If HTTP hooks or security verify failed: ``` < HTTP/1.1 401 Unauthorized < Content-Type: text/plain; charset=utf-8 < Content-Length: 16 < Server: SRS/7.0.120(Kai) < 1102: SystemAuth ``` Other errors, for exmaple, RTC disabled: ``` < HTTP/1.1 500 Internal Server Error < Content-Type: text/plain; charset=utf-8 < Content-Length: 17 < Server: SRS/7.0.120(Kai) < 5021: RtcDisabled ``` --------- Co-authored-by: OSSRS-AI <winlinam@gmail.com>
This commit is contained in:
parent
99970d6ba0
commit
f392f9a5a7
|
|
@ -7,6 +7,7 @@ The changelog for SRS.
|
|||
<a name="v7-changes"></a>
|
||||
|
||||
## SRS 7.0 Changelog
|
||||
* v7.0, 2025-11-07, AI: WHIP: Return detailed HTTP error responses with proper status codes. v7.0.121 (#4502)
|
||||
* v7.0, 2025-11-07, AI: HLS: Support query string in hls_key_url for JWT tokens. v7.0.120 (#4426)
|
||||
* v7.0, 2025-11-07, AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850)
|
||||
* v7.0, 2025-11-05, AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554)
|
||||
|
|
|
|||
|
|
@ -554,12 +554,12 @@ srs_error_t SrsGoApiRtcPublish::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMe
|
|||
}
|
||||
|
||||
if ((err = security_->check(SrsRtcConnPublish, ruc->req_->ip_, ruc->req_)) != srs_success) {
|
||||
return srs_error_wrap(err, "RTC: security check");
|
||||
return srs_error_transform(ERROR_SYSTEM_AUTH, err, "RTC: security check");
|
||||
}
|
||||
|
||||
// We must do hook after stat, because depends on it.
|
||||
if ((err = http_hooks_on_publish(ruc->req_)) != srs_success) {
|
||||
return srs_error_wrap(err, "RTC: http_hooks_on_publish");
|
||||
return srs_error_transform(ERROR_SYSTEM_AUTH, err, "RTC: http_hooks_on_publish");
|
||||
}
|
||||
|
||||
ostringstream os;
|
||||
|
|
@ -661,6 +661,44 @@ SrsGoApiRtcWhip::~SrsGoApiRtcWhip()
|
|||
}
|
||||
|
||||
srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
|
||||
{
|
||||
int code = 0;
|
||||
string code_str;
|
||||
if (true) {
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
err = serve_http_with(w, r);
|
||||
if (err == srs_success) {
|
||||
return err;
|
||||
}
|
||||
|
||||
code = srs_error_code(err);
|
||||
code_str = srs_error_code_str(err);
|
||||
srs_warn("WHIP: serve http for %s with err %d:%s, %s",
|
||||
r->url().c_str(), code, code_str.c_str(), srs_error_desc(err).c_str());
|
||||
srs_freep(err);
|
||||
}
|
||||
|
||||
if (code == ERROR_RTC_INVALID_SDP || code == ERROR_RTC_SDP_DECODE || code == ERROR_RTC_SDP_EXCHANGE) {
|
||||
string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
|
||||
return srs_go_http_error(w, SRS_CONSTS_HTTP_BadRequest, msg);
|
||||
}
|
||||
|
||||
if (code == ERROR_SYSTEM_STREAM_BUSY) {
|
||||
string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
|
||||
return srs_go_http_error(w, SRS_CONSTS_HTTP_Conflict, msg);
|
||||
}
|
||||
|
||||
if (code == ERROR_SYSTEM_AUTH) {
|
||||
string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
|
||||
return srs_go_http_error(w, SRS_CONSTS_HTTP_Unauthorized, msg);
|
||||
}
|
||||
|
||||
string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
|
||||
return srs_go_http_error(w, SRS_CONSTS_HTTP_InternalServerError, msg);
|
||||
}
|
||||
|
||||
srs_error_t SrsGoApiRtcWhip::serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
|
|
@ -691,14 +729,14 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa
|
|||
}
|
||||
|
||||
SrsRtcUserConfig ruc;
|
||||
if ((err = do_serve_http(w, r, &ruc)) != srs_success) {
|
||||
if ((err = do_serve_http_with(w, r, &ruc)) != srs_success) {
|
||||
return srs_error_wrap(err, "serve");
|
||||
}
|
||||
if (ruc.local_sdp_str_.empty()) {
|
||||
return srs_go_http_error(w, SRS_CONSTS_HTTP_InternalServerError);
|
||||
}
|
||||
|
||||
// The SDP to response.
|
||||
if (ruc.local_sdp_str_.empty()) {
|
||||
return srs_error_new(ERROR_RTC_INVALID_SDP, "empty local sdp");
|
||||
}
|
||||
string sdp = ruc.local_sdp_str_;
|
||||
|
||||
// Setup the content type to SDP.
|
||||
|
|
@ -714,7 +752,7 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa
|
|||
return w->write((char *)sdp.data(), (int)sdp.length());
|
||||
}
|
||||
|
||||
srs_error_t SrsGoApiRtcWhip::do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc)
|
||||
srs_error_t SrsGoApiRtcWhip::do_serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
|
|
@ -796,6 +834,9 @@ srs_error_t SrsGoApiRtcWhip::do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMe
|
|||
|
||||
// TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information.
|
||||
ruc->remote_sdp_str_ = remote_sdp_str;
|
||||
if (ruc->remote_sdp_str_.empty()) {
|
||||
return srs_error_new(ERROR_RTC_INVALID_SDP, "empty remote sdp");
|
||||
}
|
||||
if ((err = ruc->remote_sdp_.parse(remote_sdp_str)) != srs_success) {
|
||||
return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ public:
|
|||
|
||||
// clang-format off
|
||||
SRS_DECLARE_PRIVATE: // clang-format on
|
||||
virtual srs_error_t do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc);
|
||||
virtual srs_error_t serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r);
|
||||
virtual srs_error_t do_serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc);
|
||||
};
|
||||
|
||||
class SrsGoApiRtcNACK : public ISrsHttpHandler
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 120
|
||||
#define VERSION_REVISION 121
|
||||
|
||||
#endif
|
||||
|
|
@ -327,18 +327,21 @@ SrsCplxError *SrsCplxError::wrap(const char *func, const char *file, int line, S
|
|||
int r0 = vsnprintf(buffer, maxLogBuf, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
// Ensure buffer is null-terminated even if vsnprintf failed or truncated.
|
||||
if (r0 < 0) {
|
||||
buffer[0] = '\0';
|
||||
} else if (r0 >= maxLogBuf) {
|
||||
buffer[maxLogBuf - 1] = '\0';
|
||||
}
|
||||
|
||||
SrsCplxError *err = new SrsCplxError();
|
||||
|
||||
err->func_ = func;
|
||||
err->file_ = file;
|
||||
err->line_ = line;
|
||||
if (v) {
|
||||
err->code_ = v->code_;
|
||||
}
|
||||
err->code_ = srs_error_code(v);
|
||||
err->rerrno_ = rerrno;
|
||||
if (r0 > 0 && r0 < maxLogBuf) {
|
||||
err->msg_ = string(buffer, r0);
|
||||
}
|
||||
err->msg_ = string(buffer);
|
||||
err->wrapped_ = v;
|
||||
if (_srs_context) {
|
||||
err->cid_ = _srs_context->get_id();
|
||||
|
|
@ -347,6 +350,29 @@ SrsCplxError *SrsCplxError::wrap(const char *func, const char *file, int line, S
|
|||
return err;
|
||||
}
|
||||
|
||||
SrsCplxError *SrsCplxError::transform(const char *func, const char *file, int line, int code, SrsCplxError *v, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
static char *buffer = new char[maxLogBuf];
|
||||
int r0 = vsnprintf(buffer, maxLogBuf, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
// Ensure buffer is null-terminated even if vsnprintf failed or truncated.
|
||||
if (r0 < 0) {
|
||||
buffer[0] = '\0';
|
||||
} else if (r0 >= maxLogBuf) {
|
||||
buffer[maxLogBuf - 1] = '\0';
|
||||
}
|
||||
|
||||
// Wrap the error with additional context showing the code transformation.
|
||||
SrsCplxError *err = NULL;
|
||||
err = wrap(func, file, line, v, "code transformed (%d => %d): %s", srs_error_code(v), code, buffer);
|
||||
err->code_ = code;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
SrsCplxError *SrsCplxError::success()
|
||||
{
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -111,7 +111,9 @@
|
|||
XX(ERROR_STREAM_DISPOSING, 1098, "StreamDisposing", "Stream is disposing") \
|
||||
XX(ERROR_NOT_IMPLEMENTED, 1099, "NotImplemented", "Feature is not implemented") \
|
||||
XX(ERROR_NOT_SUPPORTED, 1100, "NotSupported", "Feature is not supported") \
|
||||
XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file")
|
||||
XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file") \
|
||||
XX(ERROR_SYSTEM_AUTH, 1102, "SystemAuth", "Failed to authenticate stream")
|
||||
|
||||
|
||||
/**************************************************/
|
||||
/* RTMP protocol error. */
|
||||
|
|
@ -380,7 +382,8 @@
|
|||
XX(ERROR_RTSP_NO_TRACK, 5039, "RtspNoTrack", "Drop RTSP packet for track not found") \
|
||||
XX(ERROR_RTSP_TOKEN_NOT_NORMAL, 5040, "RtspToken", "Invalid RTSP token state not normal") \
|
||||
XX(ERROR_RTSP_REQUEST_HEADER_EOF, 5041, "RtspHeaderEof", "Invalid RTSP request for header EOF") \
|
||||
XX(ERROR_RTSP_NEED_MORE_DATA, 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing")
|
||||
XX(ERROR_RTSP_NEED_MORE_DATA, 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing") \
|
||||
XX(ERROR_RTC_INVALID_SDP, 5043, "RtcInvalidSdp", "Invalid SDP for RTC")
|
||||
|
||||
/**************************************************/
|
||||
/* SRT protocol error. */
|
||||
|
|
@ -465,6 +468,7 @@ SRS_DECLARE_PRIVATE: // clang-format on
|
|||
public:
|
||||
static SrsCplxError *create(const char *func, const char *file, int line, int code, const char *fmt, ...);
|
||||
static SrsCplxError *wrap(const char *func, const char *file, int line, SrsCplxError *err, const char *fmt, ...);
|
||||
static SrsCplxError *transform(const char *func, const char *file, int line, int code, SrsCplxError *err, const char *fmt, ...);
|
||||
static SrsCplxError *success();
|
||||
static SrsCplxError *copy(SrsCplxError *from);
|
||||
static std::string description(SrsCplxError *err);
|
||||
|
|
@ -479,13 +483,73 @@ public:
|
|||
|
||||
// Error helpers, should use these functions to new or wrap an error.
|
||||
#define srs_success NULL // SrsCplxError::success()
|
||||
#define srs_error_new(ret, fmt, ...) SrsCplxError::create(__FUNCTION__, __FILE__, __LINE__, ret, fmt, ##__VA_ARGS__)
|
||||
|
||||
// Create a new error with the specified error code and message.
|
||||
//
|
||||
// Example:
|
||||
// if (fd < 0) {
|
||||
// return srs_error_new(ERROR_SOCKET_CREATE, "create socket fd=%d", fd);
|
||||
// }
|
||||
#define srs_error_new(code, fmt, ...) SrsCplxError::create(__FUNCTION__, __FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
|
||||
|
||||
// Wrap an existing error with additional context. The error code is
|
||||
// preserved from the wrapped error.
|
||||
//
|
||||
// Example:
|
||||
// if ((err = do_connect(host, port)) != srs_success) {
|
||||
// return srs_error_wrap(err, "connect to %s:%d", host.c_str(), port);
|
||||
// }
|
||||
#define srs_error_wrap(err, fmt, ...) SrsCplxError::wrap(__FUNCTION__, __FILE__, __LINE__, err, fmt, ##__VA_ARGS__)
|
||||
|
||||
// Transform an error by wrapping it and changing its error code. Useful
|
||||
// for converting internal errors to protocol-specific error codes (e.g.,
|
||||
// HTTP, RTMP). The wrapped error chain is preserved.
|
||||
//
|
||||
// Example:
|
||||
// if ((err = http_hooks_on_publish(ruc->req_)) != srs_success) {
|
||||
// return srs_error_transform(ERROR_SYSTEM_AUTH, err, "RTC: http_hooks_on_publish");
|
||||
// }
|
||||
#define srs_error_transform(code, err, fmt, ...) SrsCplxError::transform(__FUNCTION__, __FILE__, __LINE__, code, err, fmt, ##__VA_ARGS__)
|
||||
|
||||
// Copy an error object. Returns a new error object with the same content.
|
||||
//
|
||||
// Example:
|
||||
// srs_error_t err_copy = srs_error_copy(err);
|
||||
#define srs_error_copy(err) SrsCplxError::copy(err)
|
||||
|
||||
// Get the full description of an error including the entire error chain.
|
||||
//
|
||||
// Example:
|
||||
// srs_error_t err = do_something();
|
||||
// srs_warn("error: %s", srs_error_desc(err).c_str());
|
||||
#define srs_error_desc(err) SrsCplxError::description(err)
|
||||
|
||||
// Get a brief summary of an error (only the top-level message).
|
||||
//
|
||||
// Example:
|
||||
// srs_error_t err = do_something();
|
||||
// srs_trace("error summary: %s", srs_error_summary(err).c_str());
|
||||
#define srs_error_summary(err) SrsCplxError::summary(err)
|
||||
|
||||
// Get the error code as an integer.
|
||||
//
|
||||
// Example:
|
||||
// int code = srs_error_code(err);
|
||||
// if (code == ERROR_SOCKET_TIMEOUT) { ... }
|
||||
#define srs_error_code(err) SrsCplxError::error_code(err)
|
||||
|
||||
// Get the error code as a short string (e.g., "SocketTimeout").
|
||||
//
|
||||
// Example:
|
||||
// string code_str = srs_error_code_str(err);
|
||||
// srs_trace("error code: %s", code_str.c_str());
|
||||
#define srs_error_code_str(err) SrsCplxError::error_code_str(err)
|
||||
|
||||
// Get the error code description (e.g., "Socket io timeout").
|
||||
//
|
||||
// Example:
|
||||
// string desc = srs_error_code_strlong(err);
|
||||
// srs_warn("error description: %s", desc.c_str());
|
||||
#define srs_error_code_strlong(err) SrsCplxError::error_code_strlong(err)
|
||||
|
||||
#ifndef srs_assert
|
||||
|
|
|
|||
|
|
@ -1142,6 +1142,74 @@ VOID TEST(KernelErrorTest, ErrorChaining)
|
|||
srs_freep(level3);
|
||||
}
|
||||
|
||||
VOID TEST(KernelErrorTest, SrsCplxErrorTransform)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
||||
// Test transform with real error - changes error code
|
||||
srs_error_t original = srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "stream busy");
|
||||
err = srs_error_transform(ERROR_SYSTEM_AUTH, original, "authentication failed");
|
||||
|
||||
EXPECT_TRUE(err != srs_success);
|
||||
EXPECT_EQ(ERROR_SYSTEM_AUTH, srs_error_code(err)); // Should have new code
|
||||
|
||||
// Check description contains both messages and code transformation info
|
||||
std::string desc = srs_error_desc(err);
|
||||
EXPECT_TRUE(desc.find("authentication failed") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("stream busy") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("code transformed") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("1028") != std::string::npos); // Original code ERROR_SYSTEM_STREAM_BUSY
|
||||
EXPECT_TRUE(desc.find("1102") != std::string::npos); // New code ERROR_SYSTEM_AUTH
|
||||
|
||||
srs_freep(err);
|
||||
|
||||
// Test transform with NULL error
|
||||
err = srs_error_transform(ERROR_HTTP_STATUS_INVALID, srs_success, "transform null error");
|
||||
EXPECT_TRUE(err != srs_success);
|
||||
EXPECT_EQ(ERROR_HTTP_STATUS_INVALID, srs_error_code(err)); // Should have specified code
|
||||
|
||||
desc = srs_error_desc(err);
|
||||
EXPECT_TRUE(desc.find("transform null error") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("code transformed") != std::string::npos);
|
||||
|
||||
srs_freep(err);
|
||||
|
||||
// Test transform with formatted message
|
||||
srs_error_t inner = srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp format");
|
||||
err = srs_error_transform(ERROR_HTTP_STATUS_INVALID, inner, "http error: %s", "bad request");
|
||||
|
||||
EXPECT_TRUE(err != srs_success);
|
||||
EXPECT_EQ(ERROR_HTTP_STATUS_INVALID, srs_error_code(err));
|
||||
|
||||
desc = srs_error_desc(err);
|
||||
EXPECT_TRUE(desc.find("http error: bad request") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("invalid sdp format") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("code transformed") != std::string::npos);
|
||||
// Verify the code transformation shows both old and new codes
|
||||
std::string code_transform_msg = srs_fmt_sprintf("(%d => %d)", ERROR_RTC_SDP_DECODE, ERROR_HTTP_STATUS_INVALID);
|
||||
EXPECT_TRUE(desc.find(code_transform_msg) != std::string::npos);
|
||||
|
||||
srs_freep(err);
|
||||
|
||||
// Test chaining: wrap -> transform -> wrap
|
||||
srs_error_t level1 = srs_error_new(ERROR_SOCKET_READ, "socket read failed");
|
||||
srs_error_t level2 = srs_error_wrap(level1, "connection error");
|
||||
srs_error_t level3 = srs_error_transform(ERROR_SYSTEM_AUTH, level2, "auth check failed");
|
||||
srs_error_t level4 = srs_error_wrap(level3, "publish rejected");
|
||||
|
||||
EXPECT_TRUE(level4 != srs_success);
|
||||
EXPECT_EQ(ERROR_SYSTEM_AUTH, srs_error_code(level4)); // Should have transformed code
|
||||
|
||||
desc = srs_error_desc(level4);
|
||||
EXPECT_TRUE(desc.find("socket read failed") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("connection error") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("auth check failed") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("publish rejected") != std::string::npos);
|
||||
EXPECT_TRUE(desc.find("code transformed") != std::string::npos);
|
||||
|
||||
srs_freep(level4);
|
||||
}
|
||||
|
||||
VOID TEST(KernelErrorTest, ErrorDescriptionFormatting)
|
||||
{
|
||||
srs_error_t err;
|
||||
|
|
|
|||
|
|
@ -2254,7 +2254,7 @@ VOID TEST(SrsGoApiRtcWhipTest, ServeHttpDeleteSuccess)
|
|||
// Test SrsGoApiRtcWhip::serve_http() to verify the major use scenario for WHIP POST request.
|
||||
// This test covers the WHIP session creation flow (non-DELETE path):
|
||||
// 1. Client sends POST request with SDP offer in body
|
||||
// 2. Server processes the request via do_serve_http() which populates ruc.local_sdp_str_
|
||||
// 2. Server processes the request via do_serve_http_with() which populates ruc.local_sdp_str_
|
||||
// 3. Server returns 201 Created with SDP answer in body
|
||||
// 4. Server includes Location header for subsequent DELETE request
|
||||
// 5. Server sets Content-Type to application/sdp
|
||||
|
|
@ -2265,14 +2265,14 @@ VOID TEST(SrsGoApiRtcWhipTest, ServeHttpPostSuccess)
|
|||
// Create mock RTC API server
|
||||
SrsUniquePtr<MockRtcApiServer> mock_server(new MockRtcApiServer());
|
||||
|
||||
// Create testable WHIP handler that overrides do_serve_http
|
||||
// Create testable WHIP handler that overrides do_serve_http_with
|
||||
class TestableWhip : public SrsGoApiRtcWhip
|
||||
{
|
||||
public:
|
||||
TestableWhip(ISrsRtcApiServer *server) : SrsGoApiRtcWhip(server) {}
|
||||
virtual srs_error_t do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc)
|
||||
virtual srs_error_t do_serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc)
|
||||
{
|
||||
// Mock the do_serve_http behavior by populating the required fields
|
||||
// Mock the do_serve_http_with behavior by populating the required fields
|
||||
ruc->local_sdp_str_ = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=SRS\r\nt=0 0\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=rtpmap:96 H264/90000\r\n";
|
||||
ruc->session_id_ = "test-session-12345";
|
||||
ruc->token_ = "test-token-67890";
|
||||
|
|
@ -2304,7 +2304,7 @@ VOID TEST(SrsGoApiRtcWhipTest, ServeHttpPostSuccess)
|
|||
// Call serve_http for POST request
|
||||
// Expected behavior:
|
||||
// 1. Check if method is DELETE (no, it's POST)
|
||||
// 2. Call do_serve_http() which populates ruc.local_sdp_str_
|
||||
// 2. Call do_serve_http_with() which populates ruc.local_sdp_str_
|
||||
// 3. Set Content-Type to application/sdp
|
||||
// 4. Set Location header with session and token
|
||||
// 5. Return 201 Created with SDP answer in body
|
||||
|
|
@ -2330,8 +2330,8 @@ VOID TEST(SrsGoApiRtcWhipTest, ServeHttpPostSuccess)
|
|||
EXPECT_TRUE(response.find("m=video 9 UDP/TLS/RTP/SAVPF 96") != std::string::npos);
|
||||
}
|
||||
|
||||
// Test SrsGoApiRtcWhip::do_serve_http() - major use scenario for WHIP request parsing
|
||||
// This test covers the core parsing and validation logic of do_serve_http method:
|
||||
// Test SrsGoApiRtcWhip::do_serve_http_with() - major use scenario for WHIP request parsing
|
||||
// This test covers the core parsing and validation logic of do_serve_http_with method:
|
||||
// 1. Read SDP offer from request body
|
||||
// 2. Extract client IP from connection (with proxy IP override support)
|
||||
// 3. Parse query parameters (eip, codec, app, stream, action, ice-ufrag, ice-pwd, encrypt, dtls)
|
||||
|
|
@ -2437,7 +2437,7 @@ VOID TEST(SrsGoApiRtcWhipTest, DoServeHttpPublishSuccess)
|
|||
SrsUniquePtr<SrsRtcUserConfig> ruc(new SrsRtcUserConfig());
|
||||
|
||||
// Call do_serve_http - major use scenario
|
||||
HELPER_EXPECT_SUCCESS(whip->do_serve_http(mock_writer.get(), mock_request.get(), ruc.get()));
|
||||
HELPER_EXPECT_SUCCESS(whip->do_serve_http_with(mock_writer.get(), mock_request.get(), ruc.get()));
|
||||
|
||||
// Verify request fields were populated correctly
|
||||
EXPECT_STREQ("192.168.1.100", ruc->req_->ip_.c_str());
|
||||
|
|
@ -2466,6 +2466,186 @@ VOID TEST(SrsGoApiRtcWhipTest, DoServeHttpPublishSuccess)
|
|||
whip->config_ = NULL;
|
||||
}
|
||||
|
||||
// Test SrsGoApiRtcWhip::serve_http() error handling for invalid SDP.
|
||||
// This test verifies that WHIP returns HTTP 400 Bad Request when SDP parsing fails.
|
||||
VOID TEST(SrsGoApiRtcWhipTest, ServeHttpErrorInvalidSdp)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// Create mock RTC API server
|
||||
SrsUniquePtr<MockRtcApiServer> mock_server(new MockRtcApiServer());
|
||||
|
||||
// Create testable WHIP handler that simulates SDP parsing error
|
||||
class TestableWhip : public SrsGoApiRtcWhip
|
||||
{
|
||||
public:
|
||||
TestableWhip(ISrsRtcApiServer *server) : SrsGoApiRtcWhip(server) {}
|
||||
virtual srs_error_t serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
|
||||
{
|
||||
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp format");
|
||||
}
|
||||
};
|
||||
|
||||
// Create testable WHIP instance
|
||||
SrsUniquePtr<TestableWhip> whip(new TestableWhip(mock_server.get()));
|
||||
|
||||
// Create mock response writer
|
||||
SrsUniquePtr<MockResponseWriter> mock_writer(new MockResponseWriter());
|
||||
|
||||
// Create mock HTTP message for WHIP POST request
|
||||
SrsUniquePtr<MockHttpMessageForRtcApi> mock_request(new MockHttpMessageForRtcApi());
|
||||
mock_request->set_method(SRS_CONSTS_HTTP_POST);
|
||||
|
||||
// Call serve_http - should return HTTP 400 Bad Request
|
||||
HELPER_EXPECT_SUCCESS(whip->serve_http(mock_writer.get(), mock_request.get()));
|
||||
|
||||
// Verify response status is 400 Bad Request
|
||||
EXPECT_EQ(SRS_CONSTS_HTTP_BadRequest, mock_writer->w->status_);
|
||||
|
||||
// Get the HTTP response
|
||||
string response = string(mock_writer->io.out_buffer.bytes(), mock_writer->io.out_buffer.length());
|
||||
EXPECT_FALSE(response.empty());
|
||||
|
||||
// Verify the response contains error code and description
|
||||
EXPECT_TRUE(response.find("5012") != std::string::npos); // ERROR_RTC_SDP_DECODE
|
||||
EXPECT_TRUE(response.find("RtcSdpDecode") != std::string::npos);
|
||||
}
|
||||
|
||||
// Test SrsGoApiRtcWhip::serve_http() error handling for stream busy.
|
||||
// This test verifies that WHIP returns HTTP 409 Conflict when stream is already publishing.
|
||||
VOID TEST(SrsGoApiRtcWhipTest, ServeHttpErrorStreamBusy)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// Create mock RTC API server
|
||||
SrsUniquePtr<MockRtcApiServer> mock_server(new MockRtcApiServer());
|
||||
|
||||
// Create testable WHIP handler that simulates stream busy error
|
||||
class TestableWhip : public SrsGoApiRtcWhip
|
||||
{
|
||||
public:
|
||||
TestableWhip(ISrsRtcApiServer *server) : SrsGoApiRtcWhip(server) {}
|
||||
virtual srs_error_t serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
|
||||
{
|
||||
return srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "stream already publishing");
|
||||
}
|
||||
};
|
||||
|
||||
// Create testable WHIP instance
|
||||
SrsUniquePtr<TestableWhip> whip(new TestableWhip(mock_server.get()));
|
||||
|
||||
// Create mock response writer
|
||||
SrsUniquePtr<MockResponseWriter> mock_writer(new MockResponseWriter());
|
||||
|
||||
// Create mock HTTP message for WHIP POST request
|
||||
SrsUniquePtr<MockHttpMessageForRtcApi> mock_request(new MockHttpMessageForRtcApi());
|
||||
mock_request->set_method(SRS_CONSTS_HTTP_POST);
|
||||
|
||||
// Call serve_http - should return HTTP 409 Conflict
|
||||
HELPER_EXPECT_SUCCESS(whip->serve_http(mock_writer.get(), mock_request.get()));
|
||||
|
||||
// Verify response status is 409 Conflict
|
||||
EXPECT_EQ(SRS_CONSTS_HTTP_Conflict, mock_writer->w->status_);
|
||||
|
||||
// Get the HTTP response
|
||||
string response = string(mock_writer->io.out_buffer.bytes(), mock_writer->io.out_buffer.length());
|
||||
EXPECT_FALSE(response.empty());
|
||||
|
||||
// Verify the response contains error code and description
|
||||
EXPECT_TRUE(response.find("1028") != std::string::npos); // ERROR_SYSTEM_STREAM_BUSY
|
||||
EXPECT_TRUE(response.find("StreamBusy") != std::string::npos);
|
||||
}
|
||||
|
||||
// Test SrsGoApiRtcWhip::serve_http() error handling for authentication failure.
|
||||
// This test verifies that WHIP returns HTTP 401 Unauthorized when auth check fails.
|
||||
VOID TEST(SrsGoApiRtcWhipTest, ServeHttpErrorAuth)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// Create mock RTC API server
|
||||
SrsUniquePtr<MockRtcApiServer> mock_server(new MockRtcApiServer());
|
||||
|
||||
// Create testable WHIP handler that simulates auth error
|
||||
class TestableWhip : public SrsGoApiRtcWhip
|
||||
{
|
||||
public:
|
||||
TestableWhip(ISrsRtcApiServer *server) : SrsGoApiRtcWhip(server) {}
|
||||
virtual srs_error_t serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
|
||||
{
|
||||
return srs_error_new(ERROR_SYSTEM_AUTH, "authentication failed");
|
||||
}
|
||||
};
|
||||
|
||||
// Create testable WHIP instance
|
||||
SrsUniquePtr<TestableWhip> whip(new TestableWhip(mock_server.get()));
|
||||
|
||||
// Create mock response writer
|
||||
SrsUniquePtr<MockResponseWriter> mock_writer(new MockResponseWriter());
|
||||
|
||||
// Create mock HTTP message for WHIP POST request
|
||||
SrsUniquePtr<MockHttpMessageForRtcApi> mock_request(new MockHttpMessageForRtcApi());
|
||||
mock_request->set_method(SRS_CONSTS_HTTP_POST);
|
||||
|
||||
// Call serve_http - should return HTTP 401 Unauthorized
|
||||
HELPER_EXPECT_SUCCESS(whip->serve_http(mock_writer.get(), mock_request.get()));
|
||||
|
||||
// Verify response status is 401 Unauthorized
|
||||
EXPECT_EQ(SRS_CONSTS_HTTP_Unauthorized, mock_writer->w->status_);
|
||||
|
||||
// Get the HTTP response
|
||||
string response = string(mock_writer->io.out_buffer.bytes(), mock_writer->io.out_buffer.length());
|
||||
EXPECT_FALSE(response.empty());
|
||||
|
||||
// Verify the response contains error code and description
|
||||
EXPECT_TRUE(response.find("1102") != std::string::npos); // ERROR_SYSTEM_AUTH
|
||||
EXPECT_TRUE(response.find("SystemAuth") != std::string::npos);
|
||||
}
|
||||
|
||||
// Test SrsGoApiRtcWhip::serve_http() error handling for internal server error.
|
||||
// This test verifies that WHIP returns HTTP 500 for unexpected errors.
|
||||
VOID TEST(SrsGoApiRtcWhipTest, ServeHttpErrorInternal)
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// Create mock RTC API server
|
||||
SrsUniquePtr<MockRtcApiServer> mock_server(new MockRtcApiServer());
|
||||
|
||||
// Create testable WHIP handler that simulates internal error
|
||||
class TestableWhip : public SrsGoApiRtcWhip
|
||||
{
|
||||
public:
|
||||
TestableWhip(ISrsRtcApiServer *server) : SrsGoApiRtcWhip(server) {}
|
||||
virtual srs_error_t serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
|
||||
{
|
||||
return srs_error_new(ERROR_SOCKET_TIMEOUT, "socket timeout");
|
||||
}
|
||||
};
|
||||
|
||||
// Create testable WHIP instance
|
||||
SrsUniquePtr<TestableWhip> whip(new TestableWhip(mock_server.get()));
|
||||
|
||||
// Create mock response writer
|
||||
SrsUniquePtr<MockResponseWriter> mock_writer(new MockResponseWriter());
|
||||
|
||||
// Create mock HTTP message for WHIP POST request
|
||||
SrsUniquePtr<MockHttpMessageForRtcApi> mock_request(new MockHttpMessageForRtcApi());
|
||||
mock_request->set_method(SRS_CONSTS_HTTP_POST);
|
||||
|
||||
// Call serve_http - should return HTTP 500 Internal Server Error
|
||||
HELPER_EXPECT_SUCCESS(whip->serve_http(mock_writer.get(), mock_request.get()));
|
||||
|
||||
// Verify response status is 500 Internal Server Error
|
||||
EXPECT_EQ(SRS_CONSTS_HTTP_InternalServerError, mock_writer->w->status_);
|
||||
|
||||
// Get the HTTP response
|
||||
string response = string(mock_writer->io.out_buffer.bytes(), mock_writer->io.out_buffer.length());
|
||||
EXPECT_FALSE(response.empty());
|
||||
|
||||
// Verify the response contains error code and description
|
||||
EXPECT_TRUE(response.find("1011") != std::string::npos); // ERROR_SOCKET_TIMEOUT
|
||||
EXPECT_TRUE(response.find("SocketTimeout") != std::string::npos);
|
||||
}
|
||||
|
||||
VOID TEST(RtcApiNackTest, ServeHttpSuccess)
|
||||
{
|
||||
// This test covers the major use scenario for SrsGoApiRtcNACK::serve_http():
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user