From 1509fde2da439627feda7f2db2d1f7e32a41b83f Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sun, 5 Oct 2025 22:14:45 -0400 Subject: [PATCH] AI: Add utest to cover api module. --- trunk/src/app/srs_app_config.hpp | 12 + trunk/src/app/srs_app_http_api.cpp | 226 ++- trunk/src/app/srs_app_http_api.hpp | 77 +- trunk/src/app/srs_app_server.cpp | 18 +- trunk/src/app/srs_app_server.hpp | 14 +- trunk/src/app/srs_app_statistic.hpp | 23 + trunk/src/kernel/srs_kernel_packet.cpp | 8 +- trunk/src/kernel/srs_kernel_packet.hpp | 16 +- trunk/src/kernel/srs_kernel_ts.cpp | 12 +- trunk/src/kernel/srs_kernel_ts.hpp | 16 +- .../src/protocol/srs_protocol_http_client.hpp | 2 +- trunk/src/utest/srs_utest_app10.cpp | 56 + trunk/src/utest/srs_utest_app10.hpp | 10 + trunk/src/utest/srs_utest_app11.cpp | 1689 +++++++++++++++++ trunk/src/utest/srs_utest_app11.hpp | 69 + trunk/src/utest/srs_utest_app6.cpp | 56 + trunk/src/utest/srs_utest_app6.hpp | 19 +- trunk/src/utest/srs_utest_app9.cpp | 56 + trunk/src/utest/srs_utest_app9.hpp | 10 + trunk/src/utest/srs_utest_http.cpp | 50 + trunk/src/utest/srs_utest_http.hpp | 22 + 21 files changed, 2329 insertions(+), 132 deletions(-) diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 02cda5d46..7c9517879 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -315,6 +315,16 @@ public: virtual std::vector get_https_api_listens() = 0; virtual std::string get_https_api_ssl_key() = 0; virtual std::string get_https_api_ssl_cert() = 0; + // Whether enable the HTTP RAW API. + virtual bool get_raw_api() = 0; + // Whether allow rpc reload. + virtual bool get_raw_api_allow_reload() = 0; + // Whether allow rpc query. + virtual bool get_raw_api_allow_query() = 0; + // Whether allow rpc update. + virtual bool get_raw_api_allow_update() = 0; + // Dumps the http_api sections to json for raw api info. + virtual srs_error_t raw_to_json(SrsJsonObject *obj) = 0; public: // HTTP Server config @@ -354,6 +364,8 @@ public: // Exporter config virtual bool get_exporter_enabled() = 0; virtual std::string get_exporter_listen() = 0; + virtual std::string get_exporter_label() = 0; + virtual std::string get_exporter_tag() = 0; public: // Stats config diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 5a8190036..79c35fac1 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -170,22 +170,22 @@ srs_error_t srs_api_response_code(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsGoApiRoot::SrsGoApiRoot() { + stat_ = _srs_stat; } SrsGoApiRoot::~SrsGoApiRoot() { + stat_ = NULL; } srs_error_t SrsGoApiRoot::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *urls = SrsJsonAny::object(); obj->set("urls", urls); @@ -209,22 +209,22 @@ srs_error_t SrsGoApiRoot::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage SrsGoApiApi::SrsGoApiApi() { + stat_ = _srs_stat; } SrsGoApiApi::~SrsGoApiApi() { + stat_ = NULL; } srs_error_t SrsGoApiApi::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *urls = SrsJsonAny::object(); obj->set("urls", urls); @@ -236,22 +236,22 @@ srs_error_t SrsGoApiApi::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage * SrsGoApiV1::SrsGoApiV1() { + stat_ = _srs_stat; } SrsGoApiV1::~SrsGoApiV1() { + stat_ = NULL; } srs_error_t SrsGoApiV1::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *urls = SrsJsonAny::object(); obj->set("urls", urls); @@ -292,22 +292,22 @@ srs_error_t SrsGoApiV1::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r SrsGoApiVersion::SrsGoApiVersion() { + stat_ = _srs_stat; } SrsGoApiVersion::~SrsGoApiVersion() { + stat_ = NULL; } srs_error_t SrsGoApiVersion::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -322,22 +322,22 @@ srs_error_t SrsGoApiVersion::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa SrsGoApiSummaries::SrsGoApiSummaries() { + stat_ = _srs_stat; } SrsGoApiSummaries::~SrsGoApiSummaries() { + stat_ = NULL; } srs_error_t SrsGoApiSummaries::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); srs_api_dump_summaries(obj.get()); @@ -346,22 +346,22 @@ srs_error_t SrsGoApiSummaries::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMes SrsGoApiRusages::SrsGoApiRusages() { + stat_ = _srs_stat; } SrsGoApiRusages::~SrsGoApiRusages() { + stat_ = NULL; } srs_error_t SrsGoApiRusages::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -392,22 +392,22 @@ srs_error_t SrsGoApiRusages::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa SrsGoApiSelfProcStats::SrsGoApiSelfProcStats() { + stat_ = _srs_stat; } SrsGoApiSelfProcStats::~SrsGoApiSelfProcStats() { + stat_ = NULL; } srs_error_t SrsGoApiSelfProcStats::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -470,22 +470,22 @@ srs_error_t SrsGoApiSelfProcStats::serve_http(ISrsHttpResponseWriter *w, ISrsHtt SrsGoApiSystemProcStats::SrsGoApiSystemProcStats() { + stat_ = _srs_stat; } SrsGoApiSystemProcStats::~SrsGoApiSystemProcStats() { + stat_ = NULL; } srs_error_t SrsGoApiSystemProcStats::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -510,22 +510,22 @@ srs_error_t SrsGoApiSystemProcStats::serve_http(ISrsHttpResponseWriter *w, ISrsH SrsGoApiMemInfos::SrsGoApiMemInfos() { + stat_ = _srs_stat; } SrsGoApiMemInfos::~SrsGoApiMemInfos() { + stat_ = NULL; } srs_error_t SrsGoApiMemInfos::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -551,22 +551,22 @@ srs_error_t SrsGoApiMemInfos::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMess SrsGoApiAuthors::SrsGoApiAuthors() { + stat_ = _srs_stat; } SrsGoApiAuthors::~SrsGoApiAuthors() { + stat_ = NULL; } srs_error_t SrsGoApiAuthors::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -579,22 +579,22 @@ srs_error_t SrsGoApiAuthors::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa SrsGoApiFeatures::SrsGoApiFeatures() { + stat_ = _srs_stat; } SrsGoApiFeatures::~SrsGoApiFeatures() { + stat_ = NULL; } srs_error_t SrsGoApiFeatures::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -648,22 +648,22 @@ srs_error_t SrsGoApiFeatures::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMess SrsGoApiRequests::SrsGoApiRequests() { + stat_ = _srs_stat; } SrsGoApiRequests::~SrsGoApiRequests() { + stat_ = NULL; } srs_error_t SrsGoApiRequests::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { - SrsStatistic *stat = _srs_stat; - SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); SrsJsonObject *data = SrsJsonAny::object(); obj->set("data", data); @@ -693,40 +693,40 @@ srs_error_t SrsGoApiRequests::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMess SrsGoApiVhosts::SrsGoApiVhosts() { + stat_ = _srs_stat; } SrsGoApiVhosts::~SrsGoApiVhosts() { + stat_ = NULL; } srs_error_t SrsGoApiVhosts::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { srs_error_t err = srs_success; - SrsStatistic *stat = _srs_stat; - // path: {pattern}{vhost_id} // e.g. /api/v1/vhosts/100 pattern= /api/v1/vhosts/, vhost_id=100 string vid = r->parse_rest_id(entry_->pattern); SrsStatisticVhost *vhost = NULL; - if (!vid.empty() && (vhost = stat->find_vhost_by_id(vid)) == NULL) { + if (!vid.empty() && (vhost = stat_->find_vhost_by_id(vid)) == NULL) { return srs_api_response_code(w, r, ERROR_RTMP_VHOST_NOT_FOUND); } SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); if (r->is_http_get()) { if (!vhost) { SrsJsonArray *data = SrsJsonAny::array(); obj->set("vhosts", data); - if ((err = stat->dumps_vhosts(data)) != srs_success) { + if ((err = stat_->dumps_vhosts(data)) != srs_success) { int code = srs_error_code(err); srs_freep(err); return srs_api_response_code(w, r, code); @@ -751,33 +751,33 @@ srs_error_t SrsGoApiVhosts::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessag SrsGoApiStreams::SrsGoApiStreams() { + stat_ = _srs_stat; } SrsGoApiStreams::~SrsGoApiStreams() { + stat_ = NULL; } srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { srs_error_t err = srs_success; - SrsStatistic *stat = _srs_stat; - // path: {pattern}{stream_id} // e.g. /api/v1/streams/100 pattern= /api/v1/streams/, stream_id=100 string sid = r->parse_rest_id(entry_->pattern); SrsStatisticStream *stream = NULL; - if (!sid.empty() && (stream = stat->find_stream(sid)) == NULL) { + if (!sid.empty() && (stream = stat_->find_stream(sid)) == NULL) { return srs_api_response_code(w, r, ERROR_RTMP_STREAM_NOT_FOUND); } SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); if (r->is_http_get()) { if (!stream) { @@ -788,7 +788,7 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa std::string rcount = r->query_get("count"); int start = srs_max(0, atoi(rstart.c_str())); int count = srs_max(10, atoi(rcount.c_str())); - if ((err = stat->dumps_streams(data, start, count)) != srs_success) { + if ((err = stat_->dumps_streams(data, start, count)) != srs_success) { int code = srs_error_code(err); srs_freep(err); return srs_api_response_code(w, r, code); @@ -813,33 +813,33 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa SrsGoApiClients::SrsGoApiClients() { + stat_ = _srs_stat; } SrsGoApiClients::~SrsGoApiClients() { + stat_ = NULL; } srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { srs_error_t err = srs_success; - SrsStatistic *stat = _srs_stat; - // path: {pattern}{client_id} // e.g. /api/v1/clients/100 pattern= /api/v1/clients/, client_id=100 string client_id = r->parse_rest_id(entry_->pattern); SrsStatisticClient *client = NULL; - if (!client_id.empty() && (client = stat->find_client(client_id)) == NULL) { + if (!client_id.empty() && (client = stat_->find_client(client_id)) == NULL) { return srs_api_response_code(w, r, ERROR_RTMP_CLIENT_NOT_FOUND); } SrsUniquePtr obj(SrsJsonAny::object()); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); - obj->set("service", SrsJsonAny::str(stat->service_id().c_str())); - obj->set("pid", SrsJsonAny::str(stat->service_pid().c_str())); + obj->set("server", SrsJsonAny::str(stat_->server_id().c_str())); + obj->set("service", SrsJsonAny::str(stat_->service_id().c_str())); + obj->set("pid", SrsJsonAny::str(stat_->service_pid().c_str())); if (r->is_http_get()) { if (!client) { @@ -850,7 +850,7 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa std::string rcount = r->query_get("count"); int start = srs_max(0, atoi(rstart.c_str())); int count = srs_max(10, atoi(rcount.c_str())); - if ((err = stat->dumps_clients(data, start, count)) != srs_success) { + if ((err = stat_->dumps_clients(data, start, count)) != srs_success) { int code = srs_error_code(err); srs_freep(err); return srs_api_response_code(w, r, code); @@ -884,21 +884,30 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa return srs_api_response(w, r, obj->dumps()); } -SrsGoApiRaw::SrsGoApiRaw(SrsServer *svr) +SrsGoApiRaw::SrsGoApiRaw(ISrsSignalHandler *handler) { - server_ = svr; + handler_ = handler; - raw_api_ = _srs_config->get_raw_api(); - allow_reload_ = _srs_config->get_raw_api_allow_reload(); - allow_query_ = _srs_config->get_raw_api_allow_query(); - allow_update_ = _srs_config->get_raw_api_allow_update(); + stat_ = _srs_stat; + config_ = _srs_config; +} - _srs_config->subscribe(this); +void SrsGoApiRaw::assemble() +{ + raw_api_ = config_->get_raw_api(); + allow_reload_ = config_->get_raw_api_allow_reload(); + allow_query_ = config_->get_raw_api_allow_query(); + allow_update_ = config_->get_raw_api_allow_update(); + + config_->subscribe(this); } SrsGoApiRaw::~SrsGoApiRaw() { - _srs_config->unsubscribe(this); + config_->unsubscribe(this); + + stat_ = NULL; + config_ = NULL; } extern srs_error_t _srs_reload_err; @@ -918,7 +927,7 @@ srs_error_t SrsGoApiRaw::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage * // for rpc=raw, to query the raw api config for http api. if (rpc == "raw") { // query global scope. - if ((err = _srs_config->raw_to_json(obj.get())) != srs_success) { + if ((err = config_->raw_to_json(obj.get())) != srs_success) { int code = srs_error_code(err); srs_freep(err); return srs_api_response_code(w, r, code); @@ -945,7 +954,7 @@ srs_error_t SrsGoApiRaw::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage * return srs_api_response_code(w, r, ERROR_SYSTEM_CONFIG_RAW_DISABLED); } - server_->on_signal(SRS_SIGNAL_RELOAD); + handler_->on_signal(SRS_SIGNAL_RELOAD); return srs_api_response_code(w, r, ERROR_SUCCESS); } else if (rpc == "reload-fetch") { SrsJsonObject *data = SrsJsonAny::object(); @@ -964,10 +973,12 @@ srs_error_t SrsGoApiRaw::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage * SrsGoApiClusters::SrsGoApiClusters() { + stat_ = _srs_stat; } SrsGoApiClusters::~SrsGoApiClusters() { + stat_ = NULL; } srs_error_t SrsGoApiClusters::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -997,10 +1008,12 @@ srs_error_t SrsGoApiClusters::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMess SrsGoApiError::SrsGoApiError() { + stat_ = _srs_stat; } SrsGoApiError::~SrsGoApiError() { + stat_ = NULL; } srs_error_t SrsGoApiError::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -1013,10 +1026,12 @@ srs_error_t SrsGoApiError::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage SrsGoApiTcmalloc::SrsGoApiTcmalloc() { + stat_ = _srs_stat; } SrsGoApiTcmalloc::~SrsGoApiTcmalloc() { + stat_ = NULL; } srs_error_t SrsGoApiTcmalloc::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -1097,11 +1112,15 @@ srs_error_t SrsGoApiTcmalloc::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMess SrsGoApiValgrind::SrsGoApiValgrind() { trd_ = NULL; + + stat_ = _srs_stat; } SrsGoApiValgrind::~SrsGoApiValgrind() { srs_freep(trd_); + + stat_ = NULL; } srs_error_t SrsGoApiValgrind::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -1199,10 +1218,12 @@ srs_error_t SrsGoApiValgrind::cycle() #ifdef SRS_SIGNAL_API SrsGoApiSignal::SrsGoApiSignal() { + stat_ = _srs_stat; } SrsGoApiSignal::~SrsGoApiSignal() { + stat_ = NULL; } srs_error_t SrsGoApiSignal::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -1244,13 +1265,21 @@ srs_error_t SrsGoApiSignal::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessag SrsGoApiMetrics::SrsGoApiMetrics() { - enabled_ = _srs_config->get_exporter_enabled(); - label_ = _srs_config->get_exporter_label(); - tag_ = _srs_config->get_exporter_tag(); + stat_ = _srs_stat; + config_ = _srs_config; +} + +void SrsGoApiMetrics::assemble() +{ + enabled_ = config_->get_exporter_enabled(); + label_ = config_->get_exporter_label(); + tag_ = config_->get_exporter_tag(); } SrsGoApiMetrics::~SrsGoApiMetrics() { + stat_ = NULL; + config_ = NULL; } srs_error_t SrsGoApiMetrics::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) @@ -1273,7 +1302,6 @@ srs_error_t SrsGoApiMetrics::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa * error counter */ - SrsStatistic *stat = _srs_stat; std::stringstream ss; #if defined(__linux__) || defined(SRS_OSX) @@ -1295,9 +1323,9 @@ srs_error_t SrsGoApiMetrics::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa ss << "# HELP srs_build_info A metric with a constant '1' value labeled by build_date, version from which SRS was built.\n" << "# TYPE srs_build_info gauge\n" << "srs_build_info{" - << "server=\"" << stat->server_id() << "\"," - << "service=\"" << stat->service_id() << "\"," - << "pid=\"" << stat->service_pid() << "\"," + << "server=\"" << stat_->server_id() << "\"," + << "service=\"" << stat_->service_id() << "\"," + << "pid=\"" << stat_->service_pid() << "\"," << "build_date=\"" << SRS_BUILD_DATE << "\"," << "major=\"" << VERSION_MAJOR << "\"," << "version=\"" << RTMP_SIG_SRS_VERSION << "\"," @@ -1328,7 +1356,7 @@ srs_error_t SrsGoApiMetrics::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa // Dump metrics by statistic. int64_t send_bytes, recv_bytes, nstreams, nclients, total_nclients, nerrs; - stat->dumps_metrics(send_bytes, recv_bytes, nstreams, nclients, total_nclients, nerrs); + stat_->dumps_metrics(send_bytes, recv_bytes, nstreams, nclients, total_nclients, nerrs); // The total of bytes sent. ss << "# HELP srs_send_bytes_total SRS total sent bytes.\n" diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index d0acea1f5..93291378a 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -19,6 +19,9 @@ class SrsSdp; class ISrsRequest; class ISrsHttpResponseWriter; class SrsHttpConn; +class ISrsSignalHandler; +class ISrsStatistic; +class ISrsAppConfig; #include @@ -35,6 +38,9 @@ extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter *w, ISrsHttpMess // For http root. class SrsGoApiRoot : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiRoot(); virtual ~SrsGoApiRoot(); @@ -45,6 +51,9 @@ public: class SrsGoApiApi : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiApi(); virtual ~SrsGoApiApi(); @@ -55,6 +64,9 @@ public: class SrsGoApiV1 : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiV1(); virtual ~SrsGoApiV1(); @@ -65,6 +77,9 @@ public: class SrsGoApiVersion : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiVersion(); virtual ~SrsGoApiVersion(); @@ -75,6 +90,9 @@ public: class SrsGoApiSummaries : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiSummaries(); virtual ~SrsGoApiSummaries(); @@ -85,6 +103,9 @@ public: class SrsGoApiRusages : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiRusages(); virtual ~SrsGoApiRusages(); @@ -95,6 +116,9 @@ public: class SrsGoApiSelfProcStats : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiSelfProcStats(); virtual ~SrsGoApiSelfProcStats(); @@ -105,6 +129,9 @@ public: class SrsGoApiSystemProcStats : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiSystemProcStats(); virtual ~SrsGoApiSystemProcStats(); @@ -115,6 +142,9 @@ public: class SrsGoApiMemInfos : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiMemInfos(); virtual ~SrsGoApiMemInfos(); @@ -125,6 +155,9 @@ public: class SrsGoApiAuthors : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiAuthors(); virtual ~SrsGoApiAuthors(); @@ -135,6 +168,9 @@ public: class SrsGoApiFeatures : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiFeatures(); virtual ~SrsGoApiFeatures(); @@ -145,6 +181,9 @@ public: class SrsGoApiRequests : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiRequests(); virtual ~SrsGoApiRequests(); @@ -155,6 +194,9 @@ public: class SrsGoApiVhosts : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiVhosts(); virtual ~SrsGoApiVhosts(); @@ -165,6 +207,9 @@ public: class SrsGoApiStreams : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiStreams(); virtual ~SrsGoApiStreams(); @@ -175,6 +220,9 @@ public: class SrsGoApiClients : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiClients(); virtual ~SrsGoApiClients(); @@ -186,7 +234,11 @@ public: class SrsGoApiRaw : public ISrsHttpHandler, public ISrsReloadHandler { private: - SrsServer *server_; + ISrsStatistic *stat_; + ISrsAppConfig *config_; + +private: + ISrsSignalHandler *handler_; private: bool raw_api_; @@ -195,7 +247,8 @@ private: bool allow_update_; public: - SrsGoApiRaw(SrsServer *svr); + SrsGoApiRaw(ISrsSignalHandler *handler); + void assemble(); virtual ~SrsGoApiRaw(); public: @@ -204,6 +257,9 @@ public: class SrsGoApiClusters : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiClusters(); virtual ~SrsGoApiClusters(); @@ -214,6 +270,9 @@ public: class SrsGoApiError : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiError(); virtual ~SrsGoApiError(); @@ -225,6 +284,9 @@ public: #ifdef SRS_GPERF class SrsGoApiTcmalloc : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiTcmalloc(); virtual ~SrsGoApiTcmalloc(); @@ -237,6 +299,9 @@ public: #ifdef SRS_VALGRIND class SrsGoApiValgrind : public ISrsHttpHandler, public ISrsCoroutineHandler { +private: + ISrsStatistic *stat_; + private: ISrsCoroutine *trd_; std::string task_; @@ -256,6 +321,9 @@ public: #ifdef SRS_SIGNAL_API class SrsGoApiSignal : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + public: SrsGoApiSignal(); virtual ~SrsGoApiSignal(); @@ -267,6 +335,10 @@ public: class SrsGoApiMetrics : public ISrsHttpHandler { +private: + ISrsStatistic *stat_; + ISrsAppConfig *config_; + private: bool enabled_; std::string label_; @@ -274,6 +346,7 @@ private: public: SrsGoApiMetrics(); + void assemble(); virtual ~SrsGoApiMetrics(); public: diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index 954cf484f..281b00e43 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -142,6 +142,14 @@ srs_error_t srs_global_initialize() return err; } +ISrsSignalHandler::ISrsSignalHandler() +{ +} + +ISrsSignalHandler::~ISrsSignalHandler() +{ +} + SrsServer::SrsServer() { signal_reload_ = false; @@ -758,9 +766,13 @@ srs_error_t SrsServer::http_handle() if ((err = http_api_mux_->handle("/api/v1/clients/", new SrsGoApiClients())) != srs_success) { return srs_error_wrap(err, "handle clients"); } - if ((err = http_api_mux_->handle("/api/v1/raw", new SrsGoApiRaw(this))) != srs_success) { + + SrsGoApiRaw *raw_api = new SrsGoApiRaw(this); + raw_api->assemble(); + if ((err = http_api_mux_->handle("/api/v1/raw", raw_api)) != srs_success) { return srs_error_wrap(err, "handle raw"); } + if ((err = http_api_mux_->handle("/api/v1/clusters", new SrsGoApiClusters())) != srs_success) { return srs_error_wrap(err, "handle clusters"); } @@ -805,7 +817,9 @@ srs_error_t SrsServer::http_handle() #endif // metrics by prometheus - if ((err = http_api_mux_->handle("/metrics", new SrsGoApiMetrics())) != srs_success) { + SrsGoApiMetrics *metrics = new SrsGoApiMetrics(); + metrics->assemble(); + if ((err = http_api_mux_->handle("/metrics", metrics)) != srs_success) { return srs_error_wrap(err, "handle tests errors"); } diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 19ebd74ba..40a75dc56 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -73,6 +73,17 @@ class SrsAppFactory; // Initialize global shared variables cross all threads. extern srs_error_t srs_global_initialize(); +// The signal handler interface. +class ISrsSignalHandler +{ +public: + ISrsSignalHandler(); + virtual ~ISrsSignalHandler(); + +public: + virtual void on_signal(int signo) = 0; +}; + // 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. @@ -81,7 +92,8 @@ class SrsServer : public ISrsReloadHandler, // Reload framework for permormance public ISrsTcpHandler, public ISrsHourGlassHandler, public ISrsSrtClientHandler, - public ISrsUdpMuxHandler + public ISrsUdpMuxHandler, + public ISrsSignalHandler { private: ISrsAppConfig *config_; diff --git a/trunk/src/app/srs_app_statistic.hpp b/trunk/src/app/srs_app_statistic.hpp index 2c3d80a80..5ccc89b32 100644 --- a/trunk/src/app/srs_app_statistic.hpp +++ b/trunk/src/app/srs_app_statistic.hpp @@ -148,6 +148,29 @@ public: virtual void kbps_add_delta(std::string id, ISrsKbpsDelta *delta) = 0; virtual void kbps_sample() = 0; virtual srs_error_t on_video_frames(ISrsRequest *req, int nb_frames) = 0; + +public: + // Get the server id, used to identify the server. + // For example, when restart, the server id must changed. + virtual std::string server_id() = 0; + // Get the service id, used to identify the restart of service. + virtual std::string service_id() = 0; + // Get the service pid, used to identify the service process. + virtual std::string service_pid() = 0; + // Find vhost by id. + virtual SrsStatisticVhost *find_vhost_by_id(std::string vid) = 0; + // Find stream by id. + virtual SrsStatisticStream *find_stream(std::string sid) = 0; + // Find client by id. + virtual SrsStatisticClient *find_client(std::string client_id) = 0; + // Dumps the vhosts to json array. + virtual srs_error_t dumps_vhosts(SrsJsonArray *arr) = 0; + // Dumps the streams to json array. + virtual srs_error_t dumps_streams(SrsJsonArray *arr, int start, int count) = 0; + // Dumps the clients to json array. + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count) = 0; + // Dumps exporter metrics. + virtual srs_error_t dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) = 0; }; // The global statistic instance. diff --git a/trunk/src/kernel/srs_kernel_packet.cpp b/trunk/src/kernel/srs_kernel_packet.cpp index 161934e2a..23f3d6182 100644 --- a/trunk/src/kernel/srs_kernel_packet.cpp +++ b/trunk/src/kernel/srs_kernel_packet.cpp @@ -490,22 +490,22 @@ bool SrsFormat::is_avc_sequence_header() return vcodec_ && (h264 || h265 || av1) && video_ && video_->avc_packet_type_ == SrsVideoAvcFrameTraitSequenceHeader; } -SrsParsedAudioPacket* SrsFormat::audio() +SrsParsedAudioPacket *SrsFormat::audio() { return audio_; } -SrsAudioCodecConfig* SrsFormat::acodec() +SrsAudioCodecConfig *SrsFormat::acodec() { return acodec_; } -SrsParsedVideoPacket* SrsFormat::video() +SrsParsedVideoPacket *SrsFormat::video() { return video_; } -SrsVideoCodecConfig* SrsFormat::vcodec() +SrsVideoCodecConfig *SrsFormat::vcodec() { return vcodec_; } diff --git a/trunk/src/kernel/srs_kernel_packet.hpp b/trunk/src/kernel/srs_kernel_packet.hpp index f0157412a..b0ee2def8 100644 --- a/trunk/src/kernel/srs_kernel_packet.hpp +++ b/trunk/src/kernel/srs_kernel_packet.hpp @@ -193,10 +193,10 @@ public: public: // Getters for codec and packet information - virtual SrsParsedAudioPacket* audio() = 0; - virtual SrsAudioCodecConfig* acodec() = 0; - virtual SrsParsedVideoPacket* video() = 0; - virtual SrsVideoCodecConfig* vcodec() = 0; + virtual SrsParsedAudioPacket *audio() = 0; + virtual SrsAudioCodecConfig *acodec() = 0; + virtual SrsParsedVideoPacket *video() = 0; + virtual SrsVideoCodecConfig *vcodec() = 0; }; /** @@ -247,10 +247,10 @@ public: public: // Getters for codec and packet information - virtual SrsParsedAudioPacket* audio(); - virtual SrsAudioCodecConfig* acodec(); - virtual SrsParsedVideoPacket* video(); - virtual SrsVideoCodecConfig* vcodec(); + virtual SrsParsedAudioPacket *audio(); + virtual SrsAudioCodecConfig *acodec(); + virtual SrsParsedVideoPacket *video(); + virtual SrsVideoCodecConfig *vcodec(); private: // Demux the video packet in H.264 codec. diff --git a/trunk/src/kernel/srs_kernel_ts.cpp b/trunk/src/kernel/srs_kernel_ts.cpp index 6f8591e9f..e84405d63 100644 --- a/trunk/src/kernel/srs_kernel_ts.cpp +++ b/trunk/src/kernel/srs_kernel_ts.cpp @@ -3003,22 +3003,22 @@ srs_error_t SrsTsMessageCache::cache_video(SrsParsedVideoPacket *frame, int64_t return err; } -SrsTsMessage* SrsTsMessageCache::audio() +SrsTsMessage *SrsTsMessageCache::audio() { return audio_; } -void SrsTsMessageCache::set_audio(SrsTsMessage* msg) +void SrsTsMessageCache::set_audio(SrsTsMessage *msg) { audio_ = msg; } -SrsTsMessage* SrsTsMessageCache::video() +SrsTsMessage *SrsTsMessageCache::video() { return video_; } -void SrsTsMessageCache::set_video(SrsTsMessage* msg) +void SrsTsMessageCache::set_video(SrsTsMessage *msg) { video_ = msg; } @@ -3469,7 +3469,7 @@ srs_error_t SrsTsTransmuxer::flush_audio() } // write success, clear and free the ts message. - SrsTsMessage* audio_msg = tsmc_->audio(); + SrsTsMessage *audio_msg = tsmc_->audio(); srs_freep(audio_msg); tsmc_->set_audio(NULL); @@ -3485,7 +3485,7 @@ srs_error_t SrsTsTransmuxer::flush_video() } // write success, clear and free the ts message. - SrsTsMessage* video_msg = tsmc_->video(); + SrsTsMessage *video_msg = tsmc_->video(); srs_freep(video_msg); tsmc_->set_video(NULL); diff --git a/trunk/src/kernel/srs_kernel_ts.hpp b/trunk/src/kernel/srs_kernel_ts.hpp index 2883f2e89..ad8ef525d 100644 --- a/trunk/src/kernel/srs_kernel_ts.hpp +++ b/trunk/src/kernel/srs_kernel_ts.hpp @@ -1437,10 +1437,10 @@ public: public: // Getters and setters for cached messages - virtual SrsTsMessage* audio() = 0; - virtual void set_audio(SrsTsMessage* msg) = 0; - virtual SrsTsMessage* video() = 0; - virtual void set_video(SrsTsMessage* msg) = 0; + virtual SrsTsMessage *audio() = 0; + virtual void set_audio(SrsTsMessage *msg) = 0; + virtual SrsTsMessage *video() = 0; + virtual void set_video(SrsTsMessage *msg) = 0; }; // TS messages cache, to group frames to TS message, @@ -1464,10 +1464,10 @@ public: public: // Getters and setters for cached messages - virtual SrsTsMessage* audio(); - virtual void set_audio(SrsTsMessage* msg); - virtual SrsTsMessage* video(); - virtual void set_video(SrsTsMessage* msg); + virtual SrsTsMessage *audio(); + virtual void set_audio(SrsTsMessage *msg); + virtual SrsTsMessage *video(); + virtual void set_video(SrsTsMessage *msg); private: virtual srs_error_t do_cache_mp3(SrsParsedAudioPacket *frame); diff --git a/trunk/src/protocol/srs_protocol_http_client.hpp b/trunk/src/protocol/srs_protocol_http_client.hpp index 45258b6f3..384f4d922 100644 --- a/trunk/src/protocol/srs_protocol_http_client.hpp +++ b/trunk/src/protocol/srs_protocol_http_client.hpp @@ -33,7 +33,7 @@ class SrsTcpClient; class SrsSslClient : public ISrsReader, public ISrsStreamWriter { private: - SrsTcpClient *transport_; + ISrsProtocolReadWriter *transport_; private: SSL_CTX *ssl_ctx_; diff --git a/trunk/src/utest/srs_utest_app10.cpp b/trunk/src/utest/srs_utest_app10.cpp index e09fcdf47..f7f4592ad 100644 --- a/trunk/src/utest/srs_utest_app10.cpp +++ b/trunk/src/utest/srs_utest_app10.cpp @@ -774,6 +774,62 @@ srs_error_t MockStatisticForResampleKbps::on_video_frames(ISrsRequest *req, int return srs_success; } +std::string MockStatisticForResampleKbps::server_id() +{ + return "mock_server_id"; +} + +std::string MockStatisticForResampleKbps::service_id() +{ + return "mock_service_id"; +} + +std::string MockStatisticForResampleKbps::service_pid() +{ + return "mock_pid"; +} + +SrsStatisticVhost *MockStatisticForResampleKbps::find_vhost_by_id(std::string vid) +{ + return NULL; +} + +SrsStatisticStream *MockStatisticForResampleKbps::find_stream(std::string sid) +{ + return NULL; +} + +SrsStatisticClient *MockStatisticForResampleKbps::find_client(std::string client_id) +{ + return NULL; +} + +srs_error_t MockStatisticForResampleKbps::dumps_vhosts(SrsJsonArray *arr) +{ + return srs_success; +} + +srs_error_t MockStatisticForResampleKbps::dumps_streams(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForResampleKbps::dumps_clients(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForResampleKbps::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) +{ + send_bytes = 0; + recv_bytes = 0; + nstreams = 0; + nclients = 0; + total_nclients = 0; + nerrs = 0; + return srs_success; +} + void MockStatisticForResampleKbps::reset() { kbps_add_delta_count_ = 0; diff --git a/trunk/src/utest/srs_utest_app10.hpp b/trunk/src/utest/srs_utest_app10.hpp index 13b7beea7..80c07e5e0 100644 --- a/trunk/src/utest/srs_utest_app10.hpp +++ b/trunk/src/utest/srs_utest_app10.hpp @@ -258,6 +258,16 @@ public: virtual void kbps_add_delta(std::string id, ISrsKbpsDelta *delta); virtual void kbps_sample(); virtual srs_error_t on_video_frames(ISrsRequest *req, int nb_frames); + virtual std::string server_id(); + virtual std::string service_id(); + virtual std::string service_pid(); + virtual SrsStatisticVhost *find_vhost_by_id(std::string vid); + virtual SrsStatisticStream *find_stream(std::string sid); + virtual SrsStatisticClient *find_client(std::string client_id); + virtual srs_error_t dumps_vhosts(SrsJsonArray *arr); + virtual srs_error_t dumps_streams(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs); void reset(); }; diff --git a/trunk/src/utest/srs_utest_app11.cpp b/trunk/src/utest/srs_utest_app11.cpp index 896b2d351..2c9c2fd90 100644 --- a/trunk/src/utest/srs_utest_app11.cpp +++ b/trunk/src/utest/srs_utest_app11.cpp @@ -8,19 +8,32 @@ using namespace std; #include +#include #include #include #include #include +#include #include #include #include +#include #include #include #include #include #include +// External function declarations for testing +extern srs_error_t srs_api_response_jsonp(ISrsHttpResponseWriter *w, string callback, string data); +extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, int code); +extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, srs_error_t code); + +// External global variables for reload state +extern srs_error_t _srs_reload_err; +extern SrsReloadState _srs_reload_state; +extern std::string _srs_reload_id; + MockBufferCacheForAac::MockBufferCacheForAac() { dump_cache_count_ = 0; @@ -1012,6 +1025,62 @@ srs_error_t MockStatisticForLiveStream::on_video_frames(ISrsRequest *req, int nb return srs_success; } +std::string MockStatisticForLiveStream::server_id() +{ + return "mock_server_id"; +} + +std::string MockStatisticForLiveStream::service_id() +{ + return "mock_service_id"; +} + +std::string MockStatisticForLiveStream::service_pid() +{ + return "mock_pid"; +} + +SrsStatisticVhost *MockStatisticForLiveStream::find_vhost_by_id(std::string vid) +{ + return NULL; +} + +SrsStatisticStream *MockStatisticForLiveStream::find_stream(std::string sid) +{ + return NULL; +} + +SrsStatisticClient *MockStatisticForLiveStream::find_client(std::string client_id) +{ + return NULL; +} + +srs_error_t MockStatisticForLiveStream::dumps_vhosts(SrsJsonArray *arr) +{ + return srs_success; +} + +srs_error_t MockStatisticForLiveStream::dumps_streams(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForLiveStream::dumps_clients(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForLiveStream::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) +{ + send_bytes = 0; + recv_bytes = 0; + nstreams = 0; + nclients = 0; + total_nclients = 0; + nerrs = 0; + return srs_success; +} + // Mock ISrsSecurity implementation for SrsLiveStream testing MockSecurityForLiveStream::MockSecurityForLiveStream() { @@ -1470,6 +1539,297 @@ VOID TEST(SrsHttpStreamServerTest, HttpMountAndUnmount) srs_freep(mock_config); } +VOID TEST(SrsGoApiSummariesTest, ServeHttpSuccess) +{ + srs_error_t err; + + // Test the major use scenario: serve_http returns JSON response with server info and summaries + // This covers the typical HTTP API /api/v1/summaries request use case + + // Create SrsGoApiSummaries handler + SrsUniquePtr handler(new SrsGoApiSummaries()); + + // Create mock HTTP response writer and message + MockResponseWriter mock_writer; + SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); + + // Call serve_http - should return success and write JSON response + HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); + + // Verify that response was written + string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); + EXPECT_TRUE(response.length() > 0); + + // The response includes HTTP headers, we need to extract just the JSON body + // Find the start of JSON (after the double CRLF that separates headers from body) + size_t json_start = response.find("\r\n\r\n"); + ASSERT_TRUE(json_start != string::npos); + string json_body = response.substr(json_start + 4); + + // Parse the JSON response + SrsJsonAny *json = SrsJsonAny::loads(json_body); + ASSERT_TRUE(json != NULL); + ASSERT_TRUE(json->is_object()); + SrsUniquePtr obj((SrsJsonObject *)json); + + // Verify "code" field is ERROR_SUCCESS + SrsJsonAny *code_any = obj->get_property("code"); + ASSERT_TRUE(code_any != NULL); + ASSERT_TRUE(code_any->is_integer()); + EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); + + // Verify "server" field exists and is a string + SrsJsonAny *server_any = obj->get_property("server"); + ASSERT_TRUE(server_any != NULL); + ASSERT_TRUE(server_any->is_string()); + + // Verify "service" field exists and is a string + SrsJsonAny *service_any = obj->get_property("service"); + ASSERT_TRUE(service_any != NULL); + ASSERT_TRUE(service_any->is_string()); + + // Verify "pid" field exists and is a string + SrsJsonAny *pid_any = obj->get_property("pid"); + ASSERT_TRUE(pid_any != NULL); + ASSERT_TRUE(pid_any->is_string()); + + // Verify "data" object exists (from srs_api_dump_summaries) + SrsJsonAny *data_any = obj->get_property("data"); + ASSERT_TRUE(data_any != NULL); + ASSERT_TRUE(data_any->is_object()); +} + +VOID TEST(SrsGoApiAuthorsTest, ServeHttpSuccess) +{ + srs_error_t err; + + // Test the major use scenario: serve_http returns JSON response with license and contributors + // This covers the typical HTTP API /api/v1/authors request use case + + // Create SrsGoApiAuthors handler + SrsUniquePtr handler(new SrsGoApiAuthors()); + + // Create mock HTTP response writer and message + MockResponseWriter mock_writer; + SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); + + // Call serve_http - should return success and write JSON response + HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); + + // Verify that response was written + string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); + EXPECT_TRUE(response.length() > 0); + + // The response includes HTTP headers, we need to extract just the JSON body + // Find the start of JSON (after the double CRLF that separates headers from body) + size_t json_start = response.find("\r\n\r\n"); + ASSERT_TRUE(json_start != string::npos); + string json_body = response.substr(json_start + 4); + + // Parse the JSON response + SrsJsonAny *json = SrsJsonAny::loads(json_body); + ASSERT_TRUE(json != NULL); + ASSERT_TRUE(json->is_object()); + SrsUniquePtr obj((SrsJsonObject *)json); + + // Verify "code" field is ERROR_SUCCESS + SrsJsonAny *code_any = obj->get_property("code"); + ASSERT_TRUE(code_any != NULL); + ASSERT_TRUE(code_any->is_integer()); + EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); + + // Verify "server" field exists and is a string + SrsJsonAny *server_any = obj->get_property("server"); + ASSERT_TRUE(server_any != NULL); + ASSERT_TRUE(server_any->is_string()); + + // Verify "service" field exists and is a string + SrsJsonAny *service_any = obj->get_property("service"); + ASSERT_TRUE(service_any != NULL); + ASSERT_TRUE(service_any->is_string()); + + // Verify "pid" field exists and is a string + SrsJsonAny *pid_any = obj->get_property("pid"); + ASSERT_TRUE(pid_any != NULL); + ASSERT_TRUE(pid_any->is_string()); + + // Verify "data" object exists + SrsJsonAny *data_any = obj->get_property("data"); + ASSERT_TRUE(data_any != NULL); + ASSERT_TRUE(data_any->is_object()); + SrsJsonObject *data = (SrsJsonObject *)data_any; + + // Verify "license" field exists and contains "MIT" + SrsJsonAny *license_any = data->get_property("license"); + ASSERT_TRUE(license_any != NULL); + ASSERT_TRUE(license_any->is_string()); + EXPECT_STREQ("MIT", license_any->to_str().c_str()); + + // Verify "contributors" field exists and contains the contributors URL + SrsJsonAny *contributors_any = data->get_property("contributors"); + ASSERT_TRUE(contributors_any != NULL); + ASSERT_TRUE(contributors_any->is_string()); + string contributors_url = contributors_any->to_str(); + EXPECT_TRUE(contributors_url.find("github.com/ossrs/srs") != string::npos); + EXPECT_TRUE(contributors_url.find("AUTHORS.md") != string::npos); +} + +VOID TEST(SrsGoApiFeaturesTest, ServeHttpSuccess) +{ + srs_error_t err; + + // Test the major use scenario: serve_http returns JSON response with build info and feature flags + // This covers the typical HTTP API /api/v1/features request use case + + // Create SrsGoApiFeatures handler + SrsUniquePtr handler(new SrsGoApiFeatures()); + + // Create mock HTTP response writer and message + MockResponseWriter mock_writer; + SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); + + // Call serve_http - should return success and write JSON response + HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); + + // Verify that response was written + string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); + EXPECT_TRUE(response.length() > 0); + + // The response includes HTTP headers, we need to extract just the JSON body + // Find the start of JSON (after the double CRLF that separates headers from body) + size_t json_start = response.find("\r\n\r\n"); + ASSERT_TRUE(json_start != string::npos); + string json_body = response.substr(json_start + 4); + + // Parse the JSON response + SrsJsonAny *json = SrsJsonAny::loads(json_body); + ASSERT_TRUE(json != NULL); + ASSERT_TRUE(json->is_object()); + SrsUniquePtr obj((SrsJsonObject *)json); + + // Verify "code" field is ERROR_SUCCESS + SrsJsonAny *code_any = obj->get_property("code"); + ASSERT_TRUE(code_any != NULL); + ASSERT_TRUE(code_any->is_integer()); + EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); + + // Verify "server" field exists and is a string + SrsJsonAny *server_any = obj->get_property("server"); + ASSERT_TRUE(server_any != NULL); + ASSERT_TRUE(server_any->is_string()); + + // Verify "service" field exists and is a string + SrsJsonAny *service_any = obj->get_property("service"); + ASSERT_TRUE(service_any != NULL); + ASSERT_TRUE(service_any->is_string()); + + // Verify "pid" field exists and is a string + SrsJsonAny *pid_any = obj->get_property("pid"); + ASSERT_TRUE(pid_any != NULL); + ASSERT_TRUE(pid_any->is_string()); + + // Verify "data" object exists + SrsJsonAny *data_any = obj->get_property("data"); + ASSERT_TRUE(data_any != NULL); + ASSERT_TRUE(data_any->is_object()); + SrsJsonObject *data = (SrsJsonObject *)data_any; + + // Verify build info fields exist in data + SrsJsonAny *options_any = data->get_property("options"); + ASSERT_TRUE(options_any != NULL); + ASSERT_TRUE(options_any->is_string()); + + SrsJsonAny *options2_any = data->get_property("options2"); + ASSERT_TRUE(options2_any != NULL); + ASSERT_TRUE(options2_any->is_string()); + + SrsJsonAny *build_any = data->get_property("build"); + ASSERT_TRUE(build_any != NULL); + ASSERT_TRUE(build_any->is_string()); + + SrsJsonAny *build2_any = data->get_property("build2"); + ASSERT_TRUE(build2_any != NULL); + ASSERT_TRUE(build2_any->is_string()); + + // Verify "features" object exists in data + SrsJsonAny *features_any = data->get_property("features"); + ASSERT_TRUE(features_any != NULL); + ASSERT_TRUE(features_any->is_object()); + SrsJsonObject *features = (SrsJsonObject *)features_any; + + // Verify key feature flags exist and are boolean + SrsJsonAny *ssl_any = features->get_property("ssl"); + ASSERT_TRUE(ssl_any != NULL); + ASSERT_TRUE(ssl_any->is_boolean()); + EXPECT_TRUE(ssl_any->to_boolean()); + + SrsJsonAny *hls_any = features->get_property("hls"); + ASSERT_TRUE(hls_any != NULL); + ASSERT_TRUE(hls_any->is_boolean()); + EXPECT_TRUE(hls_any->to_boolean()); + + SrsJsonAny *hds_any = features->get_property("hds"); + ASSERT_TRUE(hds_any != NULL); + ASSERT_TRUE(hds_any->is_boolean()); + + SrsJsonAny *callback_any = features->get_property("callback"); + ASSERT_TRUE(callback_any != NULL); + ASSERT_TRUE(callback_any->is_boolean()); + EXPECT_TRUE(callback_any->to_boolean()); + + SrsJsonAny *api_any = features->get_property("api"); + ASSERT_TRUE(api_any != NULL); + ASSERT_TRUE(api_any->is_boolean()); + EXPECT_TRUE(api_any->to_boolean()); + + SrsJsonAny *httpd_any = features->get_property("httpd"); + ASSERT_TRUE(httpd_any != NULL); + ASSERT_TRUE(httpd_any->is_boolean()); + EXPECT_TRUE(httpd_any->to_boolean()); + + SrsJsonAny *dvr_any = features->get_property("dvr"); + ASSERT_TRUE(dvr_any != NULL); + ASSERT_TRUE(dvr_any->is_boolean()); + EXPECT_TRUE(dvr_any->to_boolean()); + + SrsJsonAny *transcode_any = features->get_property("transcode"); + ASSERT_TRUE(transcode_any != NULL); + ASSERT_TRUE(transcode_any->is_boolean()); + EXPECT_TRUE(transcode_any->to_boolean()); + + SrsJsonAny *ingest_any = features->get_property("ingest"); + ASSERT_TRUE(ingest_any != NULL); + ASSERT_TRUE(ingest_any->is_boolean()); + EXPECT_TRUE(ingest_any->to_boolean()); + + SrsJsonAny *stat_any = features->get_property("stat"); + ASSERT_TRUE(stat_any != NULL); + ASSERT_TRUE(stat_any->is_boolean()); + EXPECT_TRUE(stat_any->to_boolean()); + + SrsJsonAny *caster_any = features->get_property("caster"); + ASSERT_TRUE(caster_any != NULL); + ASSERT_TRUE(caster_any->is_boolean()); + EXPECT_TRUE(caster_any->to_boolean()); + + // Verify performance feature flags exist and are boolean + SrsJsonAny *complex_send_any = features->get_property("complex_send"); + ASSERT_TRUE(complex_send_any != NULL); + ASSERT_TRUE(complex_send_any->is_boolean()); + + SrsJsonAny *tcp_nodelay_any = features->get_property("tcp_nodelay"); + ASSERT_TRUE(tcp_nodelay_any != NULL); + ASSERT_TRUE(tcp_nodelay_any->is_boolean()); + + SrsJsonAny *so_sendbuf_any = features->get_property("so_sendbuf"); + ASSERT_TRUE(so_sendbuf_any != NULL); + ASSERT_TRUE(so_sendbuf_any->is_boolean()); + + SrsJsonAny *mr_any = features->get_property("mr"); + ASSERT_TRUE(mr_any != NULL); + ASSERT_TRUE(mr_any->is_boolean()); +} + VOID TEST(SrsHttpStreamServerTest, DynamicMatchHttpFlv) { srs_error_t err = srs_success; @@ -2052,3 +2412,1332 @@ VOID TEST(AppHttpStreamTest, Mp3StreamEncoderMajorScenario) EXPECT_EQ(1, mock_cache.dump_cache_count_); EXPECT_EQ(SrsRtmpJitterAlgorithmOFF, mock_cache.last_jitter_); } + +VOID TEST(HttpApiTest, JsonpResponse) +{ + srs_error_t err; + + // Create mock response writer + MockResponseWriter w; + + // Test JSONP response with callback and data + string callback = "myCallback"; + string data = "{\"code\":0,\"message\":\"success\"}"; + + HELPER_EXPECT_SUCCESS(srs_api_response_jsonp(&w, callback, data)); + + // Verify the response contains callback(data) format + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("myCallback({\"code\":0,\"message\":\"success\"})") != string::npos); +} + +MockHttpMessageForApiResponse::MockHttpMessageForApiResponse() +{ + mock_conn_ = new MockHttpConn(); + set_connection(mock_conn_); + is_jsonp_ = false; + callback_ = ""; + path_ = "/api/test"; +} + +MockHttpMessageForApiResponse::~MockHttpMessageForApiResponse() +{ + srs_freep(mock_conn_); +} + +bool MockHttpMessageForApiResponse::is_jsonp() +{ + return is_jsonp_; +} + +std::string MockHttpMessageForApiResponse::query_get(std::string key) +{ + if (key == "callback") { + return callback_; + } + std::map::iterator it = query_params_.find(key); + if (it != query_params_.end()) { + return it->second; + } + return ""; +} + +std::string MockHttpMessageForApiResponse::path() +{ + return path_; +} + +VOID TEST(HttpApiTest, GoApiV1ServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiV1::serve_http returns JSON with server info and API URLs + // This covers the typical HTTP API v1 root endpoint use case + + // Create SrsGoApiV1 instance + SrsUniquePtr api(new SrsGoApiV1()); + + // Create mock statistic + MockStatisticForLiveStream mock_stat; + + // Replace stat_ with mock + api->stat_ = &mock_stat; + + // Create mock HTTP message and response writer + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + MockResponseWriter mock_writer; + + // Call serve_http - should return JSON with server info and API URLs + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify response was written + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + EXPECT_TRUE(response.length() > 0); + + // Verify response contains expected JSON fields + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\":\"mock_server_id\"") != string::npos); + EXPECT_TRUE(response.find("\"service\":\"mock_service_id\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\":\"mock_pid\"") != string::npos); + + // Verify response contains API URLs + EXPECT_TRUE(response.find("\"urls\"") != string::npos); + EXPECT_TRUE(response.find("\"versions\"") != string::npos); + EXPECT_TRUE(response.find("\"summaries\"") != string::npos); + EXPECT_TRUE(response.find("\"rusages\"") != string::npos); + EXPECT_TRUE(response.find("\"vhosts\"") != string::npos); + EXPECT_TRUE(response.find("\"streams\"") != string::npos); + EXPECT_TRUE(response.find("\"clients\"") != string::npos); + EXPECT_TRUE(response.find("\"raw\"") != string::npos); + EXPECT_TRUE(response.find("\"clusters\"") != string::npos); + + // Verify response contains test URLs + EXPECT_TRUE(response.find("\"tests\"") != string::npos); + EXPECT_TRUE(response.find("\"requests\"") != string::npos); + EXPECT_TRUE(response.find("\"errors\"") != string::npos); + EXPECT_TRUE(response.find("\"redirects\"") != string::npos); + + // Clean up - set stat_ back to NULL before destruction + api->stat_ = NULL; +} + +VOID TEST(SrsGoApiVhostsTest, ServeHttpGetAllVhosts) +{ + srs_error_t err; + + // Test the major use scenario: GET request to /api/v1/vhosts/ returns all vhosts + // This covers the typical HTTP API request to list all vhosts + + // Create SrsGoApiVhosts handler + SrsUniquePtr handler(new SrsGoApiVhosts()); + + // Create mock HTTP mux entry with pattern + SrsHttpMuxEntry entry; + entry.pattern = "/api/v1/vhosts/"; + handler->entry_ = &entry; + + // Create mock HTTP response writer and message + MockResponseWriter mock_writer; + SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); + + // Set the URL to match the pattern (no vhost_id, so list all vhosts) + HELPER_EXPECT_SUCCESS(mock_message->set_url("http://127.0.0.1/api/v1/vhosts/", false)); + + // Call serve_http - should return success and write JSON response with all vhosts + HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); + + // Verify that response was written + string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); + EXPECT_TRUE(response.length() > 0); + + // Extract JSON body from HTTP response (after headers) + size_t json_start = response.find("\r\n\r\n"); + ASSERT_TRUE(json_start != string::npos); + string json_body = response.substr(json_start + 4); + + // Parse the JSON response + SrsJsonAny *json = SrsJsonAny::loads(json_body); + ASSERT_TRUE(json != NULL); + ASSERT_TRUE(json->is_object()); + SrsUniquePtr obj((SrsJsonObject *)json); + + // Verify "code" field is ERROR_SUCCESS + SrsJsonAny *code_any = obj->get_property("code"); + ASSERT_TRUE(code_any != NULL); + ASSERT_TRUE(code_any->is_integer()); + EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); + + // Verify "server" field exists and is a string + SrsJsonAny *server_any = obj->get_property("server"); + ASSERT_TRUE(server_any != NULL); + ASSERT_TRUE(server_any->is_string()); + + // Verify "service" field exists and is a string + SrsJsonAny *service_any = obj->get_property("service"); + ASSERT_TRUE(service_any != NULL); + ASSERT_TRUE(service_any->is_string()); + + // Verify "pid" field exists and is a string + SrsJsonAny *pid_any = obj->get_property("pid"); + ASSERT_TRUE(pid_any != NULL); + ASSERT_TRUE(pid_any->is_string()); + + // Verify "vhosts" array exists (for listing all vhosts) + SrsJsonAny *vhosts_any = obj->get_property("vhosts"); + ASSERT_TRUE(vhosts_any != NULL); + ASSERT_TRUE(vhosts_any->is_array()); +} + +VOID TEST(HttpApiTest, ApiResponseCodeWithJsonAndJsonp) +{ + srs_error_t err; + + // Test the major use scenario: srs_api_response_code handles both JSON and JSONP responses + // This covers the typical HTTP API response workflow where the function automatically + // detects whether the request is JSONP (has callback parameter) and responds accordingly + + // Test 1: JSON response with integer code (no JSONP) + { + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), 0)); + + // Verify JSON response format: {"code":0} + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); + EXPECT_TRUE(response.find("callback") == string::npos); // No callback wrapper + } + + // Test 2: JSONP response with integer code (has callback parameter) + { + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = true; + mock_msg->callback_ = "myCallback"; + + HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), 0)); + + // Verify JSONP response format: myCallback({"code":0}) + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + EXPECT_TRUE(response.find("myCallback({\"code\":0})") != string::npos); + } + + // Test 3: JSON response with srs_error_t code (no JSONP) + { + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + srs_error_t test_err = srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); + HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), test_err)); + // Note: srs_api_response_code frees the error internally, so we don't need to free it + + // Verify JSON response contains the error code + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + char expected[128]; + snprintf(expected, sizeof(expected), "{\"code\":%d}", ERROR_RTMP_STREAM_NOT_FOUND); + EXPECT_TRUE(response.find(expected) != string::npos); + } + + // Test 4: JSONP response with srs_error_t code (has callback parameter) + { + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = true; + mock_msg->callback_ = "errorCallback"; + + srs_error_t test_err = srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); + HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), test_err)); + // Note: srs_api_response_code frees the error internally, so we don't need to free it + + // Verify JSONP response format: errorCallback({"code":ERROR_CODE}) + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + char expected[128]; + snprintf(expected, sizeof(expected), "errorCallback({\"code\":%d})", ERROR_RTMP_STREAM_NOT_FOUND); + EXPECT_TRUE(response.find(expected) != string::npos); + } + + // Test 5: JSON response with success code (srs_success as srs_error_t) + { + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + srs_error_t success_err = srs_success; + HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), success_err)); + + // Verify JSON response format: {"code":0} + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); + } + + // Test the major use scenario: srs_api_response_code with both JSON and JSONP responses + // This covers the typical HTTP API response workflow where the function automatically + // detects whether the request is JSONP (has callback parameter) and responds accordingly + + // Test 1: JSON response with success code - most common use case + { + MockResponseWriterForJsonp w; + SrsUniquePtr msg(new MockHttpMessage()); + + // Call srs_api_response_code with success code + HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), ERROR_SUCCESS)); + + // Verify JSON response was written with correct code + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); + + // Verify content-type is application/json + EXPECT_STREQ("application/json", w.header()->content_type().c_str()); + } + + // Test 2: JSON response with error code + { + MockResponseWriterForJsonp w; + SrsUniquePtr msg(new MockHttpMessage()); + + // Call srs_api_response_code with error code + HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), ERROR_RTMP_STREAM_NOT_FOUND)); + + // Verify error code was written in JSON response + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + char expected[64]; + snprintf(expected, sizeof(expected), "{\"code\":%d}", ERROR_RTMP_STREAM_NOT_FOUND); + EXPECT_TRUE(response.find(expected) != string::npos); + + // Verify content-type is application/json + EXPECT_STREQ("application/json", w.header()->content_type().c_str()); + } + + // Test 3: Error code response with automatic error cleanup (srs_error_t overload) + { + MockResponseWriterForJsonp w; + SrsUniquePtr msg(new MockHttpMessage()); + + // Create an error object - srs_api_response_code will free it automatically + srs_error_t error_code = srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); + + // Call srs_api_response_code with error - it should free the error object + HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), error_code)); + + // Verify error code was written in JSON response + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + char expected[64]; + snprintf(expected, sizeof(expected), "{\"code\":%d}", ERROR_RTMP_STREAM_NOT_FOUND); + EXPECT_TRUE(response.find(expected) != string::npos); + + // Verify content-type is application/json + EXPECT_STREQ("application/json", w.header()->content_type().c_str()); + + // Note: error_code was freed by srs_api_response_code, so we don't free it here + } + + // Test 4: JSONP response (with callback parameter) + { + MockResponseWriterForJsonp w; + SrsUniquePtr msg(new MockHttpMessage()); + + // Set up JSONP request by adding callback query parameter + msg->set_url("/api/v1/test?callback=myCallback", false); + + // Debug: Check if is_jsonp() works + bool is_jsonp = msg->is_jsonp(); + string callback = msg->query_get("callback"); + + // Call srs_api_response_code with success code + HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), ERROR_SUCCESS)); + + // Verify response was written + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + + // If is_jsonp() returns true, expect JSONP format, otherwise expect JSON format + if (is_jsonp && !callback.empty()) { + // JSONP format: callback({"code":0}) + EXPECT_TRUE(response.find("myCallback({\"code\":0})") != string::npos); + EXPECT_STREQ("text/javascript", w.header()->content_type().c_str()); + } else { + // JSON format: {"code":0} + EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); + EXPECT_STREQ("application/json", w.header()->content_type().c_str()); + } + } +} + +VOID TEST(HttpApiTest, GoApiApiServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiApi::serve_http returns API metadata + // This covers the typical /api endpoint that provides server information and API version + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiApi()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"urls\"") != string::npos); + EXPECT_TRUE(response.find("\"v1\"") != string::npos); + EXPECT_TRUE(response.find("the api version 1.0") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiVersionServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiVersion::serve_http returns version information + // This covers the typical /api/v1/version endpoint that provides SRS version details + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiVersion()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + EXPECT_TRUE(response.find("\"major\"") != string::npos); + EXPECT_TRUE(response.find("\"minor\"") != string::npos); + EXPECT_TRUE(response.find("\"revision\"") != string::npos); + EXPECT_TRUE(response.find("\"version\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiRusagesServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiRusages::serve_http returns system resource usage information + // This covers the typical /api/v1/rusages endpoint that provides system rusage statistics + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiRusages()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + + // Check for rusage-specific fields + EXPECT_TRUE(response.find("\"ok\"") != string::npos); + EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_utime\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_stime\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_maxrss\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_ixrss\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_idrss\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_isrss\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_minflt\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_majflt\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_nswap\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_inblock\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_oublock\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_msgsnd\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_msgrcv\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_nsignals\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_nvcsw\"") != string::npos); + EXPECT_TRUE(response.find("\"ru_nivcsw\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiSelfProcStatsServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiSelfProcStats::serve_http returns process statistics + // This covers the typical /api/v1/self_proc_stats endpoint that provides /proc/self/stat information + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiSelfProcStats()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + + // Check for self proc stat-specific fields + EXPECT_TRUE(response.find("\"ok\"") != string::npos); + EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); + EXPECT_TRUE(response.find("\"percent\"") != string::npos); + EXPECT_TRUE(response.find("\"comm\"") != string::npos); + EXPECT_TRUE(response.find("\"state\"") != string::npos); + EXPECT_TRUE(response.find("\"ppid\"") != string::npos); + EXPECT_TRUE(response.find("\"pgrp\"") != string::npos); + EXPECT_TRUE(response.find("\"session\"") != string::npos); + EXPECT_TRUE(response.find("\"tty_nr\"") != string::npos); + EXPECT_TRUE(response.find("\"tpgid\"") != string::npos); + EXPECT_TRUE(response.find("\"flags\"") != string::npos); + EXPECT_TRUE(response.find("\"minflt\"") != string::npos); + EXPECT_TRUE(response.find("\"cminflt\"") != string::npos); + EXPECT_TRUE(response.find("\"majflt\"") != string::npos); + EXPECT_TRUE(response.find("\"cmajflt\"") != string::npos); + EXPECT_TRUE(response.find("\"utime\"") != string::npos); + EXPECT_TRUE(response.find("\"stime\"") != string::npos); + EXPECT_TRUE(response.find("\"cutime\"") != string::npos); + EXPECT_TRUE(response.find("\"cstime\"") != string::npos); + EXPECT_TRUE(response.find("\"priority\"") != string::npos); + EXPECT_TRUE(response.find("\"nice\"") != string::npos); + EXPECT_TRUE(response.find("\"num_threads\"") != string::npos); + EXPECT_TRUE(response.find("\"itrealvalue\"") != string::npos); + EXPECT_TRUE(response.find("\"starttime\"") != string::npos); + EXPECT_TRUE(response.find("\"vsize\"") != string::npos); + EXPECT_TRUE(response.find("\"rss\"") != string::npos); + EXPECT_TRUE(response.find("\"rsslim\"") != string::npos); + EXPECT_TRUE(response.find("\"startcode\"") != string::npos); + EXPECT_TRUE(response.find("\"endcode\"") != string::npos); + EXPECT_TRUE(response.find("\"startstack\"") != string::npos); + EXPECT_TRUE(response.find("\"kstkesp\"") != string::npos); + EXPECT_TRUE(response.find("\"kstkeip\"") != string::npos); + EXPECT_TRUE(response.find("\"signal\"") != string::npos); + EXPECT_TRUE(response.find("\"blocked\"") != string::npos); + EXPECT_TRUE(response.find("\"sigignore\"") != string::npos); + EXPECT_TRUE(response.find("\"sigcatch\"") != string::npos); + EXPECT_TRUE(response.find("\"wchan\"") != string::npos); + EXPECT_TRUE(response.find("\"nswap\"") != string::npos); + EXPECT_TRUE(response.find("\"cnswap\"") != string::npos); + EXPECT_TRUE(response.find("\"exit_signal\"") != string::npos); + EXPECT_TRUE(response.find("\"processor\"") != string::npos); + EXPECT_TRUE(response.find("\"rt_priority\"") != string::npos); + EXPECT_TRUE(response.find("\"policy\"") != string::npos); + EXPECT_TRUE(response.find("\"delayacct_blkio_ticks\"") != string::npos); + EXPECT_TRUE(response.find("\"guest_time\"") != string::npos); + EXPECT_TRUE(response.find("\"cguest_time\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiSystemProcStatsServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiSystemProcStats::serve_http returns system CPU statistics + // This covers the typical /api/v1/system_proc_stats endpoint that provides /proc/stat information + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiSystemProcStats()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + + // Check for system proc stat-specific fields + EXPECT_TRUE(response.find("\"ok\"") != string::npos); + EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); + EXPECT_TRUE(response.find("\"percent\"") != string::npos); + EXPECT_TRUE(response.find("\"user\"") != string::npos); + EXPECT_TRUE(response.find("\"nice\"") != string::npos); + EXPECT_TRUE(response.find("\"sys\"") != string::npos); + EXPECT_TRUE(response.find("\"idle\"") != string::npos); + EXPECT_TRUE(response.find("\"iowait\"") != string::npos); + EXPECT_TRUE(response.find("\"irq\"") != string::npos); + EXPECT_TRUE(response.find("\"softirq\"") != string::npos); + EXPECT_TRUE(response.find("\"steal\"") != string::npos); + EXPECT_TRUE(response.find("\"guest\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiMemInfosServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiMemInfos::serve_http returns memory information + // This covers the typical /api/v1/meminfos endpoint that provides /proc/meminfo statistics + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiMemInfos()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + + // Check for memory info-specific fields + EXPECT_TRUE(response.find("\"ok\"") != string::npos); + EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); + EXPECT_TRUE(response.find("\"percent_ram\"") != string::npos); + EXPECT_TRUE(response.find("\"percent_swap\"") != string::npos); + EXPECT_TRUE(response.find("\"MemActive\"") != string::npos); + EXPECT_TRUE(response.find("\"RealInUse\"") != string::npos); + EXPECT_TRUE(response.find("\"NotInUse\"") != string::npos); + EXPECT_TRUE(response.find("\"MemTotal\"") != string::npos); + EXPECT_TRUE(response.find("\"MemFree\"") != string::npos); + EXPECT_TRUE(response.find("\"Buffers\"") != string::npos); + EXPECT_TRUE(response.find("\"Cached\"") != string::npos); + EXPECT_TRUE(response.find("\"SwapTotal\"") != string::npos); + EXPECT_TRUE(response.find("\"SwapFree\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiRootServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiRoot::serve_http returns API root information + // This covers the typical / endpoint that provides server identification and available API URLs + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiRoot()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Verify response contains code field + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + + // Verify response contains server identification fields + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + + // Verify response contains urls object + EXPECT_TRUE(response.find("\"urls\"") != string::npos); + EXPECT_TRUE(response.find("\"api\"") != string::npos); + + // Verify response contains rtc URLs + EXPECT_TRUE(response.find("\"rtc\"") != string::npos); + EXPECT_TRUE(response.find("\"v1\"") != string::npos); + EXPECT_TRUE(response.find("\"play\"") != string::npos); + EXPECT_TRUE(response.find("\"publish\"") != string::npos); + EXPECT_TRUE(response.find("\"nack\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiRequestsServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiRequests::serve_http returns request information + // This covers the typical /api/v1/requests endpoint that echoes back request details + // including URI, path, method, headers, and server information + + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + SrsUniquePtr api(new SrsGoApiRequests()); + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required server info fields + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + + // Check for request-specific fields in data object + EXPECT_TRUE(response.find("\"uri\"") != string::npos); + EXPECT_TRUE(response.find("\"path\"") != string::npos); + EXPECT_TRUE(response.find("\"METHOD\"") != string::npos); + EXPECT_TRUE(response.find("\"headers\"") != string::npos); + + // Check for server information fields + EXPECT_TRUE(response.find("\"sigature\"") != string::npos); + EXPECT_TRUE(response.find("\"version\"") != string::npos); + EXPECT_TRUE(response.find("\"link\"") != string::npos); + EXPECT_TRUE(response.find("\"time\"") != string::npos); +} + +VOID TEST(HttpApiTest, GoApiVhostsServeHttpWithVhostId) +{ + srs_error_t err; + + // Test the major use scenario: SrsGoApiVhosts::serve_http returns vhost list + // This covers the typical /api/v1/vhosts endpoint that provides vhost statistics + + // Create mock response writer and HTTP message + MockResponseWriter mock_writer; + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->is_jsonp_ = false; + + // Set URL for parse_rest_id to work correctly + HELPER_EXPECT_SUCCESS(mock_msg->set_url("http://127.0.0.1/api/v1/vhosts/", false)); + + // Create mock statistic + MockStatisticForLiveStream mock_stat; + + // Create API handler and inject mock statistic + SrsUniquePtr api(new SrsGoApiVhosts()); + api->stat_ = &mock_stat; + + // Setup entry pattern for REST ID parsing + api->entry_ = new SrsHttpMuxEntry(); + api->entry_->pattern = "/api/v1/vhosts/"; + + // Test the major use scenario: no vhost ID provided - should list all vhosts + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"vhosts\"") != string::npos); + + // Clean up + api->stat_ = NULL; + srs_freep(api->entry_); +} + +VOID TEST(HttpApiTest, StreamsApiGetSpecificStream) +{ + srs_error_t err; + + // Create mock HTTP message for GET request with stream ID + SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); + mock_msg->_method = SRS_CONSTS_HTTP_GET; + mock_msg->path_ = "/api/v1/streams/test_stream_id_123"; + HELPER_EXPECT_SUCCESS(mock_msg->set_url("http://127.0.0.1/api/v1/streams/test_stream_id_123", false)); + + // Create mock response writer + MockResponseWriter mock_writer; + + // Create mock statistic with a test stream + MockStatisticForLiveStream mock_stat; + + // Create a real stream object to return from find_stream + SrsStatisticStream test_stream; + test_stream.id_ = "test_stream_id_123"; + test_stream.stream_ = "livestream"; + test_stream.app_ = "live"; + test_stream.url_ = "live/livestream"; + test_stream.tcUrl_ = "rtmp://localhost/live"; + + // Create a vhost for the stream + SrsStatisticVhost test_vhost; + test_vhost.id_ = "test_vhost_id"; + test_stream.vhost_ = &test_vhost; + + // Mock find_stream to return our test stream + // Note: We need to extend MockStatisticForLiveStream to support find_stream + // For now, we'll create a custom mock inline + class MockStatisticForStreamsApi : public MockStatisticForLiveStream + { + public: + SrsStatisticStream *stream_to_return_; + MockStatisticForStreamsApi() : stream_to_return_(NULL) {} + virtual SrsStatisticStream *find_stream(std::string sid) + { + if (sid == "test_stream_id_123" && stream_to_return_) { + return stream_to_return_; + } + return NULL; + } + }; + + MockStatisticForStreamsApi mock_stat_with_stream; + mock_stat_with_stream.stream_to_return_ = &test_stream; + + // Create API handler and inject mock statistic + SrsUniquePtr api(new SrsGoApiStreams()); + api->stat_ = &mock_stat_with_stream; + + // Setup entry pattern for REST ID parsing + api->entry_ = new SrsHttpMuxEntry(); + api->entry_->pattern = "/api/v1/streams/"; + + // Test the major use scenario: specific stream ID provided - should return stream details + HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); + + // Verify JSON response contains expected fields + string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); + + // Check for required fields in response + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\"") != string::npos); + EXPECT_TRUE(response.find("\"service\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\"") != string::npos); + EXPECT_TRUE(response.find("\"stream\"") != string::npos); + + // Check for stream-specific fields from dumps() method + EXPECT_TRUE(response.find("\"id\":\"test_stream_id_123\"") != string::npos); + EXPECT_TRUE(response.find("\"name\":\"livestream\"") != string::npos); + EXPECT_TRUE(response.find("\"app\":\"live\"") != string::npos); + + // Clean up + api->stat_ = NULL; + srs_freep(api->entry_); +} + +VOID TEST(HTTPApiTest, ClientsApiGetAllClients) +{ + srs_error_t err; + + // Test the major use scenario: GET /api/v1/clients to list all clients + // This covers the typical HTTP API usage for querying client list + + // Create mock statistic with find_client and dumps_clients support + class MockStatisticForClientsApi : public MockStatisticForLiveStream + { + public: + SrsStatisticClient *client_to_return_; + srs_error_t dumps_clients_error_; + int dumps_clients_count_; + + MockStatisticForClientsApi() : client_to_return_(NULL), dumps_clients_error_(srs_success), dumps_clients_count_(0) {} + virtual ~MockStatisticForClientsApi() {} + + virtual SrsStatisticClient *find_client(std::string client_id) + { + if (client_id == "test_client_123" && client_to_return_) { + return client_to_return_; + } + return NULL; + } + + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count) + { + dumps_clients_count_++; + if (dumps_clients_error_ != srs_success) { + return srs_error_copy(dumps_clients_error_); + } + // Add mock client data + SrsJsonObject *client = SrsJsonAny::object(); + client->set("id", SrsJsonAny::str("test_client_123")); + client->set("vhost", SrsJsonAny::str("__defaultVhost__")); + client->set("stream", SrsJsonAny::str("livestream")); + client->set("ip", SrsJsonAny::str("127.0.0.1")); + arr->append(client); + return srs_success; + } + }; + + // Create mock statistic + SrsUniquePtr mock_stat(new MockStatisticForClientsApi()); + + // Create SrsGoApiClients instance + SrsUniquePtr api(new SrsGoApiClients()); + api->stat_ = mock_stat.get(); + + // Create mock entry with pattern + api->entry_ = new SrsHttpMuxEntry(); + api->entry_->pattern = "/api/v1/clients/"; + + // Create mock HTTP request for GET /api/v1/clients?start=0&count=10 + SrsUniquePtr req(new SrsHttpMessage()); + HELPER_EXPECT_SUCCESS(req->set_url("http://127.0.0.1/api/v1/clients?start=0&count=10", false)); + + // Create mock HTTP response writer + MockResponseWriter w; + + // Call serve_http + HELPER_EXPECT_SUCCESS(api->serve_http(&w, req.get())); + + // Verify dumps_clients was called + EXPECT_EQ(1, mock_stat->dumps_clients_count_); + + // Verify response contains expected JSON structure + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\":\"mock_server_id\"") != string::npos); + EXPECT_TRUE(response.find("\"service\":\"mock_service_id\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\":\"mock_pid\"") != string::npos); + EXPECT_TRUE(response.find("\"clients\"") != string::npos); + EXPECT_TRUE(response.find("\"id\":\"test_client_123\"") != string::npos); + EXPECT_TRUE(response.find("\"stream\":\"livestream\"") != string::npos); + + // Clean up + api->stat_ = NULL; + srs_freep(api->entry_); +} + +VOID TEST(HTTPApiTest, ClientsApiGetSpecificClient) +{ + srs_error_t err; + + // Test the major use scenario: GET /api/v1/clients/{client_id} to get specific client info + // This covers the typical HTTP API usage for querying a specific client and calling client->dumps() + + // Create mock statistic with find_client support + class MockStatisticForClientApi : public MockStatisticForLiveStream + { + public: + SrsStatisticClient *client_to_return_; + + MockStatisticForClientApi() : client_to_return_(NULL) {} + virtual ~MockStatisticForClientApi() {} + + virtual SrsStatisticClient *find_client(std::string client_id) + { + if (client_id == "test_client_456" && client_to_return_) { + return client_to_return_; + } + return NULL; + } + }; + + // Create mock statistic + SrsUniquePtr mock_stat(new MockStatisticForClientApi()); + + // Create a real SrsStatisticClient with all required dependencies + SrsUniquePtr test_client(new SrsStatisticClient()); + test_client->id_ = "test_client_456"; + test_client->type_ = SrsRtmpConnPlay; + + // Create mock request for the client - SrsStatisticClient destructor will free this + MockBufferCacheRequest *mock_req = new MockBufferCacheRequest("__defaultVhost__", "live", "livestream"); + test_client->req_ = mock_req; + + // Create mock vhost and stream for the client + SrsStatisticVhost test_vhost; + test_vhost.id_ = "__defaultVhost__"; + + SrsStatisticStream test_stream; + test_stream.id_ = "livestream"; + test_stream.vhost_ = &test_vhost; + + test_client->stream_ = &test_stream; + + // Set the client to return + mock_stat->client_to_return_ = test_client.get(); + + // Create SrsGoApiClients instance + SrsUniquePtr api(new SrsGoApiClients()); + api->stat_ = mock_stat.get(); + + // Create mock entry with pattern + api->entry_ = new SrsHttpMuxEntry(); + api->entry_->pattern = "/api/v1/clients/"; + + // Create mock HTTP request for GET /api/v1/clients/test_client_456 + SrsUniquePtr req(new SrsHttpMessage()); + HELPER_EXPECT_SUCCESS(req->set_url("http://127.0.0.1/api/v1/clients/test_client_456", false)); + + // Create mock HTTP response writer + MockResponseWriter w; + + // Call serve_http - this should call client->dumps() + HELPER_EXPECT_SUCCESS(api->serve_http(&w, req.get())); + + // Verify response contains expected JSON structure with client data + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"server\":\"mock_server_id\"") != string::npos); + EXPECT_TRUE(response.find("\"service\":\"mock_service_id\"") != string::npos); + EXPECT_TRUE(response.find("\"pid\":\"mock_pid\"") != string::npos); + EXPECT_TRUE(response.find("\"client\"") != string::npos); + EXPECT_TRUE(response.find("\"id\":\"test_client_456\"") != string::npos); + EXPECT_TRUE(response.find("\"vhost\":\"__defaultVhost__\"") != string::npos); + EXPECT_TRUE(response.find("\"stream\":\"livestream\"") != string::npos); + EXPECT_TRUE(response.find("\"type\":\"rtmp-play\"") != string::npos); + + // Clean up + api->stat_ = NULL; + srs_freep(api->entry_); +} + +VOID TEST(SrsGoApiClustersTest, ServeHttpSuccess) +{ + srs_error_t err; + + // Test the major use scenario: serve_http returns JSON response with query parameters and origin cluster info + // This covers the typical HTTP API /api/v1/clusters request use case for origin cluster discovery + + // Create SrsGoApiClusters handler + SrsUniquePtr handler(new SrsGoApiClusters()); + + // Create mock HTTP response writer + MockResponseWriter mock_writer; + + // Create mock HTTP message with query parameters + class MockHttpMessageForClusters : public SrsHttpMessage + { + public: + MockHttpConn *mock_conn_; + std::map query_params_; + + MockHttpMessageForClusters() + { + mock_conn_ = new MockHttpConn(); + set_connection(mock_conn_); + // Set query parameters for the test + query_params_["ip"] = "192.168.1.100"; + query_params_["vhost"] = "test.vhost"; + query_params_["app"] = "live"; + query_params_["stream"] = "livestream"; + query_params_["coworker"] = "127.0.0.1:1935"; + } + + virtual ~MockHttpMessageForClusters() + { + srs_freep(mock_conn_); + } + + virtual std::string query_get(std::string key) + { + std::map::iterator it = query_params_.find(key); + if (it != query_params_.end()) { + return it->second; + } + return ""; + } + + virtual std::string path() + { + return "/api/v1/clusters"; + } + + virtual bool is_jsonp() + { + return false; + } + }; + + SrsUniquePtr mock_message(new MockHttpMessageForClusters()); + + // Call serve_http - should return success and write JSON response + HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); + + // Verify that response was written + string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); + EXPECT_TRUE(response.length() > 0); + + // The response includes HTTP headers, we need to extract just the JSON body + // Find the start of JSON (after the double CRLF that separates headers from body) + size_t json_start = response.find("\r\n\r\n"); + ASSERT_TRUE(json_start != string::npos); + string json_body = response.substr(json_start + 4); + + // Parse the JSON response + SrsJsonAny *json = SrsJsonAny::loads(json_body); + ASSERT_TRUE(json != NULL); + ASSERT_TRUE(json->is_object()); + SrsUniquePtr obj((SrsJsonObject *)json); + + // Verify "code" field is ERROR_SUCCESS + SrsJsonAny *code_any = obj->get_property("code"); + ASSERT_TRUE(code_any != NULL); + ASSERT_TRUE(code_any->is_integer()); + EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); + + // Verify "data" object exists + SrsJsonAny *data_any = obj->get_property("data"); + ASSERT_TRUE(data_any != NULL); + ASSERT_TRUE(data_any->is_object()); + SrsJsonObject *data = (SrsJsonObject *)data_any; + + // Verify "query" object exists and contains the query parameters + SrsJsonAny *query_any = data->get_property("query"); + ASSERT_TRUE(query_any != NULL); + ASSERT_TRUE(query_any->is_object()); + SrsJsonObject *query = (SrsJsonObject *)query_any; + + // Verify query parameters are correctly echoed back + SrsJsonAny *ip_any = query->get_property("ip"); + ASSERT_TRUE(ip_any != NULL); + ASSERT_TRUE(ip_any->is_string()); + EXPECT_STREQ("192.168.1.100", ip_any->to_str().c_str()); + + SrsJsonAny *vhost_any = query->get_property("vhost"); + ASSERT_TRUE(vhost_any != NULL); + ASSERT_TRUE(vhost_any->is_string()); + EXPECT_STREQ("test.vhost", vhost_any->to_str().c_str()); + + SrsJsonAny *app_any = query->get_property("app"); + ASSERT_TRUE(app_any != NULL); + ASSERT_TRUE(app_any->is_string()); + EXPECT_STREQ("live", app_any->to_str().c_str()); + + SrsJsonAny *stream_any = query->get_property("stream"); + ASSERT_TRUE(stream_any != NULL); + ASSERT_TRUE(stream_any->is_string()); + EXPECT_STREQ("livestream", stream_any->to_str().c_str()); + + // Verify "origin" field exists (from SrsCoWorkers::dumps) + // Note: origin will be null if the stream is not published, which is expected in this test + SrsJsonAny *origin_any = data->get_property("origin"); + ASSERT_TRUE(origin_any != NULL); + // origin can be null or object depending on whether stream is published +} + +MockSignalHandler::MockSignalHandler() +{ + signal_received_ = 0; + signal_count_ = 0; +} + +MockSignalHandler::~MockSignalHandler() +{ +} + +void MockSignalHandler::on_signal(int signo) +{ + signal_received_ = signo; + signal_count_++; +} + +void MockSignalHandler::reset() +{ + signal_received_ = 0; + signal_count_ = 0; +} + +MockAppConfigForRawApi::MockAppConfigForRawApi() +{ + raw_api_ = false; + allow_reload_ = false; + allow_query_ = false; + allow_update_ = false; + raw_to_json_error_ = srs_success; +} + +MockAppConfigForRawApi::~MockAppConfigForRawApi() +{ +} + +bool MockAppConfigForRawApi::get_raw_api() +{ + return raw_api_; +} + +bool MockAppConfigForRawApi::get_raw_api_allow_reload() +{ + return allow_reload_; +} + +bool MockAppConfigForRawApi::get_raw_api_allow_query() +{ + return allow_query_; +} + +bool MockAppConfigForRawApi::get_raw_api_allow_update() +{ + return allow_update_; +} + +srs_error_t MockAppConfigForRawApi::raw_to_json(SrsJsonObject *obj) +{ + if (raw_to_json_error_ != srs_success) { + return srs_error_copy(raw_to_json_error_); + } + + // Add some test data to the object + obj->set("raw_api", SrsJsonAny::boolean(raw_api_)); + obj->set("allow_reload", SrsJsonAny::boolean(allow_reload_)); + obj->set("allow_query", SrsJsonAny::boolean(allow_query_)); + obj->set("allow_update", SrsJsonAny::boolean(allow_update_)); + + return srs_success; +} + +VOID TEST(HttpApiTest, GoApiRawServeHttp) +{ + srs_error_t err; + + // Test the major use scenario: HTTP RAW API with reload functionality + // This covers the typical RAW API use cases: query config, reload, and reload-fetch + + // Create mock signal handler + MockSignalHandler mock_handler; + + // Create mock config + MockAppConfigForRawApi mock_config; + mock_config.raw_api_ = true; + mock_config.allow_reload_ = true; + mock_config.allow_query_ = true; + mock_config.allow_update_ = true; + + // Create SrsGoApiRaw instance + SrsUniquePtr api(new SrsGoApiRaw(&mock_handler)); + + // Inject mock config before calling assemble() + api->config_ = &mock_config; + api->assemble(); + + // Test 1: rpc=raw - query the raw api config + { + MockResponseWriter w; + SrsUniquePtr r(new MockHttpMessageForApiResponse()); + r->query_params_["rpc"] = "raw"; + + HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); + + // Verify response contains raw api config + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"raw_api\":true") != string::npos); + EXPECT_TRUE(response.find("\"allow_reload\":true") != string::npos); + } + + // Test 2: rpc=reload - trigger reload signal + { + MockResponseWriter w; + SrsUniquePtr r(new MockHttpMessageForApiResponse()); + r->query_params_["rpc"] = "reload"; + + mock_handler.reset(); + HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); + + // Verify reload signal was sent + EXPECT_EQ(SRS_SIGNAL_RELOAD, mock_handler.signal_received_); + EXPECT_EQ(1, mock_handler.signal_count_); + + // Verify response indicates success + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + } + + // Test 3: rpc=reload-fetch - query reload status + { + MockResponseWriter w; + SrsUniquePtr r(new MockHttpMessageForApiResponse()); + r->query_params_["rpc"] = "reload-fetch"; + + HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); + + // Verify response contains reload status + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + EXPECT_TRUE(response.find("\"code\":0") != string::npos); + EXPECT_TRUE(response.find("\"data\"") != string::npos); + EXPECT_TRUE(response.find("\"err\"") != string::npos); + EXPECT_TRUE(response.find("\"msg\"") != string::npos); + EXPECT_TRUE(response.find("\"state\"") != string::npos); + EXPECT_TRUE(response.find("\"rid\"") != string::npos); + } + + // Unsubscribe before cleanup to avoid double unsubscribe in destructor + mock_config.unsubscribe(api.get()); +} + +VOID TEST(SrsGoApiMetricsTest, ServeHttpSuccess) +{ + srs_error_t err = srs_success; + + // Create mock statistic with test data + MockStatisticForResampleKbps mock_stat; + + // Create mock config + MockAppConfig mock_config; + + // Create SrsGoApiMetrics instance + SrsUniquePtr api(new SrsGoApiMetrics()); + api->stat_ = &mock_stat; + api->config_ = &mock_config; + api->enabled_ = true; + api->label_ = "test_label"; + api->tag_ = "test_tag"; + + // Create mock HTTP request and response + MockResponseWriter w; + SrsUniquePtr r(new MockHttpMessageForApiResponse()); + + // Call serve_http + HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); + + // Verify response + string response = HELPER_BUFFER2STR(&w.io.out_buffer); + + // Verify response contains Prometheus metrics format + EXPECT_TRUE(response.find("# HELP srs_build_info") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_build_info gauge") != string::npos); + EXPECT_TRUE(response.find("srs_build_info{") != string::npos); + + // Verify server/service info is included + EXPECT_TRUE(response.find("server=\"mock_server_id\"") != string::npos); + EXPECT_TRUE(response.find("service=\"mock_service_id\"") != string::npos); + EXPECT_TRUE(response.find("pid=\"mock_pid\"") != string::npos); + + // Verify label and tag are included + EXPECT_TRUE(response.find("label=\"test_label\"") != string::npos); + EXPECT_TRUE(response.find("tag=\"test_tag\"") != string::npos); + + // Verify CPU metric + EXPECT_TRUE(response.find("# HELP srs_cpu_percent") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_cpu_percent gauge") != string::npos); + EXPECT_TRUE(response.find("srs_cpu_percent") != string::npos); + + // Verify memory metric + EXPECT_TRUE(response.find("# HELP srs_memory") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_memory gauge") != string::npos); + EXPECT_TRUE(response.find("srs_memory") != string::npos); + + // Verify send/receive bytes metrics + EXPECT_TRUE(response.find("# HELP srs_send_bytes_total") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_send_bytes_total counter") != string::npos); + EXPECT_TRUE(response.find("srs_send_bytes_total") != string::npos); + + EXPECT_TRUE(response.find("# HELP srs_receive_bytes_total") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_receive_bytes_total counter") != string::npos); + EXPECT_TRUE(response.find("srs_receive_bytes_total") != string::npos); + + // Verify streams metric + EXPECT_TRUE(response.find("# HELP srs_streams") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_streams gauge") != string::npos); + EXPECT_TRUE(response.find("srs_streams") != string::npos); + + // Verify clients metrics + EXPECT_TRUE(response.find("# HELP srs_clients") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_clients gauge") != string::npos); + EXPECT_TRUE(response.find("srs_clients") != string::npos); + + EXPECT_TRUE(response.find("# HELP srs_clients_total") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_clients_total counter") != string::npos); + EXPECT_TRUE(response.find("srs_clients_total") != string::npos); + + // Verify errors metric + EXPECT_TRUE(response.find("# HELP srs_clients_errs_total") != string::npos); + EXPECT_TRUE(response.find("# TYPE srs_clients_errs_total counter") != string::npos); + EXPECT_TRUE(response.find("srs_clients_errs_total") != string::npos); + + // Clean up + api->stat_ = NULL; + api->config_ = NULL; +} diff --git a/trunk/src/utest/srs_utest_app11.hpp b/trunk/src/utest/srs_utest_app11.hpp index 0e8786f67..36d3454c4 100644 --- a/trunk/src/utest/srs_utest_app11.hpp +++ b/trunk/src/utest/srs_utest_app11.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -249,6 +250,16 @@ public: virtual void kbps_add_delta(std::string id, ISrsKbpsDelta *delta); virtual void kbps_sample(); virtual srs_error_t on_video_frames(ISrsRequest *req, int nb_frames); + virtual std::string server_id(); + virtual std::string service_id(); + virtual std::string service_pid(); + virtual SrsStatisticVhost *find_vhost_by_id(std::string vid); + virtual SrsStatisticStream *find_stream(std::string sid); + virtual SrsStatisticClient *find_client(std::string client_id); + virtual srs_error_t dumps_vhosts(SrsJsonArray *arr); + virtual srs_error_t dumps_streams(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs); }; // Mock ISrsSecurity for testing SrsLiveStream::serve_http_impl @@ -349,4 +360,62 @@ public: virtual std::string host(); }; +// Mock SrsHttpMessage for testing HTTP API response functions +class MockHttpMessageForApiResponse : public SrsHttpMessage +{ +public: + MockHttpConn *mock_conn_; + bool is_jsonp_; + std::string callback_; + std::string path_; + std::map query_params_; + +public: + MockHttpMessageForApiResponse(); + virtual ~MockHttpMessageForApiResponse(); + +public: + virtual bool is_jsonp(); + virtual std::string query_get(std::string key); + virtual std::string path(); +}; + +// Mock ISrsSignalHandler for testing SrsGoApiRaw +class MockSignalHandler : public ISrsSignalHandler +{ +public: + int signal_received_; + int signal_count_; + +public: + MockSignalHandler(); + virtual ~MockSignalHandler(); + +public: + virtual void on_signal(int signo); + void reset(); +}; + +// Mock ISrsAppConfig for testing SrsGoApiRaw +class MockAppConfigForRawApi : public MockAppConfig +{ +public: + bool raw_api_; + bool allow_reload_; + bool allow_query_; + bool allow_update_; + srs_error_t raw_to_json_error_; + +public: + MockAppConfigForRawApi(); + virtual ~MockAppConfigForRawApi(); + +public: + virtual bool get_raw_api(); + virtual bool get_raw_api_allow_reload(); + virtual bool get_raw_api_allow_query(); + virtual bool get_raw_api_allow_update(); + virtual srs_error_t raw_to_json(SrsJsonObject *obj); +}; + #endif diff --git a/trunk/src/utest/srs_utest_app6.cpp b/trunk/src/utest/srs_utest_app6.cpp index ffd9ea138..6de75e66b 100644 --- a/trunk/src/utest/srs_utest_app6.cpp +++ b/trunk/src/utest/srs_utest_app6.cpp @@ -2705,6 +2705,62 @@ srs_error_t MockRtcStatistic::on_video_frames(ISrsRequest *req, int nb_frames) return srs_success; } +std::string MockRtcStatistic::server_id() +{ + return "mock_server_id"; +} + +std::string MockRtcStatistic::service_id() +{ + return "mock_service_id"; +} + +std::string MockRtcStatistic::service_pid() +{ + return "mock_pid"; +} + +SrsStatisticVhost *MockRtcStatistic::find_vhost_by_id(std::string vid) +{ + return NULL; +} + +SrsStatisticStream *MockRtcStatistic::find_stream(std::string sid) +{ + return NULL; +} + +SrsStatisticClient *MockRtcStatistic::find_client(std::string client_id) +{ + return NULL; +} + +srs_error_t MockRtcStatistic::dumps_vhosts(SrsJsonArray *arr) +{ + return srs_success; +} + +srs_error_t MockRtcStatistic::dumps_streams(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockRtcStatistic::dumps_clients(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockRtcStatistic::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) +{ + send_bytes = 0; + recv_bytes = 0; + nstreams = 0; + nclients = 0; + total_nclients = 0; + nerrs = 0; + return srs_success; +} + // Unit tests for SrsRtcAsyncCallOnStop::call() VOID TEST(RtcAsyncCallOnStopTest, CallWithHttpHooksDisabled) { diff --git a/trunk/src/utest/srs_utest_app6.hpp b/trunk/src/utest/srs_utest_app6.hpp index 5f7a9dc7e..916211a69 100644 --- a/trunk/src/utest/srs_utest_app6.hpp +++ b/trunk/src/utest/srs_utest_app6.hpp @@ -248,6 +248,7 @@ public: virtual srs_error_t reload(SrsReloadState *pstate) { return srs_success; } virtual srs_error_t persistence() { return srs_success; } virtual std::string config() { return ""; } + virtual SrsConfDirective *get_root() { return NULL; } virtual int get_max_connections() { return 1000; } virtual std::string get_pid_file() { return ""; } virtual bool empty_ip_ok() { return false; } @@ -266,6 +267,11 @@ public: virtual std::vector get_https_api_listens() { return std::vector(); } virtual std::string get_https_api_ssl_key() { return ""; } virtual std::string get_https_api_ssl_cert() { return ""; } + virtual bool get_raw_api() { return false; } + virtual bool get_raw_api_allow_reload() { return false; } + virtual bool get_raw_api_allow_query() { return false; } + virtual bool get_raw_api_allow_update() { return false; } + virtual srs_error_t raw_to_json(SrsJsonObject *obj) { return srs_success; } virtual bool get_http_stream_enabled() { return false; } virtual std::vector get_http_stream_listens() { return std::vector(); } virtual bool get_https_stream_enabled() { return false; } @@ -287,6 +293,8 @@ public: virtual std::string get_stream_caster_engine(SrsConfDirective *conf) { return ""; } virtual bool get_exporter_enabled() { return false; } virtual std::string get_exporter_listen() { return ""; } + virtual std::string get_exporter_label() { return ""; } + virtual std::string get_exporter_tag() { return ""; } virtual bool get_stats_enabled() { return false; } virtual int get_stats_network() { return 0; } virtual bool get_heartbeat_enabled() { return false; } @@ -383,7 +391,6 @@ public: virtual bool get_atc_auto(std::string vhost); virtual bool get_reduce_sequence_header(std::string vhost); virtual bool get_parse_sps(std::string vhost); - virtual SrsConfDirective *get_root() { return NULL; } virtual bool get_vhost_enabled(SrsConfDirective *conf) { return true; } virtual bool get_vhost_http_remux_enabled(std::string vhost) { return false; } virtual bool get_vhost_http_remux_enabled(SrsConfDirective *vhost) { return false; } @@ -469,6 +476,16 @@ public: virtual void kbps_add_delta(std::string id, ISrsKbpsDelta *delta); virtual void kbps_sample(); virtual srs_error_t on_video_frames(ISrsRequest *req, int nb_frames); + virtual std::string server_id(); + virtual std::string service_id(); + virtual std::string service_pid(); + virtual SrsStatisticVhost *find_vhost_by_id(std::string vid); + virtual SrsStatisticStream *find_stream(std::string sid); + virtual SrsStatisticClient *find_client(std::string client_id); + virtual srs_error_t dumps_vhosts(SrsJsonArray *arr); + virtual srs_error_t dumps_streams(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs); void set_on_client_error(srs_error_t err); void reset(); }; diff --git a/trunk/src/utest/srs_utest_app9.cpp b/trunk/src/utest/srs_utest_app9.cpp index 45882548d..231b79539 100644 --- a/trunk/src/utest/srs_utest_app9.cpp +++ b/trunk/src/utest/srs_utest_app9.cpp @@ -1922,6 +1922,62 @@ srs_error_t MockStatisticForOriginHub::on_video_frames(ISrsRequest *req, int nb_ return srs_success; } +std::string MockStatisticForOriginHub::server_id() +{ + return "mock_server_id"; +} + +std::string MockStatisticForOriginHub::service_id() +{ + return "mock_service_id"; +} + +std::string MockStatisticForOriginHub::service_pid() +{ + return "mock_pid"; +} + +SrsStatisticVhost *MockStatisticForOriginHub::find_vhost_by_id(std::string vid) +{ + return NULL; +} + +SrsStatisticStream *MockStatisticForOriginHub::find_stream(std::string sid) +{ + return NULL; +} + +SrsStatisticClient *MockStatisticForOriginHub::find_client(std::string client_id) +{ + return NULL; +} + +srs_error_t MockStatisticForOriginHub::dumps_vhosts(SrsJsonArray *arr) +{ + return srs_success; +} + +srs_error_t MockStatisticForOriginHub::dumps_streams(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForOriginHub::dumps_clients(SrsJsonArray *arr, int start, int count) +{ + return srs_success; +} + +srs_error_t MockStatisticForOriginHub::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) +{ + send_bytes = 0; + recv_bytes = 0; + nstreams = 0; + nclients = 0; + total_nclients = 0; + nerrs = 0; + return srs_success; +} + // Mock ISrsNgExec implementation MockNgExecForOriginHub::MockNgExecForOriginHub() { diff --git a/trunk/src/utest/srs_utest_app9.hpp b/trunk/src/utest/srs_utest_app9.hpp index 26c61c1b8..7a6950685 100644 --- a/trunk/src/utest/srs_utest_app9.hpp +++ b/trunk/src/utest/srs_utest_app9.hpp @@ -202,6 +202,16 @@ public: virtual void kbps_add_delta(std::string id, ISrsKbpsDelta *delta); virtual void kbps_sample(); virtual srs_error_t on_video_frames(ISrsRequest *req, int nb_frames); + virtual std::string server_id(); + virtual std::string service_id(); + virtual std::string service_pid(); + virtual SrsStatisticVhost *find_vhost_by_id(std::string vid); + virtual SrsStatisticStream *find_stream(std::string sid); + virtual SrsStatisticClient *find_client(std::string client_id); + virtual srs_error_t dumps_vhosts(SrsJsonArray *arr); + virtual srs_error_t dumps_streams(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count); + virtual srs_error_t dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs); }; // Mock ISrsNgExec for testing SrsOriginHub::on_publish diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index c3c719eb3..27faf453c 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -114,6 +114,56 @@ srs_error_t MockResponseWriter::filter(SrsHttpHeader *h) return srs_success; } +MockResponseWriterForJsonp::MockResponseWriterForJsonp() +{ + w = new SrsHttpResponseWriter(&io); + w->set_header_filter(this); +} + +MockResponseWriterForJsonp::~MockResponseWriterForJsonp() +{ + srs_freep(w); +} + +srs_error_t MockResponseWriterForJsonp::final_request() +{ + return w->final_request(); +} + +SrsHttpHeader *MockResponseWriterForJsonp::header() +{ + return w->header(); +} + +srs_error_t MockResponseWriterForJsonp::write(char *data, int size) +{ + return w->write(data, size); +} + +srs_error_t MockResponseWriterForJsonp::writev(const iovec *iov, int iovcnt, ssize_t *pnwrite) +{ + return w->writev(iov, iovcnt, pnwrite); +} + +void MockResponseWriterForJsonp::write_header(int code) +{ + w->write_header(code); +} + +srs_error_t MockResponseWriterForJsonp::filter(SrsHttpHeader *h) +{ + // Do NOT filter Content-Type for JSONP testing - we need to verify it + h->del("Server"); + h->del("Connection"); + h->del("Location"); + h->del("Content-Range"); + h->del("Access-Control-Allow-Origin"); + h->del("Access-Control-Allow-Methods"); + h->del("Access-Control-Expose-Headers"); + h->del("Access-Control-Allow-Headers"); + return srs_success; +} + string mock_http_response(int status, string content) { stringstream ss; diff --git a/trunk/src/utest/srs_utest_http.hpp b/trunk/src/utest/srs_utest_http.hpp index bf2cc4491..c414b55e0 100644 --- a/trunk/src/utest/srs_utest_http.hpp +++ b/trunk/src/utest/srs_utest_http.hpp @@ -40,6 +40,28 @@ public: virtual srs_error_t filter(SrsHttpHeader *h); }; +// Mock response writer for JSONP testing - does not filter Content-Type header +class MockResponseWriterForJsonp : public ISrsHttpResponseWriter, public ISrsHttpHeaderFilter +{ +public: + SrsHttpResponseWriter *w; + MockBufferIO io; + +public: + MockResponseWriterForJsonp(); + virtual ~MockResponseWriterForJsonp(); + +public: + virtual srs_error_t final_request(); + virtual SrsHttpHeader *header(); + virtual srs_error_t write(char *data, int size); + virtual srs_error_t writev(const iovec *iov, int iovcnt, ssize_t *pnwrite); + virtual void write_header(int code); + +public: + virtual srs_error_t filter(SrsHttpHeader *h); +}; + class MockMSegmentsReader : public ISrsReader { public: