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>
504 lines
13 KiB
C++
504 lines
13 KiB
C++
//
|
|
// Copyright (c) 2013-2025 The SRS Authors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
|
|
#include <srs_kernel_error.hpp>
|
|
|
|
#include <srs_kernel_log.hpp>
|
|
#include <srs_kernel_utility.hpp>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <sstream>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
using namespace std;
|
|
|
|
const int maxLogBuf = 4 * 1024 * 1024;
|
|
|
|
#if defined(SRS_BACKTRACE) && defined(__linux)
|
|
#include <dlfcn.h>
|
|
#include <execinfo.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
void *parse_symbol_offset(char *frame)
|
|
{
|
|
char *p = NULL;
|
|
char *p_symbol = NULL;
|
|
int nn_symbol = 0;
|
|
char *p_offset = NULL;
|
|
int nn_offset = 0;
|
|
|
|
// Read symbol and offset, for example:
|
|
// /tools/backtrace(foo+0x1820) [0x555555555820]
|
|
for (p = frame; *p; p++) {
|
|
if (*p == '(') {
|
|
p_symbol = p + 1;
|
|
} else if (*p == '+') {
|
|
if (p_symbol)
|
|
nn_symbol = p - p_symbol;
|
|
p_offset = p + 1;
|
|
} else if (*p == ')') {
|
|
if (p_offset)
|
|
nn_offset = p - p_offset;
|
|
}
|
|
}
|
|
if (!nn_symbol && !nn_offset) {
|
|
return NULL;
|
|
}
|
|
|
|
// Convert offset(0x1820) to pointer, such as 0x1820.
|
|
char tmp[128];
|
|
if (!nn_offset || nn_offset >= (int)sizeof(tmp)) {
|
|
return NULL;
|
|
}
|
|
|
|
int r0 = EOF;
|
|
void *offset = NULL;
|
|
tmp[nn_offset] = 0;
|
|
if ((r0 = sscanf(strncpy(tmp, p_offset, nn_offset), "%p", &offset)) == EOF) {
|
|
return NULL;
|
|
}
|
|
|
|
// Covert symbol(foo) to offset, such as 0x2fba.
|
|
if (!nn_symbol || nn_symbol >= (int)sizeof(tmp)) {
|
|
return offset;
|
|
}
|
|
|
|
void *object_file;
|
|
if ((object_file = dlopen(NULL, RTLD_LAZY)) == NULL) {
|
|
return offset;
|
|
}
|
|
|
|
void *address;
|
|
tmp[nn_symbol] = 0;
|
|
if ((address = dlsym(object_file, strncpy(tmp, p_symbol, nn_symbol))) == NULL) {
|
|
dlclose(object_file);
|
|
return offset;
|
|
}
|
|
|
|
Dl_info symbol_info;
|
|
if ((r0 = dladdr(address, &symbol_info)) == 0) {
|
|
dlclose(object_file);
|
|
return offset;
|
|
}
|
|
|
|
dlclose(object_file);
|
|
return (char *)symbol_info.dli_saddr - (char *)symbol_info.dli_fbase + (char *)offset;
|
|
}
|
|
|
|
extern const char *_srs_binary;
|
|
|
|
char *addr2line_format(void *addr, char *symbol, char *buffer, int nn_buffer)
|
|
{
|
|
char cmd[512] = {0};
|
|
int r0 = snprintf(cmd, sizeof(cmd), "addr2line -C -p -s -f -a -e %s %p", _srs_binary, (void *)((char *)addr - 1));
|
|
if (r0 < 0 || r0 >= (int)sizeof(cmd))
|
|
return symbol;
|
|
|
|
FILE *fp = popen(cmd, "r");
|
|
if (!fp)
|
|
return symbol;
|
|
|
|
char *p = fgets(buffer, nn_buffer, fp);
|
|
pclose(fp);
|
|
|
|
if (p == NULL)
|
|
return symbol;
|
|
if ((r0 = strlen(p)) == 0)
|
|
return symbol;
|
|
|
|
// Trait the last newline if exists.
|
|
if (p[r0 - 1] == '\n')
|
|
p[r0 - 1] = '\0';
|
|
|
|
// Find symbol not match by addr2line, like
|
|
// 0x0000000000021c87: ?? ??:0
|
|
// 0x0000000000002ffa: _start at ??:?
|
|
for (p = buffer; p < buffer + r0 - 1; p++) {
|
|
if (p[0] == '?' && p[1] == '?')
|
|
return symbol;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
#endif
|
|
|
|
int srs_parse_asan_backtrace_symbols(char *symbol, char *out_buf)
|
|
{
|
|
#if defined(SRS_BACKTRACE) && defined(__linux)
|
|
void *frame = parse_symbol_offset(symbol);
|
|
if (!frame) {
|
|
return ERROR_BACKTRACE_PARSE_OFFSET;
|
|
}
|
|
|
|
char *fmt = addr2line_format(frame, symbol, out_buf, sizeof(out_buf));
|
|
if (fmt != out_buf) {
|
|
return ERROR_BACKTRACE_ADDR2LINE;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
#endif
|
|
return ERROR_BACKTRACE_PARSE_NOT_SUPPORT;
|
|
}
|
|
|
|
#ifdef SRS_SANITIZER_LOG
|
|
void asan_report_callback(const char *str)
|
|
{
|
|
static char buf[256];
|
|
|
|
// No error code for assert failed.
|
|
errno = 0;
|
|
|
|
std::vector<std::string> asan_logs = srs_strings_split(string(str), "\n");
|
|
size_t log_count = asan_logs.size();
|
|
for (size_t i = 0; i < log_count; i++) {
|
|
std::string log = asan_logs[i];
|
|
|
|
if (!srs_strings_starts_with(srs_strings_trim_start(log, " "), "#")) {
|
|
srs_error("%s", log.c_str());
|
|
continue;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
int r0 = srs_parse_asan_backtrace_symbols((char *)log.c_str(), buf);
|
|
if (r0 != ERROR_SUCCESS) {
|
|
srs_error("%s, r0=%d", log.c_str(), r0);
|
|
} else {
|
|
srs_error("%s, %s", log.c_str(), buf);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SRS_SANITIZER
|
|
// This function return the default options for asan, before main() is called,
|
|
// see https://github.com/google/sanitizers/wiki/AddressSanitizerFlags#run-time-flags
|
|
//
|
|
// Disable halt on errors by halt_on_error, only print messages, note that it still quit for fatal errors,
|
|
// see https://github.com/google/sanitizers/wiki/AddressSanitizerFlags
|
|
//
|
|
// Disable the memory leaking detect for daemon by detect_leaks,
|
|
// see https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
|
|
//
|
|
// Also disable alloc_dealloc_mismatch for gdb.
|
|
extern "C" const char *__asan_default_options()
|
|
{
|
|
return "halt_on_error=0:detect_leaks=0:alloc_dealloc_mismatch=0";
|
|
}
|
|
#endif
|
|
|
|
bool srs_is_system_control_error(srs_error_t err)
|
|
{
|
|
int error_code = srs_error_code(err);
|
|
return error_code == ERROR_CONTROL_RTMP_CLOSE || error_code == ERROR_CONTROL_REPUBLISH || error_code == ERROR_CONTROL_REDIRECT;
|
|
}
|
|
|
|
bool srs_is_client_gracefully_close(srs_error_t err)
|
|
{
|
|
int error_code = srs_error_code(err);
|
|
return error_code == ERROR_SOCKET_READ || error_code == ERROR_SOCKET_READ_FULLY || error_code == ERROR_SOCKET_WRITE // For RTMP
|
|
|| error_code == ERROR_SRT_IO // For SRT
|
|
;
|
|
}
|
|
|
|
bool srs_is_server_gracefully_close(srs_error_t err)
|
|
{
|
|
int code = srs_error_code(err);
|
|
return code == ERROR_HTTP_STREAM_EOF;
|
|
}
|
|
|
|
SrsCplxError::SrsCplxError()
|
|
{
|
|
code_ = ERROR_SUCCESS;
|
|
wrapped_ = NULL;
|
|
rerrno_ = line_ = 0;
|
|
}
|
|
|
|
SrsCplxError::~SrsCplxError()
|
|
{
|
|
srs_freep(wrapped_);
|
|
}
|
|
|
|
std::string SrsCplxError::description()
|
|
{
|
|
if (desc_.empty()) {
|
|
stringstream ss;
|
|
ss << "code=" << code_;
|
|
|
|
string code_str = srs_error_code_str(this);
|
|
if (!code_str.empty())
|
|
ss << "(" << code_str << ")";
|
|
|
|
string code_longstr = srs_error_code_strlong(this);
|
|
if (!code_longstr.empty())
|
|
ss << "(" << code_longstr << ")";
|
|
|
|
SrsCplxError *next = this;
|
|
while (next) {
|
|
ss << " : " << next->msg_;
|
|
next = next->wrapped_;
|
|
}
|
|
ss << endl;
|
|
|
|
next = this;
|
|
while (next) {
|
|
ss << "thread [" << getpid() << "][" << next->cid_.c_str() << "]: "
|
|
<< next->func_ << "() [" << next->file_ << ":" << next->line_ << "]"
|
|
<< "[errno=" << next->rerrno_ << "]";
|
|
|
|
next = next->wrapped_;
|
|
|
|
if (next) {
|
|
ss << endl;
|
|
}
|
|
}
|
|
|
|
desc_ = ss.str();
|
|
}
|
|
|
|
return desc_;
|
|
}
|
|
|
|
std::string SrsCplxError::summary()
|
|
{
|
|
if (summary_.empty()) {
|
|
stringstream ss;
|
|
|
|
ss << "code=" << code_;
|
|
|
|
string code_str = srs_error_code_str(this);
|
|
if (!code_str.empty())
|
|
ss << "(" << code_str << ")";
|
|
|
|
SrsCplxError *next = this;
|
|
while (next) {
|
|
ss << " : " << next->msg_;
|
|
next = next->wrapped_;
|
|
}
|
|
|
|
summary_ = ss.str();
|
|
}
|
|
|
|
return summary_;
|
|
}
|
|
|
|
SrsCplxError *SrsCplxError::create(const char *func, const char *file, int line, int code, const char *fmt, ...)
|
|
{
|
|
int rerrno = (int)errno;
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
static char *buffer = new char[maxLogBuf];
|
|
int r0 = vsnprintf(buffer, maxLogBuf, fmt, ap);
|
|
va_end(ap);
|
|
|
|
SrsCplxError *err = new SrsCplxError();
|
|
|
|
err->func_ = func;
|
|
err->file_ = file;
|
|
err->line_ = line;
|
|
err->code_ = code;
|
|
err->rerrno_ = rerrno;
|
|
if (r0 > 0 && r0 < maxLogBuf) {
|
|
err->msg_ = string(buffer, r0);
|
|
}
|
|
err->wrapped_ = NULL;
|
|
if (_srs_context) {
|
|
err->cid_ = _srs_context->get_id();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
SrsCplxError *SrsCplxError::wrap(const char *func, const char *file, int line, SrsCplxError *v, const char *fmt, ...)
|
|
{
|
|
int rerrno = (int)errno;
|
|
|
|
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';
|
|
}
|
|
|
|
SrsCplxError *err = new SrsCplxError();
|
|
|
|
err->func_ = func;
|
|
err->file_ = file;
|
|
err->line_ = line;
|
|
err->code_ = srs_error_code(v);
|
|
err->rerrno_ = rerrno;
|
|
err->msg_ = string(buffer);
|
|
err->wrapped_ = v;
|
|
if (_srs_context) {
|
|
err->cid_ = _srs_context->get_id();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
SrsCplxError *SrsCplxError::copy(SrsCplxError *from)
|
|
{
|
|
if (from == srs_success) {
|
|
return srs_success;
|
|
}
|
|
|
|
SrsCplxError *err = new SrsCplxError();
|
|
|
|
err->code_ = from->code_;
|
|
err->wrapped_ = srs_error_copy(from->wrapped_);
|
|
err->msg_ = from->msg_;
|
|
err->func_ = from->func_;
|
|
err->file_ = from->file_;
|
|
err->line_ = from->line_;
|
|
err->cid_ = from->cid_;
|
|
err->rerrno_ = from->rerrno_;
|
|
err->desc_ = from->desc_;
|
|
|
|
return err;
|
|
}
|
|
|
|
string SrsCplxError::description(SrsCplxError *err)
|
|
{
|
|
return err ? err->description() : "Success";
|
|
}
|
|
|
|
string SrsCplxError::summary(SrsCplxError *err)
|
|
{
|
|
return err ? err->summary() : "Success";
|
|
}
|
|
|
|
int SrsCplxError::error_code(SrsCplxError *err)
|
|
{
|
|
return err ? err->code_ : ERROR_SUCCESS;
|
|
}
|
|
|
|
#define SRS_STRERRNO_GEN(n, v, m, s) {(SrsErrorCode)v, m, s},
|
|
static struct
|
|
{
|
|
SrsErrorCode code;
|
|
const char *name;
|
|
const char *descripton;
|
|
} _srs_strerror_tab[] = {
|
|
#ifndef _WIN32
|
|
{ERROR_SUCCESS, "Success", "Success"},
|
|
#endif
|
|
SRS_ERRNO_MAP_SYSTEM(SRS_STRERRNO_GEN)
|
|
SRS_ERRNO_MAP_RTMP(SRS_STRERRNO_GEN)
|
|
SRS_ERRNO_MAP_APP(SRS_STRERRNO_GEN)
|
|
SRS_ERRNO_MAP_HTTP(SRS_STRERRNO_GEN)
|
|
SRS_ERRNO_MAP_RTC(SRS_STRERRNO_GEN)
|
|
SRS_ERRNO_MAP_SRT(SRS_STRERRNO_GEN)
|
|
SRS_ERRNO_MAP_USER(SRS_STRERRNO_GEN)};
|
|
#undef SRS_STRERRNO_GEN
|
|
|
|
std::string SrsCplxError::error_code_str(SrsCplxError *err)
|
|
{
|
|
static string not_found = "";
|
|
static std::map<SrsErrorCode, string> error_map;
|
|
|
|
// Build map if empty.
|
|
if (error_map.empty()) {
|
|
for (int i = 0; i < (int)(sizeof(_srs_strerror_tab) / sizeof(_srs_strerror_tab[0])); i++) {
|
|
SrsErrorCode code = _srs_strerror_tab[i].code;
|
|
error_map[code] = _srs_strerror_tab[i].name;
|
|
}
|
|
}
|
|
|
|
std::map<SrsErrorCode, string>::iterator it = error_map.find((SrsErrorCode)srs_error_code(err));
|
|
if (it == error_map.end()) {
|
|
return not_found;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
std::string SrsCplxError::error_code_strlong(SrsCplxError *err)
|
|
{
|
|
static string not_found = "";
|
|
static std::map<SrsErrorCode, string> error_map;
|
|
|
|
// Build map if empty.
|
|
if (error_map.empty()) {
|
|
for (int i = 0; i < (int)(sizeof(_srs_strerror_tab) / sizeof(_srs_strerror_tab[0])); i++) {
|
|
SrsErrorCode code = _srs_strerror_tab[i].code;
|
|
error_map[code] = _srs_strerror_tab[i].descripton;
|
|
}
|
|
}
|
|
|
|
std::map<SrsErrorCode, string>::iterator it = error_map.find((SrsErrorCode)srs_error_code(err));
|
|
if (it == error_map.end()) {
|
|
return not_found;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
void SrsCplxError::srs_assert(bool expression)
|
|
{
|
|
#if defined(SRS_BACKTRACE) && defined(__linux)
|
|
if (!expression) {
|
|
void *addresses[64];
|
|
int nn_addresses = backtrace(addresses, sizeof(addresses) / sizeof(void *));
|
|
char **symbols = backtrace_symbols(addresses, nn_addresses);
|
|
|
|
// No error code for assert failed.
|
|
errno = 0;
|
|
|
|
char buffer[128];
|
|
srs_error("backtrace %d frames of %s %s", nn_addresses, _srs_binary, RTMP_SIG_SRS_SERVER);
|
|
for (int i = 0; i < nn_addresses; i++) {
|
|
void *frame = parse_symbol_offset(symbols[i]);
|
|
char *fmt = addr2line_format(frame, symbols[i], buffer, sizeof(buffer));
|
|
int parsed = (fmt == buffer);
|
|
srs_error("#%d %p %d %s", i, frame, parsed, fmt);
|
|
}
|
|
|
|
free(symbols);
|
|
}
|
|
#endif
|
|
|
|
assert(expression);
|
|
}
|