mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
完善Hook与API
This commit is contained in:
parent
2f6773f180
commit
74d074ac53
@ -13,14 +13,19 @@
|
|||||||
#include "Http/HttpRequester.h"
|
#include "Http/HttpRequester.h"
|
||||||
#include "Http/HttpSession.h"
|
#include "Http/HttpSession.h"
|
||||||
#include "Network/TcpServer.h"
|
#include "Network/TcpServer.h"
|
||||||
|
#include "Player/PlayerProxy.h"
|
||||||
|
|
||||||
using namespace Json;
|
using namespace Json;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
using namespace mediakit;
|
using namespace mediakit;
|
||||||
|
|
||||||
|
|
||||||
|
typedef map<string,variant,StrCaseCompare> ApiArgsType;
|
||||||
|
|
||||||
|
|
||||||
#define API_ARGS HttpSession::KeyValue &headerIn, \
|
#define API_ARGS HttpSession::KeyValue &headerIn, \
|
||||||
HttpSession::KeyValue &headerOut, \
|
HttpSession::KeyValue &headerOut, \
|
||||||
HttpSession::KeyValue &allArgs, \
|
ApiArgsType &allArgs, \
|
||||||
Json::Value &val
|
Json::Value &val
|
||||||
|
|
||||||
#define API_REGIST(field, name, ...) \
|
#define API_REGIST(field, name, ...) \
|
||||||
@ -49,7 +54,7 @@ typedef enum {
|
|||||||
#define API_FIELD "api."
|
#define API_FIELD "api."
|
||||||
const char kApiDebug[] = API_FIELD"apiDebug";
|
const char kApiDebug[] = API_FIELD"apiDebug";
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
mINI::Instance()[kApiDebug] = "0";
|
mINI::Instance()[kApiDebug] = "1";
|
||||||
});
|
});
|
||||||
}//namespace API
|
}//namespace API
|
||||||
|
|
||||||
@ -72,20 +77,34 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
//获取HTTP请求中url参数、content参数
|
//获取HTTP请求中url参数、content参数
|
||||||
static HttpSession::KeyValue getAllArgs(const Parser &parser) {
|
static ApiArgsType getAllArgs(const Parser &parser) {
|
||||||
HttpSession::KeyValue allArgs;
|
ApiArgsType allArgs;
|
||||||
{
|
if(parser["Content-Type"].find("application/x-www-form-urlencoded") == 0){
|
||||||
//TraceL << parser.FullUrl() << "\r\n" << parser.Content();
|
|
||||||
auto &urlArgs = parser.getUrlArgs();
|
|
||||||
auto contentArgs = parser.parseArgs(parser.Content());
|
auto contentArgs = parser.parseArgs(parser.Content());
|
||||||
for (auto &pr : contentArgs) {
|
for (auto &pr : contentArgs) {
|
||||||
allArgs.emplace(pr.first, HttpSession::urlDecode(pr.second));
|
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
||||||
}
|
}
|
||||||
for (auto &pr : urlArgs) {
|
}else if(parser["Content-Type"].find("application/json") == 0){
|
||||||
allArgs.emplace(pr.first, HttpSession::urlDecode(pr.second));
|
try {
|
||||||
|
stringstream ss(parser.Content());
|
||||||
|
Value jsonArgs;
|
||||||
|
ss >> jsonArgs;
|
||||||
|
auto keys = jsonArgs.getMemberNames();
|
||||||
|
for (auto key = keys.begin(); key != keys.end(); ++key){
|
||||||
|
allArgs[*key] = jsonArgs[*key].asString();
|
||||||
|
}
|
||||||
|
}catch (std::exception &ex){
|
||||||
|
WarnL << ex.what();
|
||||||
}
|
}
|
||||||
|
}else if(!parser["Content-Type"].empty()){
|
||||||
|
WarnL << "invalid Content-Type:" << parser["Content-Type"];
|
||||||
}
|
}
|
||||||
return allArgs;
|
|
||||||
|
auto &urlArgs = parser.getUrlArgs();
|
||||||
|
for (auto &pr : urlArgs) {
|
||||||
|
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
||||||
|
}
|
||||||
|
return std::move(allArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void addHttpListener(){
|
static inline void addHttpListener(){
|
||||||
@ -107,7 +126,7 @@ static inline void addHttpListener(){
|
|||||||
val["code"] = API::Success;
|
val["code"] = API::Success;
|
||||||
HttpSession::KeyValue &headerIn = parser.getValues();
|
HttpSession::KeyValue &headerIn = parser.getValues();
|
||||||
HttpSession::KeyValue headerOut;
|
HttpSession::KeyValue headerOut;
|
||||||
HttpSession::KeyValue allArgs = getAllArgs(parser);
|
auto allArgs = getAllArgs(parser);
|
||||||
headerOut["Content-Type"] = "application/json; charset=utf-8";
|
headerOut["Content-Type"] = "application/json; charset=utf-8";
|
||||||
if(api_debug){
|
if(api_debug){
|
||||||
auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
|
auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
|
||||||
@ -115,13 +134,13 @@ static inline void addHttpListener(){
|
|||||||
const string &contentOut){
|
const string &contentOut){
|
||||||
stringstream ss;
|
stringstream ss;
|
||||||
for(auto &pr : allArgs ){
|
for(auto &pr : allArgs ){
|
||||||
ss << pr.first << " : " << pr.second << "\r\n";
|
ss << pr.first << " : " << (string)pr.second << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugL << "request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
||||||
<< "content:\r\n" << parser.Content() << "\r\n"
|
<< "# content:\r\n" << parser.Content() << "\r\n"
|
||||||
<< "args:\r\n" << ss.str()
|
<< "# args:\r\n" << ss.str()
|
||||||
<< "response:\r\n"
|
<< "# response:\r\n"
|
||||||
<< contentOut << "\r\n";
|
<< contentOut << "\r\n";
|
||||||
|
|
||||||
invoker(codeOut,headerOut,contentOut);
|
invoker(codeOut,headerOut,contentOut);
|
||||||
@ -263,7 +282,7 @@ void installWebApi() {
|
|||||||
item["vhost"] = vhost;
|
item["vhost"] = vhost;
|
||||||
item["app"] = app;
|
item["app"] = app;
|
||||||
item["stream"] = stream;
|
item["stream"] = stream;
|
||||||
val["data"]["array"].append(item);
|
val["data"].append(item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,6 +322,33 @@ void installWebApi() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
static unordered_map<uint64_t ,PlayerProxy::Ptr> s_proxyMap;
|
||||||
|
static recursive_mutex s_proxyMapMtx;
|
||||||
|
API_REGIST(api,addStreamProxy,{
|
||||||
|
//添加拉流代理
|
||||||
|
PlayerProxy::Ptr player(new PlayerProxy(
|
||||||
|
allArgs["vhost"],
|
||||||
|
allArgs["app"],
|
||||||
|
allArgs["stream"],
|
||||||
|
allArgs["enable_hls"],
|
||||||
|
allArgs["enable_mp4"]
|
||||||
|
));
|
||||||
|
//指定RTP over TCP(播放rtsp时有效)
|
||||||
|
(*player)[kRtpType] = allArgs["rtp_type"].as<int>();
|
||||||
|
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试
|
||||||
|
player->play(allArgs["url"]);
|
||||||
|
|
||||||
|
val["data"]["id"] = player.get();
|
||||||
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||||
|
s_proxyMap[(uint64_t)player.get()] = player;
|
||||||
|
});
|
||||||
|
|
||||||
|
API_REGIST(api,delStreamProxy,{
|
||||||
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||||
|
val["data"]["flag"] = s_proxyMap.erase(allArgs["id"].as<uint64_t>()) == 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
////////////以下是注册的Hook API////////////
|
////////////以下是注册的Hook API////////////
|
||||||
API_REGIST(hook,on_publish,{
|
API_REGIST(hook,on_publish,{
|
||||||
//开始推流事件
|
//开始推流事件
|
||||||
@ -321,4 +367,40 @@ void installWebApi() {
|
|||||||
val["code"] = 0;
|
val["code"] = 0;
|
||||||
val["msg"] = "success";
|
val["msg"] = "success";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
API_REGIST(hook,on_rtsp_realm,{
|
||||||
|
//rtsp是否需要鉴权
|
||||||
|
val["code"] = 0;
|
||||||
|
val["realm"] = "zlmediakit_reaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
API_REGIST(hook,on_rtsp_auth,{
|
||||||
|
//rtsp鉴权密码,密码等于用户名
|
||||||
|
//rtsp可以有双重鉴权!后面还会触发on_play事件
|
||||||
|
val["code"] = 0;
|
||||||
|
val["encrypted"] = false;
|
||||||
|
val["passwd"] = allArgs["user_name"];
|
||||||
|
});
|
||||||
|
|
||||||
|
API_REGIST(hook,on_stream_changed,{
|
||||||
|
//媒体注册或反注册事件
|
||||||
|
val["code"] = 0;
|
||||||
|
val["msg"] = "success";
|
||||||
|
});
|
||||||
|
|
||||||
|
API_REGIST(hook,on_stream_not_found,{
|
||||||
|
//媒体未找到事件
|
||||||
|
val["code"] = 0;
|
||||||
|
val["msg"] = "success";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
API_REGIST(hook,on_record_mp4,{
|
||||||
|
//录制mp4分片完毕事件
|
||||||
|
val["code"] = 0;
|
||||||
|
val["msg"] = "success";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -11,11 +11,23 @@
|
|||||||
#include "Common/MediaSource.h"
|
#include "Common/MediaSource.h"
|
||||||
#include "Http/HttpRequester.h"
|
#include "Http/HttpRequester.h"
|
||||||
#include "Network/TcpSession.h"
|
#include "Network/TcpSession.h"
|
||||||
|
#include "Rtsp/RtspSession.h"
|
||||||
|
|
||||||
using namespace Json;
|
using namespace Json;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
using namespace mediakit;
|
using namespace mediakit;
|
||||||
|
|
||||||
|
|
||||||
|
//支持json或urlencoded方式传输参数
|
||||||
|
#define JSON_ARGS
|
||||||
|
|
||||||
|
#ifdef JSON_ARGS
|
||||||
|
typedef Value ArgsType;
|
||||||
|
#else
|
||||||
|
typedef HttpArgs ArgsType;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace Hook {
|
namespace Hook {
|
||||||
#define HOOK_FIELD "hook."
|
#define HOOK_FIELD "hook."
|
||||||
|
|
||||||
@ -24,6 +36,11 @@ const char kTimeoutSec[] = HOOK_FIELD"timeoutSec";
|
|||||||
const char kOnPublish[] = HOOK_FIELD"on_publish";
|
const char kOnPublish[] = HOOK_FIELD"on_publish";
|
||||||
const char kOnPlay[] = HOOK_FIELD"on_play";
|
const char kOnPlay[] = HOOK_FIELD"on_play";
|
||||||
const char kOnFlowReport[] = HOOK_FIELD"on_flow_report";
|
const char kOnFlowReport[] = HOOK_FIELD"on_flow_report";
|
||||||
|
const char kOnRtspRealm[] = HOOK_FIELD"on_rtsp_realm";
|
||||||
|
const char kOnRtspAuth[] = HOOK_FIELD"on_rtsp_auth";
|
||||||
|
const char kOnStreamChanged[] = HOOK_FIELD"on_stream_changed";
|
||||||
|
const char kOnStreamNotFound[] = HOOK_FIELD"on_stream_not_found";
|
||||||
|
const char kOnRecordMp4[] = HOOK_FIELD"on_record_mp4";
|
||||||
const char kAdminParams[] = HOOK_FIELD"admin_params";
|
const char kAdminParams[] = HOOK_FIELD"admin_params";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
@ -32,6 +49,11 @@ onceToken token([](){
|
|||||||
mINI::Instance()[kOnPublish] = "http://127.0.0.1/index/hook/on_publish";
|
mINI::Instance()[kOnPublish] = "http://127.0.0.1/index/hook/on_publish";
|
||||||
mINI::Instance()[kOnPlay] = "http://127.0.0.1/index/hook/on_play";
|
mINI::Instance()[kOnPlay] = "http://127.0.0.1/index/hook/on_play";
|
||||||
mINI::Instance()[kOnFlowReport] = "http://127.0.0.1/index/hook/on_flow_report";
|
mINI::Instance()[kOnFlowReport] = "http://127.0.0.1/index/hook/on_flow_report";
|
||||||
|
mINI::Instance()[kOnRtspRealm] = "http://127.0.0.1/index/hook/on_rtsp_realm";
|
||||||
|
mINI::Instance()[kOnRtspAuth] = "http://127.0.0.1/index/hook/on_rtsp_auth";
|
||||||
|
mINI::Instance()[kOnStreamChanged] = "http://127.0.0.1/index/hook/on_stream_changed";
|
||||||
|
mINI::Instance()[kOnStreamNotFound] = "http://127.0.0.1/index/hook/on_stream_not_found";
|
||||||
|
mINI::Instance()[kOnRecordMp4] = "http://127.0.0.1/index/hook/on_record_mp4";
|
||||||
mINI::Instance()[kAdminParams] = "token=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
mINI::Instance()[kAdminParams] = "token=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
||||||
},nullptr);
|
},nullptr);
|
||||||
}//namespace Hook
|
}//namespace Hook
|
||||||
@ -68,14 +90,31 @@ static void parse_http_response(const SockException &ex,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_http_hook(const string &url,const Value &body,const function<void(const Value &,const string &)> &fun){
|
string to_string(const Value &value){
|
||||||
|
return value.toStyledString();
|
||||||
|
}
|
||||||
|
|
||||||
|
string to_string(const HttpArgs &value){
|
||||||
|
return value.make();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *getContentType(const Value &value){
|
||||||
|
return "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *getContentType(const HttpArgs &value){
|
||||||
|
return "application/x-www-form-urlencoded";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_http_hook(const string &url,const ArgsType &body,const function<void(const Value &,const string &)> &fun){
|
||||||
GET_CONFIG_AND_REGISTER(float,hook_timeoutSec,Hook::kTimeoutSec);
|
GET_CONFIG_AND_REGISTER(float,hook_timeoutSec,Hook::kTimeoutSec);
|
||||||
HttpRequester::Ptr requester(new HttpRequester);
|
HttpRequester::Ptr requester(new HttpRequester);
|
||||||
requester->setMethod("POST");
|
requester->setMethod("POST");
|
||||||
requester->setBody(body.toStyledString());
|
auto bodyStr = to_string(body);
|
||||||
requester->addHeader("Content-Type","application/json; charset=utf-8");
|
requester->setBody(bodyStr);
|
||||||
|
requester->addHeader("Content-Type",getContentType(body));
|
||||||
std::shared_ptr<Ticker> pTicker(new Ticker);
|
std::shared_ptr<Ticker> pTicker(new Ticker);
|
||||||
requester->startRequester(url,[url,fun,body,requester,pTicker](const SockException &ex,
|
requester->startRequester(url,[url,fun,bodyStr,requester,pTicker](const SockException &ex,
|
||||||
const string &status,
|
const string &status,
|
||||||
const HttpClient::HttpHeader &header,
|
const HttpClient::HttpHeader &header,
|
||||||
const string &strRecvBody){
|
const string &strRecvBody){
|
||||||
@ -87,22 +126,22 @@ static void do_http_hook(const string &url,const Value &body,const function<void
|
|||||||
fun(obj,err);
|
fun(obj,err);
|
||||||
}
|
}
|
||||||
if(!err.empty()) {
|
if(!err.empty()) {
|
||||||
WarnL << "hook " << url << " " <<pTicker->elapsedTime() << "ms,failed" << err << ":" << body;
|
WarnL << "hook " << url << " " <<pTicker->elapsedTime() << "ms,failed" << err << ":" << bodyStr;
|
||||||
}else if(pTicker->elapsedTime() > 500){
|
}else if(pTicker->elapsedTime() > 500){
|
||||||
DebugL << "hook " << url << " " <<pTicker->elapsedTime() << "ms,success:" << body;
|
DebugL << "hook " << url << " " <<pTicker->elapsedTime() << "ms,success:" << bodyStr;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},hook_timeoutSec);
|
},hook_timeoutSec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value make_json(const MediaInfo &args){
|
static ArgsType make_json(const MediaInfo &args){
|
||||||
Value body;
|
ArgsType body;
|
||||||
body["schema"] = args._schema;
|
body["schema"] = args._schema;
|
||||||
body["vhost"] = args._vhost;
|
body["vhost"] = args._vhost;
|
||||||
body["app"] = args._app;
|
body["app"] = args._app;
|
||||||
body["stream"] = args._streamid;
|
body["stream"] = args._streamid;
|
||||||
body["params"] = args._param_strs;
|
body["params"] = args._param_strs;
|
||||||
return body;
|
return std::move(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -112,14 +151,20 @@ void installWebHook(){
|
|||||||
GET_CONFIG_AND_REGISTER(string,hook_play,Hook::kOnPlay);
|
GET_CONFIG_AND_REGISTER(string,hook_play,Hook::kOnPlay);
|
||||||
GET_CONFIG_AND_REGISTER(string,hook_flowreport,Hook::kOnFlowReport);
|
GET_CONFIG_AND_REGISTER(string,hook_flowreport,Hook::kOnFlowReport);
|
||||||
GET_CONFIG_AND_REGISTER(string,hook_adminparams,Hook::kAdminParams);
|
GET_CONFIG_AND_REGISTER(string,hook_adminparams,Hook::kAdminParams);
|
||||||
|
GET_CONFIG_AND_REGISTER(string,hook_rtsp_realm,Hook::kOnRtspRealm);
|
||||||
|
GET_CONFIG_AND_REGISTER(string,hook_rtsp_auth,Hook::kOnRtspAuth);
|
||||||
|
GET_CONFIG_AND_REGISTER(string,hook_stream_chaned,Hook::kOnStreamChanged);
|
||||||
|
GET_CONFIG_AND_REGISTER(string,hook_stream_not_found,Hook::kOnStreamNotFound);
|
||||||
|
GET_CONFIG_AND_REGISTER(string,hook_record_mp4,Hook::kOnRecordMp4);
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRtmpPublish,[](BroadcastRtmpPublishArgs){
|
|
||||||
if(!hook_enable || args._param_strs == hook_adminparams){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
|
||||||
|
if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty()){
|
||||||
invoker("");
|
invoker("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//异步执行该hook api,防止阻塞NoticeCenter
|
//异步执行该hook api,防止阻塞NoticeCenter
|
||||||
Value body = make_json(args);
|
auto body = make_json(args);
|
||||||
body["ip"] = sender.get_peer_ip();
|
body["ip"] = sender.get_peer_ip();
|
||||||
body["port"] = sender.get_peer_port();
|
body["port"] = sender.get_peer_port();
|
||||||
body["id"] = sender.getIdentifier();
|
body["id"] = sender.getIdentifier();
|
||||||
@ -132,11 +177,11 @@ void installWebHook(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPlayed,[](BroadcastMediaPlayedArgs){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPlayed,[](BroadcastMediaPlayedArgs){
|
||||||
if(!hook_enable || args._param_strs == hook_adminparams){
|
if(!hook_enable || args._param_strs == hook_adminparams || hook_play.empty()){
|
||||||
invoker("");
|
invoker("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Value body = make_json(args);
|
auto body = make_json(args);
|
||||||
body["ip"] = sender.get_peer_ip();
|
body["ip"] = sender.get_peer_ip();
|
||||||
body["port"] = sender.get_peer_port();
|
body["port"] = sender.get_peer_port();
|
||||||
body["id"] = sender.getIdentifier();
|
body["id"] = sender.getIdentifier();
|
||||||
@ -150,10 +195,10 @@ void installWebHook(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){
|
||||||
if(!hook_enable || args._param_strs == hook_adminparams){
|
if(!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty()){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Value body = make_json(args);
|
auto body = make_json(args);
|
||||||
body["ip"] = sender.get_peer_ip();
|
body["ip"] = sender.get_peer_ip();
|
||||||
body["port"] = sender.get_peer_port();
|
body["port"] = sender.get_peer_port();
|
||||||
body["id"] = sender.getIdentifier();
|
body["id"] = sender.getIdentifier();
|
||||||
@ -166,4 +211,122 @@ void installWebHook(){
|
|||||||
do_http_hook(hook_flowreport,body, nullptr);
|
do_http_hook(hook_flowreport,body, nullptr);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
static const string unAuthedRealm = "unAuthedRealm";
|
||||||
|
|
||||||
|
//监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
|
||||||
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastOnGetRtspRealm,[](BroadcastOnGetRtspRealmArgs){
|
||||||
|
if(!hook_enable || args._param_strs == hook_adminparams || hook_rtsp_realm.empty()){
|
||||||
|
//无需认证
|
||||||
|
invoker("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto body = make_json(args);
|
||||||
|
body["ip"] = sender.get_peer_ip();
|
||||||
|
body["port"] = sender.get_peer_port();
|
||||||
|
body["id"] = sender.getIdentifier();
|
||||||
|
|
||||||
|
EventPollerPool::Instance().getExecutor()->async([body,invoker](){
|
||||||
|
//执行hook
|
||||||
|
do_http_hook(hook_rtsp_realm,body, [invoker](const Value &obj,const string &err){
|
||||||
|
if(!err.empty()){
|
||||||
|
//如果接口访问失败,那么该rtsp流认证失败
|
||||||
|
invoker(unAuthedRealm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invoker(obj["realm"].asString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//监听kBroadcastOnRtspAuth事件返回正确的rtsp鉴权用户密码
|
||||||
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastOnRtspAuth,[](BroadcastOnRtspAuthArgs){
|
||||||
|
if(unAuthedRealm == realm || !hook_enable || hook_rtsp_auth.empty()){
|
||||||
|
//认证失败
|
||||||
|
invoker(false,makeRandStr(12));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto body = make_json(args);
|
||||||
|
body["ip"] = sender.get_peer_ip();
|
||||||
|
body["port"] = sender.get_peer_port();
|
||||||
|
body["id"] = sender.getIdentifier();
|
||||||
|
body["user_name"] = user_name;
|
||||||
|
body["must_no_encrypt"] = must_no_encrypt;
|
||||||
|
body["realm"] = realm;
|
||||||
|
|
||||||
|
EventPollerPool::Instance().getExecutor()->async([body,invoker](){
|
||||||
|
//执行hook
|
||||||
|
do_http_hook(hook_rtsp_auth,body, [invoker](const Value &obj,const string &err){
|
||||||
|
if(!err.empty()){
|
||||||
|
//认证失败
|
||||||
|
invoker(false,makeRandStr(12));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invoker(obj["encrypted"].asBool(),obj["passwd"].asString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//监听rtsp、rtmp源注册或注销事件
|
||||||
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaChanged,[](BroadcastMediaChangedArgs){
|
||||||
|
if(!hook_enable || hook_stream_chaned.empty()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArgsType body;
|
||||||
|
body["regist"] = bRegist;
|
||||||
|
body["schema"] = schema;
|
||||||
|
body["vhost"] = vhost;
|
||||||
|
body["app"] = app;
|
||||||
|
body["stream"] = stream;
|
||||||
|
|
||||||
|
EventPollerPool::Instance().getExecutor()->async([body](){
|
||||||
|
//执行hook
|
||||||
|
do_http_hook(hook_stream_chaned,body, nullptr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//监听播放失败(未找到特定的流)事件
|
||||||
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){
|
||||||
|
if(!hook_enable || hook_stream_not_found.empty()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto body = make_json(args);
|
||||||
|
body["ip"] = sender.get_peer_ip();
|
||||||
|
body["port"] = sender.get_peer_port();
|
||||||
|
body["id"] = sender.getIdentifier();
|
||||||
|
|
||||||
|
EventPollerPool::Instance().getExecutor()->async([body](){
|
||||||
|
//执行hook
|
||||||
|
do_http_hook(hook_stream_not_found,body, nullptr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#ifdef ENABLE_MP4V2
|
||||||
|
//录制mp4文件成功后广播
|
||||||
|
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
|
||||||
|
if(!hook_enable || hook_record_mp4.empty()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArgsType body;
|
||||||
|
body["start_time"] = (uint64_t)info.ui64StartedTime;
|
||||||
|
body["time_len"] = (uint64_t)info.ui64TimeLen;
|
||||||
|
body["file_size"] = info.ui64FileSize;
|
||||||
|
body["file_path"] = info.strFilePath;
|
||||||
|
body["file_name"] = info.strFileName;
|
||||||
|
body["folder"] = info.strFolder;
|
||||||
|
body["url"] = info.strUrl;
|
||||||
|
body["app"] = info.strAppName;
|
||||||
|
body["stream"] = info.strStreamId;
|
||||||
|
body["vhost"] = info.strVhost;
|
||||||
|
|
||||||
|
EventPollerPool::Instance().getExecutor()->async([body](){
|
||||||
|
//执行hook
|
||||||
|
do_http_hook(hook_record_mp4,body, nullptr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
#endif //ENABLE_MP4V2
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
169
server/main.cpp
169
server/main.cpp
@ -188,95 +188,100 @@ static void inline listen_shell_input(){
|
|||||||
EventPollerPool::Instance().getFirstPoller()->addEvent(STDIN_FILENO, Event_Read | Event_Error | Event_LT,oninput);
|
EventPollerPool::Instance().getFirstPoller()->addEvent(STDIN_FILENO, Event_Read | Event_Error | Event_LT,oninput);
|
||||||
}
|
}
|
||||||
int main(int argc,char *argv[]) {
|
int main(int argc,char *argv[]) {
|
||||||
CMD_main cmd_main;
|
{
|
||||||
try {
|
CMD_main cmd_main;
|
||||||
cmd_main.operator()(argc,argv);
|
try {
|
||||||
} catch (std::exception &ex) {
|
cmd_main.operator()(argc, argv);
|
||||||
cout << ex.what() << endl;
|
} catch (std::exception &ex) {
|
||||||
return -1;
|
cout << ex.what() << endl;
|
||||||
}
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
bool bDaemon = cmd_main.hasKey("daemon");
|
bool bDaemon = cmd_main.hasKey("daemon");
|
||||||
LogLevel logLevel = (LogLevel)cmd_main["level"].as<int>();
|
LogLevel logLevel = (LogLevel) cmd_main["level"].as<int>();
|
||||||
logLevel = MIN(MAX(logLevel,LTrace),LError);
|
logLevel = MIN(MAX(logLevel, LTrace), LError);
|
||||||
string ini_file = cmd_main["config"];
|
string ini_file = cmd_main["config"];
|
||||||
string ssl_file = cmd_main["ssl"];
|
string ssl_file = cmd_main["ssl"];
|
||||||
int threads = cmd_main["threads"];
|
int threads = cmd_main["threads"];
|
||||||
|
|
||||||
//设置日志
|
//设置日志
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel",logLevel));
|
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
|
||||||
#if defined(__linux__) || defined(__linux)
|
#if defined(__linux__) || defined(__linux)
|
||||||
Logger::Instance().add(std::make_shared<SysLogChannel>("SysLogChannel",logLevel));
|
Logger::Instance().add(std::make_shared<SysLogChannel>("SysLogChannel",logLevel));
|
||||||
#else
|
#else
|
||||||
Logger::Instance().add(std::make_shared<FileChannel>("FileChannel",exePath() + ".log",logLevel));
|
Logger::Instance().add(std::make_shared<FileChannel>("FileChannel", exePath() + ".log", logLevel));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(bDaemon){
|
if (bDaemon) {
|
||||||
//启动守护进程
|
//启动守护进程
|
||||||
System::startDaemon();
|
System::startDaemon();
|
||||||
|
}
|
||||||
|
|
||||||
|
//启动异步日志线程
|
||||||
|
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||||
|
//加载配置文件,如果配置文件不存在就创建一个
|
||||||
|
loadIniConfig(ini_file.data());
|
||||||
|
|
||||||
|
//加载证书,证书包含公钥和私钥
|
||||||
|
SSL_Initor::Instance().loadCertificate(ssl_file.data());
|
||||||
|
//信任某个自签名证书
|
||||||
|
SSL_Initor::Instance().trustCertificate(ssl_file.data());
|
||||||
|
//不忽略无效证书证书(例如自签名或过期证书)
|
||||||
|
SSL_Initor::Instance().ignoreInvalidCertificate(false);
|
||||||
|
|
||||||
|
uint16_t shellPort = mINI::Instance()[Shell::kPort];
|
||||||
|
uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
|
||||||
|
uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
|
||||||
|
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
|
||||||
|
uint16_t httpPort = mINI::Instance()[Http::kPort];
|
||||||
|
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效
|
||||||
|
*/
|
||||||
|
EventPollerPool::setPoolSize(threads);
|
||||||
|
|
||||||
|
//简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
|
||||||
|
//测试方法:telnet 127.0.0.1 9000
|
||||||
|
TcpServer::Ptr shellSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr httpSrv(new TcpServer());
|
||||||
|
|
||||||
|
shellSrv->start<ShellSession>(shellPort);
|
||||||
|
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
||||||
|
rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
|
||||||
|
//http服务器,支持websocket
|
||||||
|
httpSrv->start<EchoWebSocketSession>(httpPort);//默认80
|
||||||
|
|
||||||
|
//如果支持ssl,还可以开启https服务器
|
||||||
|
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||||
|
//https服务器,支持websocket
|
||||||
|
httpsSrv->start<SSLEchoWebSocketSession>(httpsPort);//默认443
|
||||||
|
|
||||||
|
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
||||||
|
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
||||||
|
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
|
||||||
|
|
||||||
|
installWebApi();
|
||||||
|
InfoL << "已启动http api 接口";
|
||||||
|
installWebHook();
|
||||||
|
InfoL << "已启动http hook 接口";
|
||||||
|
|
||||||
|
if (!bDaemon) {
|
||||||
|
//交互式shell输入
|
||||||
|
listen_shell_input();
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置退出信号处理函数
|
||||||
|
static semaphore sem;
|
||||||
|
signal(SIGINT, [](int) {
|
||||||
|
InfoL << "SIGINT:exit";
|
||||||
|
sem.post();
|
||||||
|
});// 设置退出信号
|
||||||
|
signal(SIGHUP, [](int) { mediakit::loadIniConfig(); });
|
||||||
|
sem.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
//启动异步日志线程
|
|
||||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
|
||||||
//加载配置文件,如果配置文件不存在就创建一个
|
|
||||||
loadIniConfig(ini_file.data());
|
|
||||||
|
|
||||||
//加载证书,证书包含公钥和私钥
|
|
||||||
SSL_Initor::Instance().loadCertificate(ssl_file.data());
|
|
||||||
//信任某个自签名证书
|
|
||||||
SSL_Initor::Instance().trustCertificate(ssl_file.data());
|
|
||||||
//不忽略无效证书证书(例如自签名或过期证书)
|
|
||||||
SSL_Initor::Instance().ignoreInvalidCertificate(false);
|
|
||||||
|
|
||||||
uint16_t shellPort = mINI::Instance()[Shell::kPort];
|
|
||||||
uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
|
|
||||||
uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
|
|
||||||
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
|
|
||||||
uint16_t httpPort = mINI::Instance()[Http::kPort];
|
|
||||||
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效
|
|
||||||
*/
|
|
||||||
EventPollerPool::setPoolSize(threads);
|
|
||||||
|
|
||||||
//简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
|
|
||||||
//测试方法:telnet 127.0.0.1 9000
|
|
||||||
TcpServer::Ptr shellSrv(new TcpServer());
|
|
||||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
|
||||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
|
||||||
TcpServer::Ptr httpSrv(new TcpServer());
|
|
||||||
|
|
||||||
shellSrv->start<ShellSession>(shellPort);
|
|
||||||
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
|
||||||
rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
|
|
||||||
//http服务器,支持websocket
|
|
||||||
httpSrv->start<EchoWebSocketSession>(httpPort);//默认80
|
|
||||||
|
|
||||||
//如果支持ssl,还可以开启https服务器
|
|
||||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
|
||||||
//https服务器,支持websocket
|
|
||||||
httpsSrv->start<SSLEchoWebSocketSession>(httpsPort);//默认443
|
|
||||||
|
|
||||||
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
|
||||||
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
|
||||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
|
|
||||||
|
|
||||||
installWebApi();
|
|
||||||
InfoL << "已启动http api 接口";
|
|
||||||
installWebHook();
|
|
||||||
InfoL << "已启动http hook 接口";
|
|
||||||
|
|
||||||
if(!bDaemon) {
|
|
||||||
//交互式shell输入
|
|
||||||
listen_shell_input();
|
|
||||||
}
|
|
||||||
|
|
||||||
//设置退出信号处理函数
|
|
||||||
static semaphore sem;
|
|
||||||
signal(SIGINT, [](int) { InfoL << "SIGINT:exit"; sem.post(); });// 设置退出信号
|
|
||||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(); });
|
|
||||||
sem.wait();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ extern const char kBroadcastOnGetRtspRealm[];
|
|||||||
//请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
|
//请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败
|
||||||
//获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码
|
//获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码
|
||||||
extern const char kBroadcastOnRtspAuth[];
|
extern const char kBroadcastOnRtspAuth[];
|
||||||
#define BroadcastOnRtspAuthArgs const MediaInfo &args,const string &user_name,const bool &must_no_encrypt,const RtspSession::onAuth &invoker,TcpSession &sender
|
#define BroadcastOnRtspAuthArgs const MediaInfo &args,const string &realm,const string &user_name,const bool &must_no_encrypt,const RtspSession::onAuth &invoker,TcpSession &sender
|
||||||
|
|
||||||
//鉴权结果回调对象
|
//鉴权结果回调对象
|
||||||
//如果errMessage为空则代表鉴权成功
|
//如果errMessage为空则代表鉴权成功
|
||||||
|
@ -37,13 +37,14 @@
|
|||||||
#include "HttpRequestSplitter.h"
|
#include "HttpRequestSplitter.h"
|
||||||
#include "HttpCookie.h"
|
#include "HttpCookie.h"
|
||||||
#include "HttpChunkedSplitter.h"
|
#include "HttpChunkedSplitter.h"
|
||||||
|
#include "strCoding.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class HttpArgs : public StrCaseMap {
|
class HttpArgs : public map<string, variant, StrCaseCompare> {
|
||||||
public:
|
public:
|
||||||
HttpArgs(){}
|
HttpArgs(){}
|
||||||
virtual ~HttpArgs(){}
|
virtual ~HttpArgs(){}
|
||||||
@ -52,7 +53,7 @@ public:
|
|||||||
for(auto &pr : *this){
|
for(auto &pr : *this){
|
||||||
ret.append(pr.first);
|
ret.append(pr.first);
|
||||||
ret.append("=");
|
ret.append("=");
|
||||||
ret.append(pr.second);
|
ret.append(strCoding::UrlUTF8Encode(pr.second));
|
||||||
ret.append("&");
|
ret.append("&");
|
||||||
}
|
}
|
||||||
if(ret.size()){
|
if(ret.size()){
|
||||||
@ -96,7 +97,8 @@ private:
|
|||||||
class HttpMultiFormBody : public HttpBody {
|
class HttpMultiFormBody : public HttpBody {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<HttpMultiFormBody> Ptr;
|
typedef std::shared_ptr<HttpMultiFormBody> Ptr;
|
||||||
HttpMultiFormBody(const StrCaseMap &args,const string &filePath,const string &boundary,uint32_t sliceSize = 4 * 1024){
|
template<typename MapType>
|
||||||
|
HttpMultiFormBody(const MapType &args,const string &filePath,const string &boundary,uint32_t sliceSize = 4 * 1024){
|
||||||
_fp = fopen(filePath.data(),"rb");
|
_fp = fopen(filePath.data(),"rb");
|
||||||
if(!_fp){
|
if(!_fp){
|
||||||
throw std::invalid_argument(StrPrinter << "打开文件失败:" << filePath << " " << get_uv_errmsg());
|
throw std::invalid_argument(StrPrinter << "打开文件失败:" << filePath << " " << get_uv_errmsg());
|
||||||
@ -156,7 +158,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static string multiFormBodyPrefix(const StrCaseMap &args,const string &boundary,const string &fileName){
|
template<typename MapType>
|
||||||
|
static string multiFormBodyPrefix(const MapType &args,const string &boundary,const string &fileName){
|
||||||
string MPboundary = string("--") + boundary;
|
string MPboundary = string("--") + boundary;
|
||||||
_StrPrinter body;
|
_StrPrinter body;
|
||||||
for(auto &pr : args){
|
for(auto &pr : args){
|
||||||
|
@ -422,7 +422,7 @@ void RtspSession::onAuthBasic(const weak_ptr<RtspSession> &weakSelf,const string
|
|||||||
}
|
}
|
||||||
|
|
||||||
//此时必须提供明文密码
|
//此时必须提供明文密码
|
||||||
if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth,strongSelf->_mediaInfo,user, true,invoker,*strongSelf)){
|
if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth,strongSelf->_mediaInfo,realm,user, true,invoker,*strongSelf)){
|
||||||
//表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之
|
//表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之
|
||||||
WarnL << "请监听kBroadcastOnRtspAuth事件!";
|
WarnL << "请监听kBroadcastOnRtspAuth事件!";
|
||||||
//但是我们还是忽略认证以便完成播放
|
//但是我们还是忽略认证以便完成播放
|
||||||
@ -503,7 +503,7 @@ void RtspSession::onAuthDigest(const weak_ptr<RtspSession> &weakSelf,const strin
|
|||||||
};
|
};
|
||||||
|
|
||||||
//此时可以提供明文或md5加密的密码
|
//此时可以提供明文或md5加密的密码
|
||||||
if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth,strongSelf->_mediaInfo,username, false,invoker,*strongSelf)){
|
if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth,strongSelf->_mediaInfo,realm,username, false,invoker,*strongSelf)){
|
||||||
//表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之
|
//表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之
|
||||||
WarnL << "请监听kBroadcastOnRtspAuth事件!";
|
WarnL << "请监听kBroadcastOnRtspAuth事件!";
|
||||||
//但是我们还是忽略认证以便完成播放
|
//但是我们还是忽略认证以便完成播放
|
||||||
|
Loading…
Reference in New Issue
Block a user