WebApi.cpp 26.3 KB
Newer Older
xiongziliang committed
1
/*
xiongziliang committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 * MIT License
 *
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
 *
 * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

27 28 29 30 31 32 33 34 35
#include <signal.h>
#include <functional>
#include <sstream>
#include <unordered_map>
#include "jsoncpp/json.h"
#include "Util/util.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/NoticeCenter.h"
xiongziliang committed
36
#ifdef ENABLE_MYSQL
37
#include "Util/SqlPool.h"
xiongziliang committed
38
#endif //ENABLE_MYSQL
39 40 41 42 43
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Http/HttpRequester.h"
#include "Http/HttpSession.h"
#include "Network/TcpServer.h"
xiongziliang committed
44
#include "Player/PlayerProxy.h"
45 46
#include "Util/MD5.h"
#include "WebApi.h"
47
#include "WebHook.h"
48

xiongziliang committed
49 50 51 52
#if !defined(_WIN32)
#include "FFmpegSource.h"
#endif//!defined(_WIN32)

53 54 55 56
using namespace Json;
using namespace toolkit;
using namespace mediakit;

xiongziliang committed
57 58 59 60

typedef map<string,variant,StrCaseCompare> ApiArgsType;


61 62
#define API_ARGS TcpSession &sender, \
                 HttpSession::KeyValue &headerIn, \
63
                 HttpSession::KeyValue &headerOut, \
xiongziliang committed
64
                 ApiArgsType &allArgs, \
65 66 67 68 69
                 Json::Value &val

#define API_REGIST(field, name, ...) \
    s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ \
         static auto lam = [&](API_ARGS) __VA_ARGS__ ;  \
70
         lam(sender,headerIn, headerOut, allArgs, val); \
71 72 73 74 75 76 77 78 79 80 81 82 83
         invoker("200 OK", headerOut, val.toStyledString()); \
     });

#define API_REGIST_INVOKER(field, name, ...) \
    s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__);

//异步http api lambad定义
typedef std::function<void(API_ARGS,const HttpSession::HttpResponseInvoker &invoker)> AsyncHttpApi;
//api列表
static map<string, AsyncHttpApi> s_map_api;

namespace API {
typedef enum {
84
    InvalidArgs = -300,
85 86 87 88 89 90 91
    SqlFailed = -200,
    AuthFailed = -100,
    OtherFailed = -1,
    Success = 0
} ApiErr;

#define API_FIELD "api."
92 93
const string kApiDebug = API_FIELD"apiDebug";
const string kSecret = API_FIELD"secret";
94

95
static onceToken token([]() {
xiongziliang committed
96
    mINI::Instance()[kApiDebug] = "1";
97
    mINI::Instance()[kSecret] = "035c73f7-bb6b-4889-a715-d9eb2d1925cc";
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
});
}//namespace API

class ApiRetException: public std::runtime_error {
public:
    ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
        _code = code;
    }
    ~ApiRetException() = default;
    int code(){ return _code; }
private:
    int _code;
};

class AuthException : public ApiRetException {
public:
    AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
    ~AuthException() = default;
};

118
class InvalidArgsException: public ApiRetException {
119
public:
120 121 122 123 124 125 126 127
    InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
    ~InvalidArgsException() = default;
};

class SuccessException: public ApiRetException {
public:
    SuccessException():ApiRetException("success",API::Success){}
    ~SuccessException() = default;
128 129
};

130 131

//获取HTTP请求中url参数、content参数
xiongziliang committed
132 133 134
static ApiArgsType getAllArgs(const Parser &parser) {
    ApiArgsType allArgs;
    if(parser["Content-Type"].find("application/x-www-form-urlencoded") == 0){
135 136
        auto contentArgs = parser.parseArgs(parser.Content());
        for (auto &pr : contentArgs) {
xiongziliang committed
137
            allArgs[pr.first] = HttpSession::urlDecode(pr.second);
138
        }
xiongziliang committed
139 140 141 142 143 144 145 146 147 148 149
    }else if(parser["Content-Type"].find("application/json") == 0){
        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();
150
        }
xiongziliang committed
151 152
    }else if(!parser["Content-Type"].empty()){
        WarnL << "invalid Content-Type:" << parser["Content-Type"];
153
    }
xiongziliang committed
154 155 156 157 158 159

    auto &urlArgs = parser.getUrlArgs();
    for (auto &pr : urlArgs) {
        allArgs[pr.first] = HttpSession::urlDecode(pr.second);
    }
    return std::move(allArgs);
160 161 162
}

static inline void addHttpListener(){
163
    GET_CONFIG(bool, api_debug, API::kApiDebug);
164 165 166 167 168 169 170 171 172
    //注册监听kBroadcastHttpRequest事件
    NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
        auto it = s_map_api.find(parser.Url());
        if (it == s_map_api.end()) {
            consumed = false;
            return;
        }
        //该api已被消费
        consumed = true;
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
        //执行API
        Json::Value val;
        val["code"] = API::Success;
        HttpSession::KeyValue headerOut;
        auto allArgs = getAllArgs(parser);
        HttpSession::KeyValue &headerIn = parser.getValues();
        headerOut["Content-Type"] = "application/json; charset=utf-8";
        if(api_debug){
            auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
                                                       const HttpSession::KeyValue &headerOut,
                                                       const string &contentOut){
                stringstream ss;
                for(auto &pr : allArgs ){
                    ss << pr.first << " : " << pr.second << "\r\n";
                }

                DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
                       << "# content:\r\n" << parser.Content() << "\r\n"
                       << "# args:\r\n" << ss.str()
                       << "# response:\r\n"
                       << contentOut << "\r\n";

                invoker(codeOut,headerOut,contentOut);
            };
            ((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
        }
199

200
        try {
201
            it->second(sender,headerIn, headerOut, allArgs, val, invoker);
202 203 204 205
        }  catch(ApiRetException &ex){
            val["code"] = ex.code();
            val["msg"] = ex.what();
            invoker("200 OK", headerOut, val.toStyledString());
xiongziliang committed
206 207 208
        }
#ifdef ENABLE_MYSQL
        catch(SqlException &ex){
209 210 211 212
            val["code"] = API::SqlFailed;
            val["msg"] = StrPrinter << "操作数据库失败:" << ex.what() << ":" << ex.getSql();
            WarnL << ex.what() << ":" << ex.getSql();
            invoker("200 OK", headerOut, val.toStyledString());
xiongziliang committed
213 214 215
        }
#endif// ENABLE_MYSQL
        catch (std::exception &ex) {
216 217 218 219
            val["code"] = API::OtherFailed;
            val["msg"] = ex.what();
            invoker("200 OK", headerOut, val.toStyledString());
        }
220 221 222
    });
}

223
template <typename Args,typename First>
xiongziliang committed
224
bool checkArgs(Args &&args,First &&first){
225 226 227 228
    return !args[first].empty();
}

template <typename Args,typename First,typename ...KeyTypes>
xiongziliang committed
229 230
bool checkArgs(Args &&args,First &&first,KeyTypes && ...keys){
    return !args[first].empty() && checkArgs(std::forward<Args>(args),std::forward<KeyTypes>(keys)...);
231 232 233
}

#define CHECK_ARGS(...)  \
xiongziliang committed
234
    if(!checkArgs(allArgs,##__VA_ARGS__)){ \
235
        throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
236 237
    }

238
#define CHECK_SECRET() \
239 240 241 242 243
    if(sender.get_peer_ip() != "127.0.0.1"){ \
        CHECK_ARGS("secret"); \
        if(api_secret != allArgs["secret"]){ \
            throw AuthException("secret错误"); \
        } \
244 245
    }

246
static unordered_map<string ,PlayerProxy::Ptr> s_proxyMap;
247
static recursive_mutex s_proxyMapMtx;
248 249 250
static inline string getProxyKey(const string &vhost,const string &app,const string &stream){
    return vhost + "/" + app + "/" + stream;
}
251

xiongziliang committed
252
#if !defined(_WIN32)
253 254
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
static recursive_mutex s_ffmpegMapMtx;
xiongziliang committed
255
#endif//#if !defined(_WIN32)
256

257 258 259 260 261
/**
 * 安装api接口
 * 所有api都支持GET和POST两种方式
 * POST方式参数支持application/json和application/x-www-form-urlencoded方式
 */
