You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

679 lines
26KB

  1. /*
  2. * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
  3. *
  4. * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
  5. *
  6. * Use of this source code is governed by MIT license that can be found in the
  7. * LICENSE file in the root of the source tree. All contributing project authors
  8. * may be found in the AUTHORS file in the root of the source tree.
  9. */
  10. #include <sstream>
  11. #include "Util/logger.h"
  12. #include "Util/onceToken.h"
  13. #include "Util/NoticeCenter.h"
  14. #include "Common/config.h"
  15. #include "Common/MediaSource.h"
  16. #include "Http/HttpRequester.h"
  17. #include "Network/Session.h"
  18. #include "Rtsp/RtspSession.h"
  19. #include "Http/HttpSession.h"
  20. #include "WebHook.h"
  21. #include "WebApi.h"
  22. using namespace std;
  23. using namespace Json;
  24. using namespace toolkit;
  25. using namespace mediakit;
  26. namespace Hook {
  27. #define HOOK_FIELD "hook."
  28. const string kEnable = HOOK_FIELD"enable";
  29. const string kTimeoutSec = HOOK_FIELD"timeoutSec";
  30. const string kOnPublish = HOOK_FIELD"on_publish";
  31. const string kOnPlay = HOOK_FIELD"on_play";
  32. const string kOnFlowReport = HOOK_FIELD"on_flow_report";
  33. const string kOnRtspRealm = HOOK_FIELD"on_rtsp_realm";
  34. const string kOnRtspAuth = HOOK_FIELD"on_rtsp_auth";
  35. const string kOnStreamChanged = HOOK_FIELD"on_stream_changed";
  36. const string kOnStreamNotFound = HOOK_FIELD"on_stream_not_found";
  37. const string kOnRecordMp4 = HOOK_FIELD"on_record_mp4";
  38. const string kOnRecordTs = HOOK_FIELD"on_record_ts";
  39. const string kOnShellLogin = HOOK_FIELD"on_shell_login";
  40. const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader";
  41. const string kOnHttpAccess = HOOK_FIELD"on_http_access";
  42. const string kOnServerStarted = HOOK_FIELD"on_server_started";
  43. const string kOnServerKeepalive = HOOK_FIELD"on_server_keepalive";
  44. const string kOnSendRtpStopped = HOOK_FIELD"on_send_rtp_stopped";
  45. const string kOnRtpServerTimeout = HOOK_FIELD"on_rtp_server_timeout";
  46. const string kAdminParams = HOOK_FIELD"admin_params";
  47. const string kAliveInterval = HOOK_FIELD"alive_interval";
  48. const string kRetry = HOOK_FIELD"retry";
  49. const string kRetryDelay = HOOK_FIELD"retry_delay";
  50. onceToken token([](){
  51. mINI::Instance()[kEnable] = false;
  52. mINI::Instance()[kTimeoutSec] = 10;
  53. //默认hook地址设置为空,采用默认行为(例如不鉴权)
  54. mINI::Instance()[kOnPublish] = "";
  55. mINI::Instance()[kOnPlay] = "";
  56. mINI::Instance()[kOnFlowReport] = "";
  57. mINI::Instance()[kOnRtspRealm] = "";
  58. mINI::Instance()[kOnRtspAuth] = "";
  59. mINI::Instance()[kOnStreamChanged] = "";
  60. mINI::Instance()[kOnStreamNotFound] = "";
  61. mINI::Instance()[kOnRecordMp4] = "";
  62. mINI::Instance()[kOnRecordTs] = "";
  63. mINI::Instance()[kOnShellLogin] = "";
  64. mINI::Instance()[kOnStreamNoneReader] = "";
  65. mINI::Instance()[kOnHttpAccess] = "";
  66. mINI::Instance()[kOnServerStarted] = "";
  67. mINI::Instance()[kOnServerKeepalive] = "";
  68. mINI::Instance()[kOnSendRtpStopped] = "";
  69. mINI::Instance()[kOnRtpServerTimeout] = "";
  70. mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
  71. mINI::Instance()[kAliveInterval] = 30.0;
  72. mINI::Instance()[kRetry] = 1;
  73. mINI::Instance()[kRetryDelay] = 3.0;
  74. },nullptr);
  75. }//namespace Hook
  76. namespace Cluster {
  77. #define CLUSTER_FIELD "cluster."
  78. const string kOriginUrl = CLUSTER_FIELD "origin_url";
  79. const string kTimeoutSec = CLUSTER_FIELD "timeout_sec";
  80. const string kRetryCount = CLUSTER_FIELD "retry_count";
  81. static onceToken token([]() {
  82. mINI::Instance()[kOriginUrl] = "";
  83. mINI::Instance()[kTimeoutSec] = 15;
  84. mINI::Instance()[kRetryCount] = 3;
  85. });
  86. }//namespace Cluster
  87. static void parse_http_response(const SockException &ex, const Parser &res,
  88. const function<void(const Value &,const string &)> &fun){
  89. if (ex) {
  90. auto errStr = StrPrinter << "[network err]:" << ex.what() << endl;
  91. fun(Json::nullValue, errStr);
  92. return;
  93. }
  94. if (res.Url() != "200") {
  95. auto errStr = StrPrinter << "[bad http status code]:" << res.Url() << endl;
  96. fun(Json::nullValue, errStr);
  97. return;
  98. }
  99. Value result;
  100. try {
  101. stringstream ss(res.Content());
  102. ss >> result;
  103. } catch (std::exception &ex) {
  104. auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl;
  105. fun(Json::nullValue, errStr);
  106. return;
  107. }
  108. if (result["code"].asInt() != 0) {
  109. auto errStr = StrPrinter << "[json code]:" << "code=" << result["code"] << ",msg=" << result["msg"] << endl;
  110. fun(Json::nullValue, errStr);
  111. return;
  112. }
  113. try {
  114. fun(result, "");
  115. } catch (std::exception &ex) {
  116. auto errStr = StrPrinter << "[do hook invoker failed]:" << ex.what() << endl;
  117. //如果还是抛异常,那么再上抛异常
  118. fun(Json::nullValue, errStr);
  119. }
  120. }
  121. string to_string(const Value &value){
  122. return value.toStyledString();
  123. }
  124. string to_string(const HttpArgs &value){
  125. return value.make();
  126. }
  127. const char *getContentType(const Value &value){
  128. return "application/json";
  129. }
  130. const char *getContentType(const HttpArgs &value){
  131. return "application/x-www-form-urlencoded";
  132. }
  133. string getVhost(const Value &value) {
  134. const char *key = VHOST_KEY;
  135. auto val = value.find(key, key + sizeof(VHOST_KEY) - 1);
  136. return val ? val->asString() : "";
  137. }
  138. string getVhost(const HttpArgs &value) {
  139. auto val = value.find(VHOST_KEY);
  140. return val != value.end() ? val->second : "";
  141. }
  142. void do_http_hook(const string &url, const ArgsType &body, const function<void(const Value &, const string &)> &func, uint32_t retry) {
  143. GET_CONFIG(string, mediaServerId, General::kMediaServerId);
  144. GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
  145. GET_CONFIG(float, retry_delay, Hook::kRetryDelay);
  146. const_cast<ArgsType &>(body)["mediaServerId"] = mediaServerId;
  147. HttpRequester::Ptr requester(new HttpRequester);
  148. requester->setMethod("POST");
  149. auto bodyStr = to_string(body);
  150. requester->setBody(bodyStr);
  151. requester->addHeader("Content-Type", getContentType(body));
  152. auto vhost = getVhost(body);
  153. if (!vhost.empty()) {
  154. requester->addHeader("X-VHOST", vhost);
  155. }
  156. Ticker ticker;
  157. requester->startRequester(url, [url, func, bodyStr, body, requester, ticker, retry](const SockException &ex, const Parser &res) mutable {
  158. onceToken token(nullptr, [&]() mutable { requester.reset(); });
  159. parse_http_response(ex, res, [&](const Value &obj, const string &err) {
  160. if (!err.empty()) {
  161. // hook失败
  162. WarnL << "hook " << url << " " << ticker.elapsedTime() << "ms,failed" << err << ":" << bodyStr;
  163. if (retry-- > 0) {
  164. requester->getPoller()->doDelayTask(MAX(retry_delay, 0.0) * 1000, [url, body, func, retry] {
  165. do_http_hook(url, body, func, retry);
  166. return 0;
  167. });
  168. //重试不需要触发回调
  169. return;
  170. }
  171. } else if (ticker.elapsedTime() > 500) {
  172. //hook成功,但是hook响应超过500ms,打印警告日志
  173. DebugL << "hook " << url << " " << ticker.elapsedTime() << "ms,success:" << bodyStr;
  174. }
  175. if (func) {
  176. func(obj, err);
  177. }
  178. });
  179. }, hook_timeoutSec);
  180. }
  181. void do_http_hook(const string &url, const ArgsType &body, const function<void(const Value &, const string &)> &func) {
  182. GET_CONFIG(uint32_t, hook_retry, Hook::kRetry);
  183. do_http_hook(url, body, func, hook_retry);
  184. }
  185. static ArgsType make_json(const MediaInfo &args){
  186. ArgsType body;
  187. body["schema"] = args._schema;
  188. body[VHOST_KEY] = args._vhost;
  189. body["app"] = args._app;
  190. body["stream"] = args._streamid;
  191. body["params"] = args._param_strs;
  192. return body;
  193. }
  194. static void reportServerStarted(){
  195. GET_CONFIG(bool,hook_enable,Hook::kEnable);
  196. GET_CONFIG(string,hook_server_started,Hook::kOnServerStarted);
  197. if(!hook_enable || hook_server_started.empty()){
  198. return;
  199. }
  200. ArgsType body;
  201. for (auto &pr : mINI::Instance()) {
  202. body[pr.first] = (string &) pr.second;
  203. }
  204. //执行hook
  205. do_http_hook(hook_server_started,body, nullptr);
  206. }
  207. // 服务器定时保活定时器
  208. static Timer::Ptr g_keepalive_timer;
  209. static void reportServerKeepalive() {
  210. GET_CONFIG(bool, hook_enable, Hook::kEnable);
  211. GET_CONFIG(string, hook_server_keepalive, Hook::kOnServerKeepalive);
  212. if (!hook_enable || hook_server_keepalive.empty()) {
  213. return;
  214. }
  215. GET_CONFIG(float, alive_interval, Hook::kAliveInterval);
  216. g_keepalive_timer = std::make_shared<Timer>(alive_interval, []() {
  217. getStatisticJson([](const Value &data) mutable {
  218. ArgsType body;
  219. body["data"] = data;
  220. //执行hook
  221. do_http_hook(hook_server_keepalive, body, nullptr);
  222. });
  223. return true;
  224. }, nullptr);
  225. }
  226. static const string kEdgeServerParam = "edge=1";
  227. static string getPullUrl(const string &origin_fmt, const MediaInfo &info) {
  228. char url[1024] = { 0 };
  229. if ((ssize_t)origin_fmt.size() > snprintf(url, sizeof(url), origin_fmt.data(), info._app.data(), info._streamid.data())) {
  230. WarnL << "get origin url failed, origin_fmt:" << origin_fmt;
  231. return "";
  232. }
  233. //告知源站这是来自边沿站的拉流请求,如果未找到流请立即返回拉流失败
  234. return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info._vhost + '&' + info._param_strs;
  235. }
  236. static void pullStreamFromOrigin(const vector<string>& urls, size_t index, size_t failed_cnt, const MediaInfo &args,
  237. const function<void()> &closePlayer) {
  238. GET_CONFIG(float, cluster_timeout_sec, Cluster::kTimeoutSec);
  239. GET_CONFIG(int, retry_count, Cluster::kRetryCount);
  240. auto url = getPullUrl(urls[index % urls.size()], args);
  241. auto timeout_sec = cluster_timeout_sec / urls.size();
  242. InfoL << "pull stream from origin, failed_cnt: " << failed_cnt << ", timeout_sec: " << timeout_sec << ", url: " << url;
  243. ProtocolOption option;
  244. option.enable_hls = option.enable_hls || (args._schema == HLS_SCHEMA);
  245. option.enable_mp4 = false;
  246. addStreamProxy(args._vhost, args._app, args._streamid, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec,
  247. [=](const SockException &ex, const string &key) mutable {
  248. if (!ex) {
  249. return;
  250. }
  251. //拉流失败
  252. if (++failed_cnt == urls.size()) {
  253. //已经重试所有源站了
  254. WarnL << "pull stream from origin final failed: " << url;
  255. closePlayer();
  256. return;
  257. }
  258. pullStreamFromOrigin(urls, index + 1, failed_cnt, args, closePlayer);
  259. });
  260. }
  261. static void *web_hook_tag = nullptr;
  262. static mINI jsonToMini(const Value &obj) {
  263. mINI ret;
  264. if (obj.isObject()) {
  265. for (auto it = obj.begin(); it != obj.end(); ++it) {
  266. try {
  267. auto str = (*it).asString();
  268. ret[it.name()] = std::move(str);
  269. } catch (std::exception &) {
  270. WarnL << "Json is not convertible to string, key: " << it.name() << ", value: " << (*it);
  271. }
  272. }
  273. }
  274. return ret;
  275. }
  276. void installWebHook(){
  277. GET_CONFIG(bool,hook_enable,Hook::kEnable);
  278. GET_CONFIG(string,hook_adminparams,Hook::kAdminParams);
  279. NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
  280. GET_CONFIG(string,hook_publish,Hook::kOnPublish);
  281. if (!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
  282. invoker("", ProtocolOption());
  283. return;
  284. }
  285. //异步执行该hook api,防止阻塞NoticeCenter
  286. auto body = make_json(args);
  287. body["ip"] = sender.get_peer_ip();
  288. body["port"] = sender.get_peer_port();
  289. body["id"] = sender.getIdentifier();
  290. body["originType"] = (int) type;
  291. body["originTypeStr"] = getOriginTypeString(type);
  292. //执行hook
  293. do_http_hook(hook_publish, body, [invoker](const Value &obj, const string &err) mutable {
  294. if (err.empty()) {
  295. //推流鉴权成功
  296. invoker(err, ProtocolOption(jsonToMini(obj)));
  297. } else {
  298. //推流鉴权失败
  299. invoker(err, ProtocolOption());
  300. }
  301. });
  302. });
  303. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastMediaPlayed,[](BroadcastMediaPlayedArgs){
  304. GET_CONFIG(string,hook_play,Hook::kOnPlay);
  305. if(!hook_enable || args._param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1"){
  306. invoker("");
  307. return;
  308. }
  309. auto body = make_json(args);
  310. body["ip"] = sender.get_peer_ip();
  311. body["port"] = sender.get_peer_port();
  312. body["id"] = sender.getIdentifier();
  313. //执行hook
  314. do_http_hook(hook_play,body,[invoker](const Value &obj,const string &err){
  315. invoker(err);
  316. });
  317. });
  318. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){
  319. GET_CONFIG(string,hook_flowreport,Hook::kOnFlowReport);
  320. if(!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1"){
  321. return;
  322. }
  323. auto body = make_json(args);
  324. body["totalBytes"] = (Json::UInt64)totalBytes;
  325. body["duration"] = (Json::UInt64)totalDuration;
  326. body["player"] = isPlayer;
  327. body["ip"] = sender.get_peer_ip();
  328. body["port"] = sender.get_peer_port();
  329. body["id"] = sender.getIdentifier();
  330. //执行hook
  331. do_http_hook(hook_flowreport,body, nullptr);
  332. });
  333. static const string unAuthedRealm = "unAuthedRealm";
  334. //监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
  335. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastOnGetRtspRealm,[](BroadcastOnGetRtspRealmArgs){
  336. GET_CONFIG(string,hook_rtsp_realm,Hook::kOnRtspRealm);
  337. if(!hook_enable || args._param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1"){
  338. //无需认证
  339. invoker("");
  340. return;
  341. }
  342. auto body = make_json(args);
  343. body["ip"] = sender.get_peer_ip();
  344. body["port"] = sender.get_peer_port();
  345. body["id"] = sender.getIdentifier();
  346. //执行hook
  347. do_http_hook(hook_rtsp_realm,body, [invoker](const Value &obj,const string &err){
  348. if(!err.empty()){
  349. //如果接口访问失败,那么该rtsp流认证失败
  350. invoker(unAuthedRealm);
  351. return;
  352. }
  353. invoker(obj["realm"].asString());
  354. });
  355. });
  356. //监听kBroadcastOnRtspAuth事件返回正确的rtsp鉴权用户密码
  357. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastOnRtspAuth,[](BroadcastOnRtspAuthArgs){
  358. GET_CONFIG(string,hook_rtsp_auth,Hook::kOnRtspAuth);
  359. if(unAuthedRealm == realm || !hook_enable || hook_rtsp_auth.empty()){
  360. //认证失败
  361. invoker(false,makeRandStr(12));
  362. return;
  363. }
  364. auto body = make_json(args);
  365. body["ip"] = sender.get_peer_ip();
  366. body["port"] = sender.get_peer_port();
  367. body["id"] = sender.getIdentifier();
  368. body["user_name"] = user_name;
  369. body["must_no_encrypt"] = must_no_encrypt;
  370. body["realm"] = realm;
  371. //执行hook
  372. do_http_hook(hook_rtsp_auth,body, [invoker](const Value &obj,const string &err){
  373. if(!err.empty()){
  374. //认证失败
  375. invoker(false,makeRandStr(12));
  376. return;
  377. }
  378. invoker(obj["encrypted"].asBool(),obj["passwd"].asString());
  379. });
  380. });
  381. //监听rtsp、rtmp源注册或注销事件
  382. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastMediaChanged,[](BroadcastMediaChangedArgs){
  383. GET_CONFIG(string,hook_stream_chaned,Hook::kOnStreamChanged);
  384. if(!hook_enable || hook_stream_chaned.empty()){
  385. return;
  386. }
  387. ArgsType body;
  388. if (bRegist) {
  389. body = makeMediaSourceJson(sender);
  390. body["regist"] = bRegist;
  391. } else {
  392. body["schema"] = sender.getSchema();
  393. body[VHOST_KEY] = sender.getVhost();
  394. body["app"] = sender.getApp();
  395. body["stream"] = sender.getId();
  396. body["regist"] = bRegist;
  397. }
  398. //执行hook
  399. do_http_hook(hook_stream_chaned,body, nullptr);
  400. });
  401. GET_CONFIG_FUNC(vector<string>, origin_urls, Cluster::kOriginUrl, [](const string &str) {
  402. vector<string> ret;
  403. for (auto &url : split(str, ";")) {
  404. trim(url);
  405. if (!url.empty()) {
  406. ret.emplace_back(url);
  407. }
  408. }
  409. return ret;
  410. });
  411. //监听播放失败(未找到特定的流)事件
  412. NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastNotFoundStream, [](BroadcastNotFoundStreamArgs) {
  413. if (!origin_urls.empty()) {
  414. //设置了源站,那么尝试溯源
  415. static atomic<uint8_t> s_index { 0 };
  416. pullStreamFromOrigin(origin_urls, s_index.load(), 0, args, closePlayer);
  417. ++s_index;
  418. return;
  419. }
  420. if (start_with(args._param_strs, kEdgeServerParam)) {
  421. //源站收到来自边沿站的溯源请求,流不存在时立即返回拉流失败
  422. closePlayer();
  423. return;
  424. }
  425. GET_CONFIG(string, hook_stream_not_found, Hook::kOnStreamNotFound);
  426. if (!hook_enable || hook_stream_not_found.empty()) {
  427. return;
  428. }
  429. auto body = make_json(args);
  430. body["ip"] = sender.get_peer_ip();
  431. body["port"] = sender.get_peer_port();
  432. body["id"] = sender.getIdentifier();
  433. // Hook回复立即关闭流
  434. auto res_cb = [closePlayer](const Value &res, const string &err) {
  435. bool flag = res["close"].asBool();
  436. if (flag) {
  437. closePlayer();
  438. }
  439. };
  440. //执行hook
  441. do_http_hook(hook_stream_not_found, body, res_cb);
  442. });
  443. static auto getRecordInfo = [](const RecordInfo &info) {
  444. ArgsType body;
  445. body["start_time"] = (Json::UInt64) info.start_time;
  446. body["file_size"] = (Json::UInt64) info.file_size;
  447. body["time_len"] = info.time_len;
  448. body["file_path"] = info.file_path;
  449. body["file_name"] = info.file_name;
  450. body["folder"] = info.folder;
  451. body["url"] = info.url;
  452. body["app"] = info.app;
  453. body["stream"] = info.stream;
  454. body[VHOST_KEY] = info.vhost;
  455. return body;
  456. };
  457. #ifdef ENABLE_MP4
  458. //录制mp4文件成功后广播
  459. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
  460. GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4);
  461. if (!hook_enable || hook_record_mp4.empty()) {
  462. return;
  463. }
  464. //执行hook
  465. do_http_hook(hook_record_mp4, getRecordInfo(info), nullptr);
  466. });
  467. #endif //ENABLE_MP4
  468. NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastRecordTs, [](BroadcastRecordTsArgs) {
  469. GET_CONFIG(string,hook_record_ts,Hook::kOnRecordTs);
  470. if (!hook_enable || hook_record_ts.empty()) {
  471. return;
  472. }
  473. // 执行 hook
  474. do_http_hook(hook_record_ts, getRecordInfo(info), nullptr);
  475. });
  476. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){
  477. GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin);
  478. if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){
  479. invoker("");
  480. return;
  481. }
  482. ArgsType body;
  483. body["ip"] = sender.get_peer_ip();
  484. body["port"] = sender.get_peer_port();
  485. body["id"] = sender.getIdentifier();
  486. body["user_name"] = user_name;
  487. body["passwd"] = passwd;
  488. //执行hook
  489. do_http_hook(hook_shell_login,body, [invoker](const Value &,const string &err){
  490. invoker(err);
  491. });
  492. });
  493. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastStreamNoneReader,[](BroadcastStreamNoneReaderArgs) {
  494. if (!origin_urls.empty()) {
  495. //边沿站无人观看时立即停止溯源
  496. sender.close(false);
  497. WarnL << "无人观看主动关闭流:" << sender.getOriginUrl();
  498. return;
  499. }
  500. GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader);
  501. if(!hook_enable || hook_stream_none_reader.empty()){
  502. return;
  503. }
  504. ArgsType body;
  505. body["schema"] = sender.getSchema();
  506. body[VHOST_KEY] = sender.getVhost();
  507. body["app"] = sender.getApp();
  508. body["stream"] = sender.getId();
  509. weak_ptr<MediaSource> weakSrc = sender.shared_from_this();
  510. //执行hook
  511. do_http_hook(hook_stream_none_reader,body, [weakSrc](const Value &obj,const string &err){
  512. bool flag = obj["close"].asBool();
  513. auto strongSrc = weakSrc.lock();
  514. if(!flag || !err.empty() || !strongSrc){
  515. return;
  516. }
  517. strongSrc->close(false);
  518. WarnL << "无人观看主动关闭流:" << strongSrc->getOriginUrl();
  519. });
  520. });
  521. NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) {
  522. GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped);
  523. if (!hook_enable || hook_send_rtp_stopped.empty()) {
  524. return;
  525. }
  526. ArgsType body;
  527. body[VHOST_KEY] = sender.getVhost();
  528. body["app"] = sender.getApp();
  529. body["stream"] = sender.getStreamId();
  530. body["ssrc"] = ssrc;
  531. body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource());
  532. body["originTypeStr"] = getOriginTypeString(sender.getOriginType(MediaSource::NullMediaSource()));
  533. body["originUrl"] = sender.getOriginUrl(MediaSource::NullMediaSource());
  534. body["msg"] = ex.what();
  535. body["err"] = ex.getErrCode();
  536. //执行hook
  537. do_http_hook(hook_send_rtp_stopped, body, nullptr);
  538. });
  539. /**
  540. * kBroadcastHttpAccess事件触发机制
  541. * 1、根据http请求头查找cookie,找到进入步骤3
  542. * 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5
  543. * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
  544. * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
  545. * 5、触发kBroadcastHttpAccess事件
  546. */
  547. //开发者应该通过该事件判定http客户端是否有权限访问http服务器上的特定文件
  548. //ZLMediaKit会记录本次鉴权的结果至cookie
  549. //如果鉴权成功,在cookie有效期内,那么下次客户端再访问授权目录时,ZLMediaKit会直接返回文件
  550. //如果鉴权失败,在cookie有效期内,如果http url参数不变(否则会立即再次触发鉴权事件),ZLMediaKit会直接返回错误码
  551. //如果用户客户端不支持cookie,那么ZLMediaKit会根据url参数查找cookie并追踪用户,
  552. //如果没有url参数,客户端又不支持cookie,那么会根据ip和端口追踪用户
  553. //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
  554. NoticeCenter::Instance().addListener(&web_hook_tag,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
  555. GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
  556. if(sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams){
  557. //如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时
  558. invoker("","",60 * 60);
  559. return;
  560. }
  561. if(!hook_enable || hook_http_access.empty()){
  562. //未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
  563. //因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
  564. invoker("","",0);
  565. return;
  566. }
  567. ArgsType body;
  568. body["ip"] = sender.get_peer_ip();
  569. body["port"] = sender.get_peer_port();
  570. body["id"] = sender.getIdentifier();
  571. body["path"] = path;
  572. body["is_dir"] = is_dir;
  573. body["params"] = parser.Params();
  574. for(auto &pr : parser.getHeader()){
  575. body[string("header.") + pr.first] = pr.second;
  576. }
  577. //执行hook
  578. do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){
  579. if(!err.empty()){
  580. //如果接口访问失败,那么仅限本次没有访问http服务器的权限
  581. invoker(err,"",0);
  582. return;
  583. }
  584. //err参数代表不能访问的原因,空则代表可以访问
  585. //path参数是该客户端能访问或被禁止的顶端目录,如果path为空字符串,则表述为当前目录
  586. //second参数规定该cookie超时时间,如果second为0,本次鉴权结果不缓存
  587. invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt());
  588. });
  589. });
  590. NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) {
  591. GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout);
  592. if (!hook_enable || rtp_server_timeout.empty()) {
  593. return;
  594. }
  595. ArgsType body;
  596. body["local_port"] = local_port;
  597. body["stream_id"] = stream_id;
  598. body["tcp_mode"] = tcp_mode;
  599. body["re_use_port"] = re_use_port;
  600. body["ssrc"] = ssrc;
  601. do_http_hook(rtp_server_timeout, body);
  602. });
  603. //汇报服务器重新启动
  604. reportServerStarted();
  605. //定时上报保活
  606. reportServerKeepalive();
  607. }
  608. void unInstallWebHook(){
  609. g_keepalive_timer.reset();
  610. NoticeCenter::Instance().delListener(&web_hook_tag);
  611. }