From dc11418c796e05992851ef0735d1ce51b798ff4c Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 3 Jan 2015 15:33:23 +0800 Subject: [PATCH] fix #274: http-callback support on_dvr when reap a dvr file. 2.0.89 --- README.md | 1 + trunk/conf/full.conf | 21 +++++++- trunk/research/api-server/server.py | 72 +++++++++++++++++++++++++++- trunk/src/app/srs_app_config.cpp | 12 +++++ trunk/src/app/srs_app_config.hpp | 5 ++ trunk/src/app/srs_app_dvr.cpp | 24 ++++++++++ trunk/src/app/srs_app_http_hooks.cpp | 58 +++++++++++++++++++++- trunk/src/app/srs_app_http_hooks.hpp | 12 +++-- trunk/src/app/srs_app_rtmp_conn.cpp | 3 ++ trunk/src/core/srs_core.hpp | 2 +- trunk/src/rtmp/srs_protocol_rtmp.cpp | 1 + trunk/src/rtmp/srs_protocol_rtmp.hpp | 3 ++ 12 files changed, 207 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1c1dd2d4a..0c0d17c55 100755 --- a/README.md +++ b/README.md @@ -501,6 +501,7 @@ Supported operating systems and hardware: * 2013-10-17, Created.
## History +* v2.0, 2015-01-03, fix [#274](https://github.com/winlinvip/simple-rtmp-server/issues/274), http-callback support on_dvr when reap a dvr file. 2.0.89 * v2.0, 2015-01-03, hotfix to remove the pageUrl for http callback. 2.0.88 * v2.0, 2015-01-03, fix [#179](https://github.com/winlinvip/simple-rtmp-server/issues/179), dvr support custom filepath by variables. 2.0.87 * v2.0, 2015-01-02, fix [#211](https://github.com/winlinvip/simple-rtmp-server/issues/211), support security allow/deny publish/play all/ip. 2.0.86 diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 34613109b..3f7e860c9 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -296,6 +296,11 @@ vhost dvr.srs.com { # 3. off, disable the time jitter algorithm, like atc. # default: full time_jitter full; + + # on_dvr + # for the dvr http callback, @see http_hooks.on_dvr of vhost hooks.callback.srs.com + # @read https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DVR#http-callback + # @read https://github.com/winlinvip/simple-rtmp-server/wiki/v2_EN_DVR#http-callback } } @@ -338,7 +343,7 @@ vhost ingest.srs.com { } } -# vhost for http +# vhost for http server config in each vhost. vhost http.srs.com { # http vhost specified config http { @@ -490,6 +495,20 @@ vhost hooks.callback.srs.com { # support multiple api hooks, format: # on_stop http://xxx/api0 http://xxx/api1 http://xxx/apiN on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; + # when srs reap a dvr file, call the hook, + # the request in the POST data string is a object encode by json: + # { + # "action": "on_dvr", + # "client_id": 1985, + # "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + # "stream": "livestream", + # "cwd": "/usr/local/srs", + # "file": "./objs/nginx/html/live/livestream.1420254068776.flv" + # } + # if valid, the hook must return HTTP code 200(Stauts OK) and response + # an int value specifies the error code(0 corresponding to success): + # 0 + on_dvr http://127.0.0.1:8085/api/v1/dvrs http://localhost:8085/api/v1/dvrs; } } diff --git a/trunk/research/api-server/server.py b/trunk/research/api-server/server.py index 028ab9e5b..8c1f4823b 100755 --- a/trunk/research/api-server/server.py +++ b/trunk/research/api-server/server.py @@ -154,7 +154,7 @@ class RESTClients(object): return code ''' -handle the streams requests: publish/unpublish/dvr stream. +handle the streams requests: publish/unpublish stream. ''' class RESTStreams(object): exposed = True @@ -240,6 +240,74 @@ class RESTStreams(object): return code +''' +handle the dvrs requests: dvr stream. +''' +class RESTDvrs(object): + exposed = True + + def GET(self): + enable_crossdomain() + + dvrs = {} + return json.dumps(dvrs) + + ''' + for SRS hook: on_dvr + on_dvr: + when srs reap a dvr file, call the hook, + the request in the POST data string is a object encode by json: + { + "action": "on_dvr", + "client_id": 1985, + "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live", + "stream": "livestream", + "cwd": "/usr/local/srs", + "file": "./objs/nginx/html/live/livestream.1420254068776.flv" + } + if valid, the hook must return HTTP code 200(Stauts OK) and response + an int value specifies the error code(0 corresponding to success): + 0 + ''' + def POST(self): + enable_crossdomain() + + # return the error code in str + code = Error.success + + req = cherrypy.request.body.read() + trace("post to dvrs, req=%s"%(req)) + try: + json_req = json.loads(req) + except Exception, ex: + code = Error.system_parse_json + trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code)) + return str(code) + + action = json_req["action"] + if action == "on_dvr": + code = self.__on_dvr(json_req) + else: + trace("invalid request action: %s"%(json_req["action"])) + code = Error.request_invalid_action + + return str(code) + + def OPTIONS(self, *args, **kwargs): + enable_crossdomain() + + def __on_dvr(self, req): + code = Error.success + + trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, cwd=%s, file=%s"%( + req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], + req["cwd"], req["file"] + )) + + # TODO: process the on_dvr event + + return code + ''' handle the sessions requests: client play/stop stream ''' @@ -1039,6 +1107,7 @@ class V1(object): self.clients = RESTClients() self.streams = RESTStreams() self.sessions = RESTSessions() + self.dvrs = RESTDvrs() self.chats = RESTChats() self.servers = RESTServers() self.nodes = RESTNodes() @@ -1048,6 +1117,7 @@ class V1(object): "clients": "for srs http callback, to handle the clients requests: connect/disconnect vhost/app.", "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.", "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream", + "dvrs": "for srs http callback, to handle the dvr requests: dvr stream.", "chats": "for srs demo meeting, the chat streams, public chat room.", "nodes": { "summary": "for srs cdn node", diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 4b1d616be..a6a695fb8 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1426,6 +1426,7 @@ int SrsConfig::check_config() string m = conf->at(j)->name.c_str(); if (m != "enabled" && m != "on_connect" && m != "on_close" && m != "on_publish" && m != "on_unpublish" && m != "on_play" && m != "on_stop" + && m != "on_dvr" ) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported vhost http_hooks directive %s, ret=%d", m.c_str(), ret); @@ -2335,6 +2336,17 @@ SrsConfDirective* SrsConfig::get_vhost_on_stop(string vhost) return conf->get("on_stop"); } +SrsConfDirective* SrsConfig::get_vhost_on_dvr(string vhost) +{ + SrsConfDirective* conf = get_vhost_http_hooks(vhost); + + if (!conf) { + return NULL; + } + + return conf->get("on_dvr"); +} + bool SrsConfig::get_bw_check_enabled(string vhost) { SrsConfDirective* conf = get_vhost(vhost); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 959553204..80858be5a 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -610,6 +610,11 @@ public: * @return the on_stop callback directive, the args is the url to callback. */ virtual SrsConfDirective* get_vhost_on_stop(std::string vhost); + /** + * get the on_dvr callbacks of vhost. + * @return the on_dvr callback directive, the args is the url to callback. + */ + virtual SrsConfDirective* get_vhost_on_dvr(std::string vhost); // bwct(bandwidth check tool) section public: /** diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index c5aef39ea..631513698 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -404,6 +404,30 @@ int SrsDvrPlan::flv_close() return ret; } +#ifdef SRS_AUTO_HTTP_CALLBACK + SrsRequest* req = _req; + if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { + // HTTP: on_dvr + SrsConfDirective* on_dvr = _srs_config->get_vhost_on_dvr(req->vhost); + if (!on_dvr) { + srs_info("ignore the empty http callback: on_dvr"); + return ret; + } + + int connection_id = _srs_context->get_id(); + std::string ip = req->ip; + std::string cwd = _srs_config->cwd(); + std::string file = segment->path; + for (int i = 0; i < (int)on_dvr->args.size(); i++) { + std::string url = on_dvr->args.at(i); + if ((ret = SrsHttpHooks::on_dvr(url, connection_id, ip, req, cwd, file)) != ERROR_SUCCESS) { + srs_error("hook client on_dvr failed. url=%s, ret=%d", url.c_str(), ret); + return ret; + } + } + } +#endif + return ret; } diff --git a/trunk/src/app/srs_app_http_hooks.cpp b/trunk/src/app/srs_app_http_hooks.cpp index fcf263abd..cfa4712f6 100644 --- a/trunk/src/app/srs_app_http_hooks.cpp +++ b/trunk/src/app/srs_app_http_hooks.cpp @@ -379,5 +379,61 @@ void SrsHttpHooks::on_stop(string url, int client_id, string ip, SrsRequest* req return; } -#endif +int SrsHttpHooks::on_dvr(string url, int client_id, string ip, SrsRequest* req, string cwd, string file) +{ + int ret = ERROR_SUCCESS; + + SrsHttpUri uri; + if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { + srs_error("http uri parse on_dvr url failed, ignored. " + "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); + return ret; + } + + std::stringstream ss; + ss << __SRS_JOBJECT_START + << __SRS_JFIELD_STR("action", "on_dvr") << __SRS_JFIELD_CONT + << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT + << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT + << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT + << __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT + << __SRS_JFIELD_STR("stream", req->stream) << __SRS_JFIELD_CONT + << __SRS_JFIELD_STR("cwd", cwd) << __SRS_JFIELD_CONT + << __SRS_JFIELD_STR("file", file) + << __SRS_JOBJECT_END; + std::string data = ss.str(); + std::string res; + int status_code; + + SrsHttpClient http; + if ((ret = http.post(&uri, data, status_code, res)) != ERROR_SUCCESS) { + srs_error("http post on_dvr uri failed, ignored. " + "client_id=%d, url=%s, request=%s, response=%s, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), ret); + return ret; + } + + // ensure the http status is ok. + // https://github.com/winlinvip/simple-rtmp-server/issues/158 + if (status_code != SRS_CONSTS_HTTP_OK) { + ret = ERROR_HTTP_STATUS_INVLIAD; + srs_error("http hook on_dvr status failed. " + "client_id=%d, code=%d, ret=%d", client_id, status_code, ret); + return ret; + } + + if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { + ret = ERROR_HTTP_DATA_INVLIAD; + srs_warn("http hook on_dvr validate failed, ignored. " + "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); + return ret; + } + + srs_trace("http hook on_dvr success. " + "client_id=%d, url=%s, request=%s, response=%s, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), ret); + + return ret; +} +#endif diff --git a/trunk/src/app/srs_app_http_hooks.hpp b/trunk/src/app/srs_app_http_hooks.hpp index b6c9ca6a2..c4286d13c 100644 --- a/trunk/src/app/srs_app_http_hooks.hpp +++ b/trunk/src/app/srs_app_http_hooks.hpp @@ -58,7 +58,6 @@ public: * @param client_id the id of client on server. * @param url the api server url, to valid the client. * ignore if empty. - * @return valid failed or connect to the url failed. */ static int on_connect(std::string url, int client_id, std::string ip, SrsRequest* req); /** @@ -73,7 +72,6 @@ public: * @param client_id the id of client on server. * @param url the api server url, to valid the client. * ignore if empty. - * @return valid failed or connect to the url failed. */ static int on_publish(std::string url, int client_id, std::string ip, SrsRequest* req); /** @@ -88,7 +86,6 @@ public: * @param client_id the id of client on server. * @param url the api server url, to valid the client. * ignore if empty. - * @return valid failed or connect to the url failed. */ static int on_play(std::string url, int client_id, std::string ip, SrsRequest* req); /** @@ -98,6 +95,15 @@ public: * ignore if empty. */ static void on_stop(std::string url, int client_id, std::string ip, SrsRequest* req); + /** + * on_dvr hook, when reap a dvr file. + * @param client_id the id of client on server. + * @param url the api server url, to process the event. + * ignore if empty. + * @param cwd the current work directory, used to resolve the reltive file path. + * @param file the file path, can be relative or absolute path. + */ + static int on_dvr(std::string url, int client_id, std::string ip, SrsRequest* req, std::string cwd, std::string file); }; #endif diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp index 029c0ace2..0c81446c1 100644 --- a/trunk/src/app/srs_app_rtmp_conn.cpp +++ b/trunk/src/app/srs_app_rtmp_conn.cpp @@ -135,6 +135,9 @@ int SrsRtmpConn::do_cycle() } srs_verbose("rtmp connect app success"); + // set client ip to request. + req->ip = ip; + // discovery vhost, resolve the vhost from config SrsConfDirective* parsed_vhost = _srs_config->get_vhost(req->vhost); if (parsed_vhost) { diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp index 98ad1273a..327630442 100644 --- a/trunk/src/core/srs_core.hpp +++ b/trunk/src/core/srs_core.hpp @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // current release version #define VERSION_MAJOR 2 #define VERSION_MINOR 0 -#define VERSION_REVISION 88 +#define VERSION_REVISION 89 // server info. #define RTMP_SIG_SRS_KEY "SRS" #define RTMP_SIG_SRS_ROLE "origin/edge server" diff --git a/trunk/src/rtmp/srs_protocol_rtmp.cpp b/trunk/src/rtmp/srs_protocol_rtmp.cpp index d1ab088b3..713910bf9 100644 --- a/trunk/src/rtmp/srs_protocol_rtmp.cpp +++ b/trunk/src/rtmp/srs_protocol_rtmp.cpp @@ -91,6 +91,7 @@ SrsRequest* SrsRequest::copy() { SrsRequest* cp = new SrsRequest(); + cp->ip = ip; cp->app = app; cp->objectEncoding = objectEncoding; cp->pageUrl = pageUrl; diff --git a/trunk/src/rtmp/srs_protocol_rtmp.hpp b/trunk/src/rtmp/srs_protocol_rtmp.hpp index a8de64d56..7451823f1 100644 --- a/trunk/src/rtmp/srs_protocol_rtmp.hpp +++ b/trunk/src/rtmp/srs_protocol_rtmp.hpp @@ -53,6 +53,9 @@ class IMergeReadHandler; */ class SrsRequest { +public: + // client ip. + std::string ip; public: /** * tcUrl: rtmp://request_vhost:port/app/stream