262 263 264
void installWebApi() {
    addHttpListener();

265
    GET_CONFIG(string,api_secret,API::kSecret);
266

267 268
    //获取线程负载
    //测试url http://127.0.0.1/index/api/getThreadsLoad
269 270 271 272
    API_REGIST_INVOKER(api, getThreadsLoad, {
        EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
            Value val;
            auto vec = EventPollerPool::Instance().getExecutorLoad();
xiongziliang committed
273
            int i = API::Success;
274 275 276 277 278 279 280 281 282 283
            for (auto load : vec) {
                Value obj(objectValue);
                obj["load"] = load;
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

284 285
    //获取服务器配置
    //测试url http://127.0.0.1/index/api/getServerConfig
286
    API_REGIST(api, getServerConfig, {
287
        CHECK_SECRET();
288 289 290 291 292 293 294
        Value obj;
        for (auto &pr : mINI::Instance()) {
            obj[pr.first] = (string &) pr.second;
        }
        val["data"].append(obj);
    });

295 296 297
    //设置服务器配置
    //测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
    //你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参
298
    API_REGIST(api, setServerConfig, {
299
        CHECK_SECRET();
300
        auto &ini = mINI::Instance();
xiongziliang committed
301
        int changed = API::Success;
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
        for (auto &pr : allArgs) {
            if (ini.find(pr.first) == ini.end()) {
                //没有这个key
                continue;
            }
            if (ini[pr.first] == pr.second) {
                continue;
            }
            ini[pr.first] = pr.second;
            //替换成功
            ++changed;
        }
        if (changed > 0) {
            NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
            ini.dumpFile();
        }
        val["changed"] = changed;
    });

321 322 323

    //获取服务器api列表
    //测试url http://127.0.0.1/index/api/getApiList
324
    API_REGIST(api,getApiList,{
325
        CHECK_SECRET();
326 327 328 329 330
        for(auto &pr : s_map_api){
            val["data"].append(pr.first);
        }
    });

xiongziliang committed
331
#if !defined(_WIN32)
332 333
    //重启服务器,只有Daemon方式才能重启,否则是直接关闭!
    //测试url http://127.0.0.1/index/api/restartServer
334
    API_REGIST(api,restartServer,{
335
        CHECK_SECRET();
336 337 338 339 340 341 342 343 344 345 346 347 348 349
        EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
            //尝试正常退出
            ::kill(getpid(), SIGINT);

            //3秒后强制退出
            EventPollerPool::Instance().getPoller()->doDelayTask(3000,[](){
                exit(0);
                return 0;
            });

            return 0;
        });
        val["msg"] = "服务器将在一秒后自动重启";
    });
xiongziliang committed
350
#endif//#if !defined(_WIN32)
351 352


353 354 355 356
    //获取流列表,可选筛选参数
    //测试url0(获取所有流) http://127.0.0.1/index/api/getMediaList
    //测试url1(获取虚拟主机为"__defaultVost__"的流) http://127.0.0.1/index/api/getMediaList?vhost=__defaultVost__
    //测试url2(获取rtsp类型的流) http://127.0.0.1/index/api/getMediaList?schema=rtsp
357
    API_REGIST(api,getMediaList,{
358
        CHECK_SECRET();
359
        //获取所有MediaSource列表
xiongziliang committed
360
        val["code"] = API::Success;
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
        val["msg"] = "success";
        MediaSource::for_each_media([&](const string &schema,
                                        const string &vhost,
                                        const string &app,
                                        const string &stream,
                                        const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != schema){
                return;
            }
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != vhost){
                return;
            }
            if(!allArgs["app"].empty() && allArgs["app"] != app){
                return;
            }
            Value item;
            item["schema"] = schema;
            item["vhost"] = vhost;
            item["app"] = app;
            item["stream"] = stream;
xiongziliang committed
381
            val["data"].append(item);
382 383 384
        });
    });

