Commit 74d074ac by xiongziliang

完善Hook与API

parent 2f6773f1
...@@ -13,14 +13,19 @@ ...@@ -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 { ...@@ -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: ...@@ -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(){ ...@@ -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(){ ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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";
});
} }
\ No newline at end of file
...@@ -188,95 +188,100 @@ static void inline listen_shell_input(){ ...@@ -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>()); Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
//加载配置文件,如果配置文件不存在就创建一个 //加载配置文件,如果配置文件不存在就创建一个
loadIniConfig(ini_file.data()); loadIniConfig(ini_file.data());
//加载证书,证书包含公钥和私钥 //加载证书,证书包含公钥和私钥
SSL_Initor::Instance().loadCertificate(ssl_file.data()); SSL_Initor::Instance().loadCertificate(ssl_file.data());
//信任某个自签名证书 //信任某个自签名证书
SSL_Initor::Instance().trustCertificate(ssl_file.data()); SSL_Initor::Instance().trustCertificate(ssl_file.data());
//不忽略无效证书证书(例如自签名或过期证书) //不忽略无效证书证书(例如自签名或过期证书)
SSL_Initor::Instance().ignoreInvalidCertificate(false); SSL_Initor::Instance().ignoreInvalidCertificate(false);
uint16_t shellPort = mINI::Instance()[Shell::kPort]; uint16_t shellPort = mINI::Instance()[Shell::kPort];
uint16_t rtspPort = mINI::Instance()[Rtsp::kPort]; uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort]; uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort]; uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
uint16_t httpPort = mINI::Instance()[Http::kPort]; uint16_t httpPort = mINI::Instance()[Http::kPort];
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort]; uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
/** /**
* 设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效 * 设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效
*/ */
EventPollerPool::setPoolSize(threads); EventPollerPool::setPoolSize(threads);
//简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象 //简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
//测试方法:telnet 127.0.0.1 9000 //测试方法:telnet 127.0.0.1 9000
TcpServer::Ptr shellSrv(new TcpServer()); TcpServer::Ptr shellSrv(new TcpServer());
TcpServer::Ptr rtspSrv(new TcpServer()); TcpServer::Ptr rtspSrv(new TcpServer());
TcpServer::Ptr rtmpSrv(new TcpServer()); TcpServer::Ptr rtmpSrv(new TcpServer());
TcpServer::Ptr httpSrv(new TcpServer()); TcpServer::Ptr httpSrv(new TcpServer());
shellSrv->start<ShellSession>(shellPort); shellSrv->start<ShellSession>(shellPort);
rtspSrv->start<RtspSession>(rtspPort);//默认554 rtspSrv->start<RtspSession>(rtspPort);//默认554
rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935 rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
//http服务器,支持websocket //http服务器,支持websocket
httpSrv->start<EchoWebSocketSession>(httpPort);//默认80 httpSrv->start<EchoWebSocketSession>(httpPort);//默认80
//如果支持ssl,还可以开启https服务器 //如果支持ssl,还可以开启https服务器
TcpServer::Ptr httpsSrv(new TcpServer()); TcpServer::Ptr httpsSrv(new TcpServer());
//https服务器,支持websocket //https服务器,支持websocket
httpsSrv->start<SSLEchoWebSocketSession>(httpsPort);//默认443 httpsSrv->start<SSLEchoWebSocketSession>(httpsPort);//默认443
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 //支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
TcpServer::Ptr rtspSSLSrv(new TcpServer()); TcpServer::Ptr rtspSSLSrv(new TcpServer());
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322 rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
installWebApi(); installWebApi();
InfoL << "已启动http api 接口"; InfoL << "已启动http api 接口";
installWebHook(); installWebHook();
InfoL << "已启动http hook 接口"; InfoL << "已启动http hook 接口";
if(!bDaemon) { if (!bDaemon) {
//交互式shell输入 //交互式shell输入
listen_shell_input(); listen_shell_input();
} }
//设置退出信号处理函数 //设置退出信号处理函数
static semaphore sem; static semaphore sem;
signal(SIGINT, [](int) { InfoL << "SIGINT:exit"; sem.post(); });// 设置退出信号 signal(SIGINT, [](int) {
signal(SIGHUP, [](int) { mediakit::loadIniConfig(); }); InfoL << "SIGINT:exit";
sem.wait(); sem.post();
});// 设置退出信号
signal(SIGHUP, [](int) { mediakit::loadIniConfig(); });
sem.wait();
}
return 0; return 0;
} }
...@@ -86,7 +86,7 @@ extern const char kBroadcastOnGetRtspRealm[]; ...@@ -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 @@ ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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 ...@@ -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 ...@@ -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事件!";
//但是我们还是忽略认证以便完成播放 //但是我们还是忽略认证以便完成播放
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论