diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index e67931d4f..1d138345a 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -109,6 +109,9 @@ http_api { # the http api port # default: 1985 listen 1985; + # whether enable crossdomain request. + # default: on + crossdomain on; } # embeded http server in srs. # the http streaming config, for HLS/HDS/DASH/HTTPProgressive @@ -286,6 +289,31 @@ vhost dvr.srs.com { # segment reap flv when flv duration exceed the specified dvr_duration. # append always append to flv file, never reap it. # api reap flv when api required. + # about the api plan, the HTTP api to dvr, + # http url to control dvr, for example, http://dev:1985/api/v1/dvrs + # method=GET + # to query dvrs of server. + # request params, for example ?vhost=__defaultVhost__, where: + # vhost, query all dvr of this vhost. + # response in json, where: + # {code:0, dvrs: [{plan:"api", path:"./objs/nginx/html", + # autostart:true, wait_keyframe:true, jitter:"full" + # }]} + # method=POST + # to start dvr of specified vhost. + # request should encode in json, specifies the dvr to create, where: + # {plan:"api", path:"./objs/nginx/html", + # autostart:true, wait_keyframe:true, jitter:"full", + # vhost:"__defaultVhost", callback:"http://dvr/callback" + # } + # response in json, where: + # {code:0} + # method=DELETE, to stop dvr + # to stop dvr of specified vhost. + # request params, for example ?vhost=__defaultVhost__, where: + # vhost, stop all dvr of this vhost. + # response in json, where: + # {code:0} # default: session dvr_plan session; # the dvr output path. diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index dc3614154..2387cb652 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1334,7 +1334,7 @@ int SrsConfig::check_config() SrsConfDirective* conf = get_http_api(); for (int i = 0; conf && i < (int)conf->directives.size(); i++) { string n = conf->at(i)->name; - if (n != "enabled" && n != "listen") { + if (n != "enabled" && n != "listen" && n != "crossdomain") { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported http_api directive %s, ret=%d", n.c_str(), ret); return ret; @@ -3453,6 +3453,22 @@ int SrsConfig::get_http_api_listen() return ::atoi(conf->arg0().c_str()); } +bool SrsConfig::get_http_api_crossdomain() +{ + SrsConfDirective* conf = get_http_api(); + + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN; + } + + conf = conf->get("crossdomain"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN; + } + + return conf->arg0() != "off"; +} + bool SrsConfig::get_http_stream_enabled() { SrsConfDirective* conf = get_http_stream(); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 8e149b954..bcef5eac4 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -79,6 +79,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONF_DEFAULT_HTTP_STREAM_PORT 8080 #define SRS_CONF_DEFAULT_HTTP_API_PORT 1985 +#define SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN true #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED false #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL 9.9 @@ -957,6 +958,10 @@ public: * get the http api listen port. */ virtual int get_http_api_listen(); + /** + * whether enable crossdomain for http api. + */ + virtual bool get_http_api_crossdomain(); // http stream section private: /** diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 68a662055..97d60d8c5 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -40,6 +40,7 @@ using namespace std; #include #include #include +#include // update the flv duration and filesize every this interval in ms. #define __SRS_DVR_UPDATE_DURATION_INTERVAL 60000 @@ -665,6 +666,8 @@ SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) return new SrsDvrSessionPlan(); } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_APPEND) { return new SrsDvrAppendPlan(); + } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_API) { + return new SrsDvrApiPlan(); } else { srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str()); srs_assert(false); @@ -721,6 +724,56 @@ void SrsDvrSessionPlan::on_unpublish() dvr_enabled = false; } +SrsDvrApiPlan::SrsDvrApiPlan() +{ +} + +SrsDvrApiPlan::~SrsDvrApiPlan() +{ +} + +int SrsDvrApiPlan::on_publish() +{ + int ret = ERROR_SUCCESS; + + // support multiple publish. + if (dvr_enabled) { + return ret; + } + + if (!_srs_config->get_dvr_enabled(req->vhost)) { + return ret; + } + + if ((ret = segment->close()) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = segment->open()) != ERROR_SUCCESS) { + return ret; + } + + dvr_enabled = true; + + return ret; +} + +void SrsDvrApiPlan::on_unpublish() +{ + // support multiple publish. + if (!dvr_enabled) { + return; + } + + // ignore error. + int ret = segment->close(); + if (ret != ERROR_SUCCESS) { + srs_warn("ignore flv close error. ret=%d", ret); + } + + dvr_enabled = false; +} + SrsDvrAppendPlan::SrsDvrAppendPlan() { last_update_time = 0; @@ -977,6 +1030,29 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) return ret; } +SrsApiDvrPool* SrsApiDvrPool::_instance = new SrsApiDvrPool(); + +SrsApiDvrPool* SrsApiDvrPool::instance() +{ + return SrsApiDvrPool::_instance; +} + +SrsApiDvrPool::SrsApiDvrPool() +{ +} + +SrsApiDvrPool::~SrsApiDvrPool() +{ +} + +int SrsApiDvrPool::dumps(stringstream& ss) +{ + int ret = ERROR_SUCCESS; + ss << __SRS_JARRAY_START + << __SRS_JARRAY_END; + return ret; +} + SrsDvr::SrsDvr(SrsSource* s) { source = s; diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index e66ea9eb3..f3e6ce1bb 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -30,6 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include #ifdef SRS_AUTO_DVR @@ -223,6 +224,19 @@ public: virtual void on_unpublish(); }; +/** +* api plan: reap flv by api. +*/ +class SrsDvrApiPlan : public SrsDvrPlan +{ +public: + SrsDvrApiPlan(); + virtual ~SrsDvrApiPlan(); +public: + virtual int on_publish(); + virtual void on_unpublish(); +}; + /** * always append to flv file, never reap it. */ @@ -282,6 +296,22 @@ private: virtual int update_duration(SrsSharedPtrMessage* msg); }; +/** +* the api dvr pool. +*/ +class SrsApiDvrPool +{ +private: + static SrsApiDvrPool* _instance; +private: + SrsApiDvrPool(); +public: + static SrsApiDvrPool* instance(); + virtual ~SrsApiDvrPool(); +public: + virtual int dumps(std::stringstream& ss); +}; + /** * dvr(digital video recorder) to record RTMP stream to flv file. * TODO: FIXME: add utest for it. diff --git a/trunk/src/app/srs_app_http.cpp b/trunk/src/app/srs_app_http.cpp index 9333f7af5..5ad63ca99 100644 --- a/trunk/src/app/srs_app_http.cpp +++ b/trunk/src/app/srs_app_http.cpp @@ -139,6 +139,9 @@ bool srs_go_http_body_allowd(int status) // returns "application/octet-stream". string srs_go_http_detect(char* data, int size) { + // detect only when data specified. + if (data) { + } return "application/octet-stream"; // fallback } @@ -715,8 +718,8 @@ int SrsGoHttpResponseWriter::final_request() return skt->write((void*)ch.data(), (int)ch.length(), NULL); } - // ignore when send with content length - return ERROR_SUCCESS; + // flush when send with content length + return write(NULL, 0); } SrsGoHttpHeader* SrsGoHttpResponseWriter::header() @@ -743,6 +746,11 @@ int SrsGoHttpResponseWriter::write(char* data, int size) srs_error("http: send header failed. ret=%d", ret); return ret; } + + // ignore NULL content. + if (!data) { + return ret; + } // directly send with content length if (content_length != -1) { diff --git a/trunk/src/app/srs_app_http.hpp b/trunk/src/app/srs_app_http.hpp index abc4f29f3..b0745fd8e 100644 --- a/trunk/src/app/srs_app_http.hpp +++ b/trunk/src/app/srs_app_http.hpp @@ -123,6 +123,29 @@ public: // A ResponseWriter interface is used by an HTTP handler to // construct an HTTP response. +// Usage 1, response with specified length content: +// ISrsGoHttpResponseWriter* w; // create or get response. +// std::string msg = "Hello, HTTP!"; +// w->header()->set_content_type("text/plain; charset=utf-8"); +// w->header()->set_content_length(msg.length()); +// w->write_header(SRS_CONSTS_HTTP_OK); +// w->write((char*)msg.data(), (int)msg.length()); +// w->final_request(); // optional flush. +// Usage 2, response with HTTP code only, zero content length. +// ISrsGoHttpResponseWriter* w; // create or get response. +// w->header()->set_content_length(0); +// w->write_header(SRS_CONSTS_HTTP_OK); +// w->final_request(); +// Usage 3, response in chunked encoding. +// ISrsGoHttpResponseWriter* w; // create or get response. +// std::string msg = "Hello, HTTP!"; +// w->header()->set_content_type("application/octet-stream"); +// w->write_header(SRS_CONSTS_HTTP_OK); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->final_request(); // required to end the chunked and flush. class ISrsGoHttpResponseWriter { public: @@ -143,6 +166,7 @@ public: // before writing the data. If the Header does not contain a // Content-Type line, Write adds a Content-Type set to the result of passing // the initial 512 bytes of written data to DetectContentType. + // @param data, the data to send. NULL to flush header only. virtual int write(char* data, int size) = 0; // WriteHeader sends an HTTP response header with status code. diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 8f7dfa6dc..9657a41af 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -37,6 +37,8 @@ using namespace std; #include #include #include +#include +#include SrsGoApiRoot::SrsGoApiRoot() { @@ -472,11 +474,48 @@ int SrsGoApiStreams::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) return srs_go_http_response_json(w, ss.str()); } +SrsGoApiDvrs::SrsGoApiDvrs() +{ +} + +SrsGoApiDvrs::~SrsGoApiDvrs() +{ +} + +int SrsGoApiDvrs::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) +{ + std::stringstream ss; + +#ifndef SRS_AUTO_DVR + ss << __SRS_JOBJECT_START + << __SRS_JFIELD_ERROR(ERROR_HTTP_DVR_DISABLED) + << __SRS_JOBJECT_END; +#else + SrsApiDvrPool* pool = SrsApiDvrPool::instance(); + if (r->is_http_get()) { + std::stringstream data; + int ret = pool->dumps(data); + + ss << __SRS_JOBJECT_START + << __SRS_JFIELD_ERROR(ret) << __SRS_JFIELD_CONT + << __SRS_JFIELD_ORG("dvrs", data.str()) + << __SRS_JOBJECT_END; + } else { + ss << __SRS_JOBJECT_START + << __SRS_JFIELD_ERROR(ERROR_HTTP_DVR_REQUEST) + << __SRS_JOBJECT_END; + } +#endif + + return srs_go_http_response_json(w, ss.str()); +} + SrsHttpApi::SrsHttpApi(SrsServer* svr, st_netfd_t fd, SrsGoHttpServeMux* m) : SrsConnection(svr, fd) { mux = m; parser = new SrsHttpParser(); + crossdomain_required = false; } SrsHttpApi::~SrsHttpApi() @@ -549,6 +588,29 @@ int SrsHttpApi::process_request(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) srs_trace("HTTP %s %s, content-length=%"PRId64"", r->method_str().c_str(), r->url().c_str(), r->content_length()); + // method is OPTIONS and enable crossdomain, required crossdomain header. + if (r->is_http_options() && _srs_config->get_http_api_crossdomain()) { + crossdomain_required = true; + } + + // whenever crossdomain required, set crossdomain header. + if (crossdomain_required) { + w->header()->set("Access-Control-Allow-Origin", "*"); + w->header()->set("Access-Control-Allow-Methods", "GET, POST, HEAD, PUT, DELETE"); + w->header()->set("Access-Control-Allow-Headers", "Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type"); + } + + // handle the http options. + if (r->is_http_options()) { + w->header()->set_content_length(0); + if (_srs_config->get_http_api_crossdomain()) { + w->write_header(SRS_CONSTS_HTTP_OK); + } else { + w->write_header(SRS_CONSTS_HTTP_MethodNotAllowed); + } + return w->final_request(); + } + // use default server mux to serve http request. if ((ret = mux->serve_http(w, r)) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret)) { diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index d21f3683d..067047cda 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -159,11 +159,21 @@ public: virtual int serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r); }; +class SrsGoApiDvrs : public ISrsGoHttpHandler +{ +public: + SrsGoApiDvrs(); + virtual ~SrsGoApiDvrs(); +public: + virtual int serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r); +}; + class SrsHttpApi : public SrsConnection { private: SrsHttpParser* parser; SrsGoHttpServeMux* mux; + bool crossdomain_required; public: SrsHttpApi(SrsServer* svr, st_netfd_t fd, SrsGoHttpServeMux* m); virtual ~SrsHttpApi(); diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index 8be3609f1..7d2481ea5 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -529,6 +529,9 @@ int SrsServer::initialize() if ((ret = http_api_mux->handle("/api/v1/streams", new SrsGoApiStreams())) != ERROR_SUCCESS) { return ret; } + if ((ret = http_api_mux->handle("/api/v1/dvrs", new SrsGoApiDvrs())) != ERROR_SUCCESS) { + return ret; + } #endif #ifdef SRS_AUTO_HTTP_SERVER diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 539045231..994fe92d6 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -209,6 +209,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_AAC_ADTS_HEADER 3047 #define ERROR_AAC_DATA_INVALID 3048 #define ERROR_HLS_TRY_MP3 3049 +#define ERROR_HTTP_DVR_DISABLED 3050 +#define ERROR_HTTP_DVR_REQUEST 3051 /////////////////////////////////////////////////////// // HTTP/StreamCaster protocol error.