385
    //主动关断流,包括关断拉流、推流
386
    //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
387
    API_REGIST(api,close_stream,{
388
        CHECK_SECRET();
389
        CHECK_ARGS("schema","vhost","app","stream");
390 391 392 393 394 395
        //踢掉推流器
        auto src = MediaSource::find(allArgs["schema"],
                                     allArgs["vhost"],
                                     allArgs["app"],
                                     allArgs["stream"]);
        if(src){
396
            bool flag = src->close(allArgs["force"].as<bool>());
397
            val["code"] = flag ? 0 : -1;
398
            val["msg"] = flag ? "success" : "close failed";
399 400
        }else{
            val["code"] = -2;
401
            val["msg"] = "can not find the stream";
402 403 404
        }
    });

405 406 407 408 409 410 411 412 413 414
    //获取所有TcpSession列表信息
    //可以根据本地端口和远端ip来筛选
    //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
    API_REGIST(api,getAllSession,{
        CHECK_SECRET();
        Value jsession;
        uint16_t local_port = allArgs["local_port"].as<uint16_t>();
        string &peer_ip = allArgs["peer_ip"];

        SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){
xiongziliang committed
415
            if(local_port != API::Success && local_port != session->get_local_port()){
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
                return;
            }
            if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){
                return;
            }
            jsession["peer_ip"] = session->get_peer_ip();
            jsession["peer_port"] = session->get_peer_port();
            jsession["local_ip"] = session->get_local_ip();
            jsession["local_port"] = session->get_local_port();
            jsession["id"] = id;
            jsession["typeid"] = typeid(*session).name();
            val["data"].append(jsession);
        });
    });

    //断开tcp连接,比如说可以断开rtsp、rtmp播放器等
    //测试url http://127.0.0.1/index/api/kick_session?id=123456
433
    API_REGIST(api,kick_session,{
434
        CHECK_SECRET();
435
        CHECK_ARGS("id");
436
        //踢掉tcp会话
xiongziliang committed
437
        auto session = SessionMap::Instance().get(allArgs["id"]);
438
        if(!session){
xiongziliang committed
439
            val["code"] = API::OtherFailed;
440 441 442 443
            val["msg"] = "can not find the target";
            return;
        }
        session->safeShutdown();
xiongziliang committed
444
        val["code"] = API::Success;
445 446 447
        val["msg"] = "success";
    });

448 449 450 451
    static auto addStreamProxy = [](const string &vhost,
                                    const string &app,
                                    const string &stream,
                                    const string &url,
452 453
                                    bool enable_rtsp,
                                    bool enable_rtmp,
454 455 456 457 458
                                    bool enable_hls,
                                    bool enable_mp4,
                                    int rtp_type,
                                    const function<void(const SockException &ex,const string &key)> &cb){
        auto key = getProxyKey(vhost,app,stream);
459 460 461 462 463 464
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        if(s_proxyMap.find(key) != s_proxyMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }
xiongziliang committed
465
        //添加拉流代理
466
        PlayerProxy::Ptr player(new PlayerProxy(vhost,app,stream,enable_rtsp,enable_rtmp,enable_hls,enable_mp4));
467 468
        s_proxyMap[key] = player;
        
xiongziliang committed
469
        //指定RTP over TCP(播放rtsp时有效)
470
        (*player)[kRtpType] = rtp_type;
471
        //开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
472
        player->setPlayCallbackOnce([cb,key](const SockException &ex){
473
            if(ex){
474
                lock_guard<recursive_mutex> lck(s_proxyMapMtx);
475
                s_proxyMap.erase(key);
476
            }
477
            cb(ex,key);
478
        });
479 480 481 482 483 484

        //被主动关闭拉流
        player->setOnClose([key](){
            lock_guard<recursive_mutex> lck(s_proxyMapMtx);
            s_proxyMap.erase(key);
        });
485 486 487 488
        player->play(url);
    };

    //动态添加rtsp/rtmp拉流代理
489
    //测试url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs
490 491
    API_REGIST_INVOKER(api,addStreamProxy,{
        CHECK_SECRET();
492
        CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
493 494 495 496
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       allArgs["url"],
497 498 499 500
                       allArgs["enable_rtsp"],/* 是否rtsp转发 */
                       allArgs["enable_rtmp"],/* 是否rtmp转发 */
                       allArgs["enable_hls"],/* 是否hls转发 */
                       allArgs["enable_mp4"],/* 是否MP4录制 */
501 502 503 504 505 506 507 508 509 510
                       allArgs["rtp_type"],
                       [invoker,val,headerOut](const SockException &ex,const string &key){
                           if(ex){
                               const_cast<Value &>(val)["code"] = API::OtherFailed;
                               const_cast<Value &>(val)["msg"] = ex.what();
                           }else{
                               const_cast<Value &>(val)["data"]["key"] = key;
                           }
                           invoker("200 OK", headerOut, val.toStyledString());
                       });
xiongziliang committed
511 512
    });

513 514
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
xiongziliang committed
515
    API_REGIST(api,delStreamProxy,{
516
        CHECK_SECRET();
517
        CHECK_ARGS("key");
xiongziliang committed
518
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
519
        val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
xiongziliang committed
520 521
    });

xiongziliang committed
522
#if !defined(_WIN32)
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
    static auto addFFmepgSource = [](const string &src_url,
                                     const string &dst_url,
                                     int timeout_ms,
                                     const function<void(const SockException &ex,const string &key)> &cb){
        auto key = MD5(dst_url).hexdigest();
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        if(s_ffmpegMap.find(key) != s_ffmpegMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }

        FFmpegSource::Ptr ffmpeg = std::make_shared<FFmpegSource>();
        s_ffmpegMap[key] = ffmpeg;

        ffmpeg->setOnClose([key](){
            lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
            s_ffmpegMap.erase(key);
        });
        ffmpeg->play(src_url, dst_url,timeout_ms,[cb , key](const SockException &ex){
            if(ex){
                lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
                s_ffmpegMap.erase(key);
            }
            cb(ex,key);
        });
    };

    //动态添加rtsp/rtmp拉流代理
    //测试url http://127.0.0.1/index/api/addFFmpegSource?src_url=http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8&dst_url=rtmp://127.0.0.1/live/hks2&timeout_ms=10000
    API_REGIST_INVOKER(api,addFFmpegSource,{
        CHECK_SECRET();
        CHECK_ARGS("src_url","dst_url","timeout_ms");
        auto src_url = allArgs["src_url"];
        auto dst_url = allArgs["dst_url"];
        int timeout_ms = allArgs["timeout_ms"];

        addFFmepgSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
            if(ex){
                const_cast<Value &>(val)["code"] = API::OtherFailed;
                const_cast<Value &>(val)["msg"] = ex.what();
            }else{
                const_cast<Value &>(val)["data"]["key"] = key;
            }
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
    API_REGIST(api,delFFmepgSource,{
        CHECK_SECRET();
        CHECK_ARGS("key");
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
    });
xiongziliang committed
579
#endif
xiongziliang committed
580

581 582 583
    ////////////以下是注册的Hook API////////////
    API_REGIST(hook,on_publish,{
        //开始推流事件
584 585 586 587 588 589
        //转换成rtsp或rtmp
        val["enableRtxp"] = true;
        //转换hls
        val["enableHls"] = true;
        //不录制mp4
        val["enableMP4"] = false;
590 591 592 593
    });

    API_REGIST(hook,on_play,{
        //开始播放事件
594
        throw SuccessException();
595 596 597 598
    });

    API_REGIST(hook,on_flow_report,{
        //流量统计hook api
599
        throw SuccessException();
600
    });
xiongziliang committed
601 602

    API_REGIST(hook,on_rtsp_realm,{
603
        //rtsp是否需要鉴权,默认需要鉴权
xiongziliang committed
604
        val["code"] = API::Success;
xiongziliang committed
605 606 607 608 609 610
        val["realm"] = "zlmediakit_reaml";
    });

    API_REGIST(hook,on_rtsp_auth,{
        //rtsp鉴权密码,密码等于用户名
        //rtsp可以有双重鉴权!后面还会触发on_play事件
611
        CHECK_ARGS("user_name");
xiongziliang committed
612
        val["code"] = API::Success;
xiongziliang committed
613
        val["encrypted"] = false;
614
        val["passwd"] = allArgs["user_name"].data();
xiongziliang committed
615 616 617 618
    });

    API_REGIST(hook,on_stream_changed,{
        //媒体注册或反注册事件
619
        throw SuccessException();
xiongziliang committed
620 621
    });

xiongziliang committed
622 623

#if !defined(_WIN32)
624
    API_REGIST_INVOKER(hook,on_stream_not_found_ffmpeg,{
625
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
626 627
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
628 629
        //通过FFmpeg按需拉流
        GET_CONFIG(int,rtmp_port,Rtmp::kPort);
630 631
        GET_CONFIG(int,timeout_sec,Hook::kTimeoutSec);

632 633 634 635 636 637 638
        string dst_url = StrPrinter
                << "rtmp://127.0.0.1:"
                << rtmp_port << "/"
                << allArgs["app"] << "/"
                << allArgs["stream"] << "?vhost="
                << allArgs["vhost"];

639
        addFFmepgSource("http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
640
                        dst_url,
641
                        (1000 * timeout_sec) - 500,
642 643 644 645 646 647 648 649 650
                        [invoker,val,headerOut](const SockException &ex,const string &key){
                            if(ex){
                                const_cast<Value &>(val)["code"] = API::OtherFailed;
                                const_cast<Value &>(val)["msg"] = ex.what();
                            }else{
                                const_cast<Value &>(val)["data"]["key"] = key;
                            }
                            invoker("200 OK", headerOut, val.toStyledString());
                        });
xiongziliang committed
651
    });
652
#endif//!defined(_WIN32)
xiongziliang committed
653 654 655 656 657

    API_REGIST_INVOKER(hook,on_stream_not_found,{
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
658 659 660 661 662
        //通过内置支持的rtsp/rtmp按需拉流
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       /** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
663 664 665 666 667
                       "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
                       true,/* 开启rtsp转发 */
                       true,/* 开启rtmp转发 */
                       true,/* 开启hls转发 */
                       false,/* 禁用MP4录制 */
668 669 670 671 672 673 674 675 676 677
                       0,//rtp over tcp方式拉流
                       [invoker,val,headerOut](const SockException &ex,const string &key){
                           if(ex){
                               const_cast<Value &>(val)["code"] = API::OtherFailed;
                               const_cast<Value &>(val)["msg"] = ex.what();
                           }else{
                               const_cast<Value &>(val)["data"]["key"] = key;
                           }
                           invoker("200 OK", headerOut, val.toStyledString());
                       });
xiongziliang committed
678 679 680 681
    });

    API_REGIST(hook,on_record_mp4,{
        //录制mp4分片完毕事件
682
        throw SuccessException();
xiongziliang committed
683 684
    });

685 686 687 688
    API_REGIST(hook,on_shell_login,{
        //shell登录调试事件
        throw SuccessException();
    });
689 690 691 692 693 694

    API_REGIST(hook,on_stream_none_reader,{
        //无人观看流默认关闭
        val["close"] = true;
    });

695 696 697 698 699
    static auto checkAccess = [](const string &params){
        //我们假定大家都要权限访问
        return true;
    };

700
    API_REGIST(hook,on_http_access,{
701 702 703
        //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
        if(!checkAccess(allArgs["params"])){
            //无访问权限
704 705
            val["err"] = "无访问权限";
            //仅限制访问当前目录
706
            val["path"] = "";
707
            //标记该客户端无权限1分钟
708 709 710 711
            val["second"] = 60;
            return;
        }

712 713 714 715 716
        //可以访问
        val["err"] = "";
        //只能访问当前目录
        val["path"] = "";
        //该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
717
        val["second"] = 10 * 60;
718 719
    });

720

721
}
xiongziliang committed
722

723
void unInstallWebApi(){
724 725 726 727 728
    {
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        s_proxyMap.clear();
    }

xiongziliang committed
729
#if !defined(_WIN32)
730 731 732 733
    {
        lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
        s_ffmpegMap.clear();
    }
xiongziliang committed
734
#endif
735
}