WebApi.cpp 44 KB
Newer Older
xiongziliang committed
1
/*
xiongziliang committed
2
 * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
xiongziliang committed
3 4 5
 *
 * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
 *
xiongziliang committed
6 7 8
 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
xiongziliang committed
9 10
 */

11 12
#include <sys/stat.h>
#include <math.h>
13 14 15 16 17 18 19 20 21
#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
22
#ifdef ENABLE_MYSQL
23
#include "Util/SqlPool.h"
xiongziliang committed
24
#endif //ENABLE_MYSQL
25 26 27 28 29
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Http/HttpRequester.h"
#include "Http/HttpSession.h"
#include "Network/TcpServer.h"
xiongziliang committed
30
#include "Player/PlayerProxy.h"
31 32
#include "Util/MD5.h"
#include "WebApi.h"
33
#include "WebHook.h"
34
#include "Thread/WorkThreadPool.h"
35
#include "Rtp/RtpSelector.h"
xiongziliang committed
36
#include "FFmpegSource.h"
37 38 39
#if defined(ENABLE_RTPPROXY)
#include "Rtp/RtpServer.h"
#endif
40 41 42 43 44 45
using namespace Json;
using namespace toolkit;
using namespace mediakit;

namespace API {
typedef enum {
xiongziliang committed
46 47 48 49 50 51
    Exception = -400,//代码抛异常
    InvalidArgs = -300,//参数不合法
    SqlFailed = -200,//sql执行失败
    AuthFailed = -100,//鉴权失败
    OtherFailed = -1,//业务代码执行失败,
    Success = 0//执行成功
52 53 54
} ApiErr;

#define API_FIELD "api."
55 56
const string kApiDebug = API_FIELD"apiDebug";
const string kSecret = API_FIELD"secret";
57
const string kSnapRoot = API_FIELD"snapRoot";
58
const string kDefaultSnap = API_FIELD"defaultSnap";
59

60
static onceToken token([]() {
xiongziliang committed
61
    mINI::Instance()[kApiDebug] = "1";
62
    mINI::Instance()[kSecret] = "035c73f7-bb6b-4889-a715-d9eb2d1925cc";
63
    mINI::Instance()[kSnapRoot] = "./www/snap/";
64
    mINI::Instance()[kDefaultSnap] = "./www/logo.png";
65 66 67
});
}//namespace API

68

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
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;
};

86
class InvalidArgsException: public ApiRetException {
87
public:
88 89 90 91 92 93 94 95
    InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
    ~InvalidArgsException() = default;
};

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

98
#define API_ARGS1 SockInfo &sender,HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, ApiArgsType &allArgs, Json::Value &val
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
#define API_ARGS2 API_ARGS1, const HttpSession::HttpResponseInvoker &invoker
#define API_ARGS_VALUE1 sender,headerIn,headerOut,allArgs,val
#define API_ARGS_VALUE2 API_ARGS_VALUE1, invoker

typedef map<string, variant, StrCaseCompare> ApiArgsType;
//http api列表
static map<string, std::function<void(API_ARGS2)> > s_map_api;

template<typename FUNC>
static void api_regist1(const string &api_path, FUNC &&func) {
    s_map_api.emplace(api_path, [func](API_ARGS2) {
        func(API_ARGS_VALUE1);
        invoker("200 OK", headerOut, val.toStyledString());
    });
}

template<typename FUNC>
static void api_regist2(const string &api_path, FUNC &&func) {
    s_map_api.emplace(api_path, std::forward<FUNC>(func));
}
119 120

//获取HTTP请求中url参数、content参数
xiongziliang committed
121 122
static ApiArgsType getAllArgs(const Parser &parser) {
    ApiArgsType allArgs;
123
    if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
124 125
        auto contentArgs = parser.parseArgs(parser.Content());
        for (auto &pr : contentArgs) {
xiongziliang committed
126
            allArgs[pr.first] = HttpSession::urlDecode(pr.second);
127
        }
128
    } else if (parser["Content-Type"].find("application/json") == 0) {
xiongziliang committed
129 130 131 132 133
        try {
            stringstream ss(parser.Content());
            Value jsonArgs;
            ss >> jsonArgs;
            auto keys = jsonArgs.getMemberNames();
134
            for (auto key = keys.begin(); key != keys.end(); ++key) {
xiongziliang committed
135 136
                allArgs[*key] = jsonArgs[*key].asString();
            }
137
        } catch (std::exception &ex) {
xiongziliang committed
138
            WarnL << ex.what();
139
        }
140
    } else if (!parser["Content-Type"].empty()) {
xiongziliang committed
141
        WarnL << "invalid Content-Type:" << parser["Content-Type"];
142
    }
xiongziliang committed
143

144 145
    for (auto &pr :  parser.getUrlArgs()) {
        allArgs[pr.first] = pr.second;
xiongziliang committed
146
    }
147
    return allArgs;
148 149 150
}

static inline void addHttpListener(){
151
    GET_CONFIG(bool, api_debug, API::kApiDebug);
152 153 154 155 156 157 158 159
    //注册监听kBroadcastHttpRequest事件
    NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
        auto it = s_map_api.find(parser.Url());
        if (it == s_map_api.end()) {
            return;
        }
        //该api已被消费
        consumed = true;
160 161 162 163 164
        //执行API
        Json::Value val;
        val["code"] = API::Success;
        HttpSession::KeyValue headerOut;
        auto allArgs = getAllArgs(parser);
xiongziliang committed
165
        HttpSession::KeyValue &headerIn = parser.getHeader();
166 167
        GET_CONFIG(string,charSet,Http::kCharSet);
        headerOut["Content-Type"] = StrPrinter << "application/json; charset=" << charSet;
168 169 170
        if(api_debug){
            auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
                                                       const HttpSession::KeyValue &headerOut,
171
                                                       const HttpBody::Ptr &body){
172 173 174 175 176
                stringstream ss;
                for(auto &pr : allArgs ){
                    ss << pr.first << " : " << pr.second << "\r\n";
                }

177 178 179 180 181 182
                //body默认为空
                int64_t size = 0;
                if (body && body->remainSize()) {
                    //有body,获取body大小
                    size = body->remainSize();
                }
183

184
                if(size && size < 4 * 1024){
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
                    string contentOut = body->readData(size)->toString();
                    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);
                } else{
                    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 size:"
                           << size <<"\r\n";
                    invoker(codeOut,headerOut,body);
                }
200 201 202
            };
            ((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
        }
203

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

227
template <typename Args,typename First>
xiongziliang committed
228
bool checkArgs(Args &&args,First &&first){
229 230 231 232
    return !args[first].empty();
}

template <typename Args,typename First,typename ...KeyTypes>
xiongziliang committed
233 234
bool checkArgs(Args &&args,First &&first,KeyTypes && ...keys){
    return !args[first].empty() && checkArgs(std::forward<Args>(args),std::forward<KeyTypes>(keys)...);
235 236 237
}

#define CHECK_ARGS(...)  \
xiongziliang committed
238
    if(!checkArgs(allArgs,##__VA_ARGS__)){ \
239
        throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
240 241
    }

242
#define CHECK_SECRET() \
243 244 245 246 247
    if(sender.get_peer_ip() != "127.0.0.1"){ \
        CHECK_ARGS("secret"); \
        if(api_secret != allArgs["secret"]){ \
            throw AuthException("secret错误"); \
        } \
248 249
    }

250
//拉流代理器列表
251
static unordered_map<string ,PlayerProxy::Ptr> s_proxyMap;
252 253
static recursive_mutex s_proxyMapMtx;

254
//FFmpeg拉流代理器列表
255 256 257
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
static recursive_mutex s_ffmpegMapMtx;

258 259
#if defined(ENABLE_RTPPROXY)
//rtp服务器列表
260
static unordered_map<string, RtpServer::Ptr> s_rtpServerMap;
261 262 263 264 265 266 267
static recursive_mutex s_rtpServerMapMtx;
#endif

static inline string getProxyKey(const string &vhost,const string &app,const string &stream){
    return vhost + "/" + app + "/" + stream;
}

268 269 270 271 272
/**
 * 安装api接口
 * 所有api都支持GET和POST两种方式
 * POST方式参数支持application/json和application/x-www-form-urlencoded方式
 */
273 274
void installWebApi() {
    addHttpListener();
275
    GET_CONFIG(string,api_secret,API::kSecret);
276

277 278
    //获取线程负载
    //测试url http://127.0.0.1/index/api/getThreadsLoad
279
    api_regist2("/index/api/getThreadsLoad",[](API_ARGS2){
280 281 282
        EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
            Value val;
            auto vec = EventPollerPool::Instance().getExecutorLoad();
xiongziliang committed
283
            int i = API::Success;
284 285 286
            for (auto load : vec) {
                Value obj(objectValue);
                obj["load"] = load;
287 288 289
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
290
            val["code"] = API::Success;
291 292 293 294 295 296
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

    //获取后台工作线程负载
    //测试url http://127.0.0.1/index/api/getWorkThreadsLoad
297
    api_regist2("/index/api/getWorkThreadsLoad", [](API_ARGS2){
298 299 300 301 302 303 304
        WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
            Value val;
            auto vec = WorkThreadPool::Instance().getExecutorLoad();
            int i = 0;
            for (auto load : vec) {
                Value obj(objectValue);
                obj["load"] = load;
305 306 307
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
308
            val["code"] = API::Success;
309 310 311 312
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

313 314
    //获取服务器配置
    //测试url http://127.0.0.1/index/api/getServerConfig
315
    api_regist1("/index/api/getServerConfig",[](API_ARGS1){
316
        CHECK_SECRET();
317 318 319 320 321 322 323
        Value obj;
        for (auto &pr : mINI::Instance()) {
            obj[pr.first] = (string &) pr.second;
        }
        val["data"].append(obj);
    });

324 325 326
    //设置服务器配置
    //测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
    //你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参
327
    api_regist1("/index/api/setServerConfig",[](API_ARGS1){
328
        CHECK_SECRET();
329
        auto &ini = mINI::Instance();
xiongziliang committed
330
        int changed = API::Success;
331 332 333 334 335 336 337 338 339 340 341 342 343 344
        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);
345
            ini.dumpFile(g_ini_file);
346 347 348 349
        }
        val["changed"] = changed;
    });

350

351
    static auto s_get_api_list = [](API_ARGS1){
352
        CHECK_SECRET();
353 354 355
        for(auto &pr : s_map_api){
            val["data"].append(pr.first);
        }
356 357 358 359 360 361 362 363 364 365 366 367
    };

    //获取服务器api列表
    //测试url http://127.0.0.1/index/api/getApiList
    api_regist1("/index/api/getApiList",[](API_ARGS1){
        s_get_api_list(API_ARGS_VALUE1);
    });

    //获取服务器api列表
    //测试url http://127.0.0.1/index/
    api_regist1("/index/",[](API_ARGS1){
        s_get_api_list(API_ARGS_VALUE1);
368 369
    });

xiongziliang committed
370
#if !defined(_WIN32)
371 372
    //重启服务器,只有Daemon方式才能重启,否则是直接关闭!
    //测试url http://127.0.0.1/index/api/restartServer
373
    api_regist1("/index/api/restartServer",[](API_ARGS1){
374
        CHECK_SECRET();
375 376 377 378 379 380 381 382 383 384 385 386 387 388
        EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
            //尝试正常退出
            ::kill(getpid(), SIGINT);

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

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


392 393 394 395 396 397
    static auto makeMediaSourceJson = [](const MediaSource::Ptr &media){
        Value item;
        item["schema"] = media->getSchema();
        item["vhost"] = media->getVhost();
        item["app"] = media->getApp();
        item["stream"] = media->getId();
398 399 400
        item["createStamp"] = (Json::UInt64) media->getCreateStamp();
        item["aliveSecond"] = (Json::UInt64) media->getAliveSecond();
        item["bytesSpeed"] = media->getBytesSpeed();
401 402
        item["readerCount"] = media->readerCount();
        item["totalReaderCount"] = media->totalReaderCount();
403 404 405 406 407 408 409 410 411 412 413 414 415 416
        item["originType"] = (int) media->getOriginType();
        item["originTypeStr"] = getOriginTypeString(media->getOriginType());
        item["originUrl"] = media->getOriginUrl();
        auto originSock = media->getOriginSock();
        if (originSock) {
            item["originSock"]["local_ip"] = originSock->get_local_ip();
            item["originSock"]["local_port"] = originSock->get_local_port();
            item["originSock"]["peer_ip"] = originSock->get_peer_ip();
            item["originSock"]["peer_port"] = originSock->get_peer_port();
            item["originSock"]["identifier"] = originSock->getIdentifier();
        } else {
            item["originSock"] = Json::nullValue;
        }

417 418 419 420
        for(auto &track : media->getTracks()){
            Value obj;
            auto codec_type = track->getTrackType();
            obj["codec_id"] = track->getCodecId();
xiongziliang committed
421
            obj["codec_id_name"] = track->getCodecName();
422 423 424 425 426 427 428 429 430 431 432 433 434 435
            obj["ready"] = track->ready();
            obj["codec_type"] = codec_type;
            switch(codec_type){
                case TrackAudio : {
                    auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
                    obj["sample_rate"] = audio_track->getAudioSampleRate();
                    obj["channels"] = audio_track->getAudioChannel();
                    obj["sample_bit"] = audio_track->getAudioSampleBit();
                    break;
                }
                case TrackVideo : {
                    auto video_track = dynamic_pointer_cast<VideoTrack>(track);
                    obj["width"] = video_track->getVideoWidth();
                    obj["height"] = video_track->getVideoHeight();
xiongziliang committed
436
                    obj["fps"] = round(video_track->getVideoFps());
437 438 439 440 441 442 443 444 445 446
                    break;
                }
                default:
                    break;
            }
            item["tracks"].append(obj);
        }
        return item;
    };

447 448 449 450
    //获取流列表,可选筛选参数
    //测试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
451
    api_regist1("/index/api/getMediaList",[](API_ARGS1){
452
        CHECK_SECRET();
453
        //获取所有MediaSource列表
xiongziliang committed
454 455
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
456 457
                return;
            }
xiongziliang committed
458
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
459 460
                return;
            }
xiongziliang committed
461
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
462 463
                return;
            }
464
            val["data"].append(makeMediaSourceJson(media));
465 466 467
        });
    });

468
    //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
469
    api_regist1("/index/api/isMediaOnline",[](API_ARGS1){
470 471
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
472
        val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]));
473 474
    });

475
    //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
476
    api_regist1("/index/api/getMediaInfo",[](API_ARGS1){
477 478
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
479
        auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]);
480 481 482 483
        if(!src){
            val["online"] = false;
            return;
        }
484
        val = makeMediaSourceJson(src);
485
        val["online"] = true;
486
        val["code"] = API::Success;
487 488
    });

489
    //主动关断流,包括关断拉流、推流
490
    //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
491
    api_regist1("/index/api/close_stream",[](API_ARGS1){
492
        CHECK_SECRET();
493
        CHECK_ARGS("schema","vhost","app","stream");
494 495 496 497 498 499
        //踢掉推流器
        auto src = MediaSource::find(allArgs["schema"],
                                     allArgs["vhost"],
                                     allArgs["app"],
                                     allArgs["stream"]);
        if(src){
500
            bool flag = src->close(allArgs["force"].as<bool>());
501
            val["result"] = flag ? 0 : -1;
502
            val["msg"] = flag ? "success" : "close failed";
xiongziliang committed
503
            val["code"] = API::OtherFailed;
504
        }else{
505
            val["result"] = -2;
506
            val["msg"] = "can not find the stream";
xiongziliang committed
507
            val["code"] = API::OtherFailed;
508 509 510
        }
    });

511 512
    //批量主动关断流,包括关断拉流、推流
    //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
513
    api_regist1("/index/api/close_streams",[](API_ARGS1){
514 515 516 517
        CHECK_SECRET();
        //筛选命中个数
        int count_hit = 0;
        int count_closed = 0;
518
        list<MediaSource::Ptr> media_list;
xiongziliang committed
519 520
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
521 522
                return;
            }
xiongziliang committed
523
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
524 525
                return;
            }
xiongziliang committed
526
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
527 528
                return;
            }
xiongziliang committed
529
            if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
530 531 532
                return;
            }
            ++count_hit;
533 534 535 536 537 538
            media_list.emplace_back(media);
        });

        bool force = allArgs["force"].as<bool>();
        for(auto &media : media_list){
            if(media->close(force)){
539 540
                ++count_closed;
            }
541
        }
542 543 544 545
        val["count_hit"] = count_hit;
        val["count_closed"] = count_closed;
    });

546 547 548
    //获取所有TcpSession列表信息
    //可以根据本地端口和远端ip来筛选
    //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
549
    api_regist1("/index/api/getAllSession",[](API_ARGS1){
550 551 552 553 554 555
        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
556
            if(local_port != 0 && local_port != session->get_local_port()){
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
                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
574
    api_regist1("/index/api/kick_session",[](API_ARGS1){
575
        CHECK_SECRET();
576
        CHECK_ARGS("id");
577
        //踢掉tcp会话
xiongziliang committed
578
        auto session = SessionMap::Instance().get(allArgs["id"]);
579
        if(!session){
580
            throw ApiRetException("can not find the target",API::OtherFailed);
581 582 583 584
        }
        session->safeShutdown();
    });

585 586 587

    //批量断开tcp连接,比如说可以断开rtsp、rtmp播放器等
    //测试url http://127.0.0.1/index/api/kick_sessions?local_port=1935
588
    api_regist1("/index/api/kick_sessions",[](API_ARGS1){
589 590 591 592 593
        CHECK_SECRET();
        uint16_t local_port = allArgs["local_port"].as<uint16_t>();
        string &peer_ip = allArgs["peer_ip"];
        uint64_t count_hit = 0;

594
        list<TcpSession::Ptr> session_list;
595 596 597 598 599 600 601
        SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){
            if(local_port != 0 && local_port != session->get_local_port()){
                return;
            }
            if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){
                return;
            }
602
            session_list.emplace_back(session);
603 604
            ++count_hit;
        });
605 606 607 608

        for(auto &session : session_list){
            session->safeShutdown();
        }
609 610 611
        val["count_hit"] = (Json::UInt64)count_hit;
    });

612 613 614 615 616 617 618 619 620
    static auto addStreamProxy = [](const string &vhost,
                                    const string &app,
                                    const string &stream,
                                    const string &url,
                                    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);
621 622 623 624 625 626
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        if(s_proxyMap.find(key) != s_proxyMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }
xiongziliang committed
627
        //添加拉流代理
628
        PlayerProxy::Ptr player(new PlayerProxy(vhost, app, stream, enable_hls, enable_mp4));
629 630
        s_proxyMap[key] = player;
        
xiongziliang committed
631
        //指定RTP over TCP(播放rtsp时有效)
632
        (*player)[kRtpType] = rtp_type;
633
        //开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
634
        player->setPlayCallbackOnce([cb,key](const SockException &ex){
635
            if(ex){
636
                lock_guard<recursive_mutex> lck(s_proxyMapMtx);
637
                s_proxyMap.erase(key);
638
            }
639
            cb(ex,key);
640
        });
641 642 643 644 645 646

        //被主动关闭拉流
        player->setOnClose([key](){
            lock_guard<recursive_mutex> lck(s_proxyMapMtx);
            s_proxyMap.erase(key);
        });
647 648 649 650
        player->play(url);
    };

    //动态添加rtsp/rtmp拉流代理
651
    //测试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
652
    api_regist2("/index/api/addStreamProxy",[](API_ARGS2){
653
        CHECK_SECRET();
654
        CHECK_ARGS("vhost","app","stream","url");
655 656 657 658
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       allArgs["url"],
659 660
                       allArgs["enable_hls"],/* 是否hls转发 */
                       allArgs["enable_mp4"],/* 是否MP4录制 */
661 662 663 664 665 666 667 668 669 670
                       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
671 672
    });

673 674
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
675
    api_regist1("/index/api/delStreamProxy",[](API_ARGS1){
676
        CHECK_SECRET();
677
        CHECK_ARGS("key");
xiongziliang committed
678
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
679
        val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
xiongziliang committed
680 681
    });

xiongziliang committed
682
    static auto addFFmpegSource = [](const string &src_url,
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
                                     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
712
    api_regist2("/index/api/addFFmpegSource",[](API_ARGS2){
713 714 715 716 717 718
        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"];

xiongziliang committed
719
        addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
720 721 722 723 724 725 726 727 728 729
            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
730

731
    static auto api_delFFmpegSource = [](API_ARGS1){
732 733 734 735
        CHECK_SECRET();
        CHECK_ARGS("key");
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
xiongziliang committed
736 737 738 739
    };

    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
740 741
    api_regist1("/index/api/delFFmpegSource",[](API_ARGS1){
        api_delFFmpegSource(API_ARGS_VALUE1);
xiongziliang committed
742 743 744
    });

    //此处为了兼容之前的拼写错误
745 746
    api_regist1("/index/api/delFFmepgSource",[](API_ARGS1){
        api_delFFmpegSource(API_ARGS_VALUE1);
747
    });
xiongziliang committed
748

749 750
    //新增http api下载可执行程序文件接口
    //测试url http://127.0.0.1/index/api/downloadBin
751
    api_regist2("/index/api/downloadBin",[](API_ARGS2){
752
        CHECK_SECRET();
xiongziliang committed
753
        invoker.responseFile(headerIn,StrCaseMap(),exePath());
754 755
    });

756
#if defined(ENABLE_RTPPROXY)
757
    api_regist1("/index/api/getRtpInfo",[](API_ARGS1){
758
        CHECK_SECRET();
759
        CHECK_ARGS("stream_id");
760

761 762
        auto process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
        if (!process) {
763 764 765 766 767 768
            val["exist"] = false;
            return;
        }
        val["exist"] = true;
        val["peer_ip"] = process->get_peer_ip();
        val["peer_port"] = process->get_peer_port();
xiongziliang committed
769 770
        val["local_port"] = process->get_local_port();
        val["local_ip"] = process->get_local_ip();
771
    });
772 773 774

    api_regist1("/index/api/openRtpServer",[](API_ARGS1){
        CHECK_SECRET();
775
        CHECK_ARGS("port", "enable_tcp", "stream_id");
776

777
        auto stream_id = allArgs["stream_id"];
778 779 780 781 782 783 784

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
        if(s_rtpServerMap.find(stream_id) != s_rtpServerMap.end()) {
            //为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的stream_id
            throw InvalidArgsException("该stream_id已存在");
        }

785
        RtpServer::Ptr server = std::make_shared<RtpServer>();
786 787
        server->start(allArgs["port"], stream_id, allArgs["enable_tcp"].as<bool>());
        server->setOnDetach([stream_id]() {
788 789
            //设置rtp超时移除事件
            lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
790
            s_rtpServerMap.erase(stream_id);
791
        });
792

793 794
        //保存对象
        s_rtpServerMap.emplace(stream_id, server);
795
        //回复json
796
        val["port"] = server->getPort();
797 798 799 800
    });

    api_regist1("/index/api/closeRtpServer",[](API_ARGS1){
        CHECK_SECRET();
801
        CHECK_ARGS("stream_id");
802 803

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
804 805 806 807 808 809 810 811
        auto it = s_rtpServerMap.find(allArgs["stream_id"]);
        if(it == s_rtpServerMap.end()){
            val["hit"] = 0;
            return;
        }
        auto server = it->second;
        s_rtpServerMap.erase(it);
        val["hit"] = 1;
812 813
    });

814 815 816 817
    api_regist1("/index/api/listRtpServer",[](API_ARGS1){
        CHECK_SECRET();

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
818 819 820 821 822
        for (auto &pr : s_rtpServerMap) {
            Value obj;
            obj["stream_id"] = pr.first;
            obj["port"] = pr.second->getPort();
            val["data"].append(obj);
823 824
        }
    });
825

826 827 828 829
    api_regist2("/index/api/startSendRtp",[](API_ARGS2){
        CHECK_SECRET();
        CHECK_ARGS("vhost", "app", "stream", "ssrc", "dst_url", "dst_port", "is_udp");

830
        auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
        if (!src) {
            throw ApiRetException("该媒体流不存在", API::OtherFailed);
        }

        src->startSendRtp(allArgs["dst_url"], allArgs["dst_port"], allArgs["ssrc"], allArgs["is_udp"], [val, headerOut, invoker](const SockException &ex){
            if (ex) {
                const_cast<Value &>(val)["code"] = API::OtherFailed;
                const_cast<Value &>(val)["msg"] = ex.what();
            }
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

    api_regist1("/index/api/stopSendRtp",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("vhost", "app", "stream");

848
        auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
849 850 851 852
        if (!src) {
            throw ApiRetException("该媒体流不存在", API::OtherFailed);
        }

853 854 855
        if (!src->stopSendRtp()) {
            throw ApiRetException("尚未开始推流,停止失败", API::OtherFailed);
        }
856 857 858
    });


859
#endif//ENABLE_RTPPROXY
860

861 862 863 864
    // 开始录制hls或MP4
    api_regist1("/index/api/startRecord",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
865
        auto result = Recorder::startRecord((Recorder::type) allArgs["type"].as<int>(),
866 867 868 869
                                              allArgs["vhost"],
                                              allArgs["app"],
                                              allArgs["stream"],
                                              allArgs["customized_path"]);
xiongziliang committed
870 871
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
872
        val["msg"] = result ? "success" :  "start record failed";
873 874 875 876 877 878
    });

    // 停止录制hls或MP4
    api_regist1("/index/api/stopRecord",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
879
        auto result = Recorder::stopRecord((Recorder::type) allArgs["type"].as<int>(),
880 881 882
                                             allArgs["vhost"],
                                             allArgs["app"],
                                             allArgs["stream"]);
xiongziliang committed
883 884
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
885
        val["msg"] = result ? "success" :  "stop record failed";
886 887 888 889 890 891 892 893 894 895 896 897
    });

    // 获取hls或MP4录制状态
    api_regist1("/index/api/isRecording",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
        val["status"] = Recorder::isRecording((Recorder::type) allArgs["type"].as<int>(),
                                              allArgs["vhost"],
                                              allArgs["app"],
                                              allArgs["stream"]);
    });

898 899 900
    //获取录像文件夹列表或mp4文件列表
    //http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
    api_regist1("/index/api/getMp4RecordFile", [](API_ARGS1){
901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
        CHECK_SECRET();
        CHECK_ARGS("vhost", "app", "stream");
        auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"],allArgs["stream"]);
        auto period = allArgs["period"];

        //判断是获取mp4文件列表还是获取文件夹列表
        bool search_mp4 = period.size() == sizeof("2020-02-01") - 1;
        if (search_mp4) {
            record_path = record_path + period + "/";
        }

        Json::Value paths(arrayValue);
        //这是筛选日期,获取文件夹列表
        File::scanDir(record_path, [&](const string &path, bool isDir) {
            int pos = path.rfind('/');
            if (pos != string::npos) {
                string relative_path = path.substr(pos + 1);
                if (search_mp4) {
                    if (!isDir) {
                        //我们只收集mp4文件,对文件夹不感兴趣
                        paths.append(relative_path);
                    }
                } else if (isDir && relative_path.find(period) == 0) {
                    //匹配到对应日期的文件夹
                    paths.append(relative_path);
                }
            }
            return true;
        }, false);
liuziloong committed
930

931 932
        val["data"]["rootPath"] = record_path;
        val["data"]["paths"] = paths;
933
    });
liuziloong committed
934

935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
    static auto responseSnap = [](const string &snap_path,
                                  const HttpSession::KeyValue &headerIn,
                                  const HttpSession::HttpResponseInvoker &invoker) {
        StrCaseMap headerOut;
        struct stat statbuf = {0};
        GET_CONFIG(string, defaultSnap, API::kDefaultSnap);
        if (!(stat(snap_path.data(), &statbuf) == 0 && statbuf.st_size != 0) && !defaultSnap.empty()) {
            //空文件且设置了预设图,则返回预设图片(也就是FFmpeg生成截图中空档期的默认图片)
            const_cast<string&>(snap_path) = File::absolutePath(defaultSnap, "");
            headerOut["Content-Type"] = HttpFileManager::getContentType(snap_path.data());
        } else {
            //之前生成的截图文件,我们默认为jpeg格式
            headerOut["Content-Type"] = HttpFileManager::getContentType(".jpeg");
        }
        //返回图片给http客户端
        invoker.responseFile(headerIn, headerOut, snap_path);
    };
952 953 954 955 956 957

    //获取截图缓存或者实时截图
    //http://127.0.0.1/index/api/getSnap?url=rtmp://127.0.0.1/record/robot.mp4&timeout_sec=10&expire_sec=3
    api_regist2("/index/api/getSnap", [](API_ARGS2){
        CHECK_SECRET();
        CHECK_ARGS("url", "timeout_sec", "expire_sec");
958 959
        GET_CONFIG(string, snap_root, API::kSnapRoot);

960
        bool have_old_snap = false, res_old_snap = false;
961
        int expire_sec = allArgs["expire_sec"];
962
        auto scan_path = File::absolutePath(MD5(allArgs["url"]).hexdigest(), snap_root) + "/";
963 964
        string new_snap = StrPrinter << scan_path << time(NULL) << ".jpeg";

965
        File::scanDir(scan_path, [&](const string &path, bool isDir) {
966 967
            if (isDir || !end_with(path, ".jpeg")) {
                //忽略文件夹或其他类型的文件
968
                return true;
969
            }
970 971 972 973

            //找到截图
            auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg");
            if (atoll(tm.data()) + expire_sec < time(NULL)) {
974 975 976
                //截图已经过期,改名,以便再次请求时,可以返回老截图
                rename(path.data(), new_snap.data());
                have_old_snap = true;
977 978 979
                return true;
            }

980 981 982 983
            //截图存在,且未过期,那么返回之
            res_old_snap = true;
            responseSnap(path, headerIn, invoker);
            //中断遍历
984
            return false;
985 986
        });

987 988
        if (res_old_snap) {
            //已经回复了旧的截图
989
            return;
990 991 992
        }

        //无截图或者截图已经过期
993 994 995 996 997 998 999
        if (!have_old_snap) {
            //无过期截图,生成一个空文件,目的是顺便创建文件夹路径
            //同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程
            auto file = File::create_file(new_snap.data(), "wb");
            if (file) {
                fclose(file);
            }
1000 1001
        }

1002 1003 1004 1005
        //启动FFmpeg进程,开始截图,生成临时文件,截图成功后替换为正式文件
        auto new_snap_tmp = new_snap + ".tmp";
        FFmpegSnap::makeSnap(allArgs["url"], new_snap_tmp, allArgs["timeout_sec"], [invoker, headerIn, new_snap, new_snap_tmp](bool success) {
            if (!success) {
1006
                //生成截图失败,可能残留空文件
1007 1008 1009 1010 1011
                File::delete_file(new_snap_tmp.data());
            } else {
                //临时文件改成正式文件
                File::delete_file(new_snap.data());
                rename(new_snap_tmp.data(), new_snap.data());
1012
            }
1013
            responseSnap(new_snap, headerIn, invoker);
1014 1015 1016
        });
    });

1017
    ////////////以下是注册的Hook API////////////
1018
    api_regist1("/index/hook/on_publish",[](API_ARGS1){
1019
        //开始推流事件
1020 1021 1022 1023 1024 1025
        //转换成rtsp或rtmp
        val["enableRtxp"] = true;
        //转换hls
        val["enableHls"] = true;
        //不录制mp4
        val["enableMP4"] = false;
1026 1027
    });

1028
    api_regist1("/index/hook/on_play",[](API_ARGS1){
1029 1030 1031
        //开始播放事件
    });

1032
    api_regist1("/index/hook/on_flow_report",[](API_ARGS1){
1033 1034
        //流量统计hook api
    });
xiongziliang committed
1035

1036
    api_regist1("/index/hook/on_rtsp_realm",[](API_ARGS1){
1037
        //rtsp是否需要鉴权,默认需要鉴权
xiongziliang committed
1038
        val["code"] = API::Success;
xiongziliang committed
1039 1040 1041
        val["realm"] = "zlmediakit_reaml";
    });

1042
    api_regist1("/index/hook/on_rtsp_auth",[](API_ARGS1){
xiongziliang committed
1043 1044
        //rtsp鉴权密码,密码等于用户名
        //rtsp可以有双重鉴权!后面还会触发on_play事件
1045
        CHECK_ARGS("user_name");
xiongziliang committed
1046
        val["code"] = API::Success;
xiongziliang committed
1047
        val["encrypted"] = false;
1048
        val["passwd"] = allArgs["user_name"].data();
xiongziliang committed
1049 1050
    });

1051
    api_regist1("/index/hook/on_stream_changed",[](API_ARGS1){
xiongziliang committed
1052 1053 1054
        //媒体注册或反注册事件
    });

xiongziliang committed
1055 1056

#if !defined(_WIN32)
1057
    api_regist2("/index/hook/on_stream_not_found_ffmpeg",[](API_ARGS2){
1058
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
1059 1060
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
1061 1062
        //通过FFmpeg按需拉流
        GET_CONFIG(int,rtmp_port,Rtmp::kPort);
1063 1064
        GET_CONFIG(int,timeout_sec,Hook::kTimeoutSec);

1065 1066 1067 1068 1069 1070 1071
        string dst_url = StrPrinter
                << "rtmp://127.0.0.1:"
                << rtmp_port << "/"
                << allArgs["app"] << "/"
                << allArgs["stream"] << "?vhost="
                << allArgs["vhost"];

xiongziliang committed
1072
        addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
1073
                        dst_url,
1074
                        (1000 * timeout_sec) - 500,
1075 1076 1077 1078 1079 1080 1081 1082 1083
                        [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
1084
    });
1085
#endif//!defined(_WIN32)
xiongziliang committed
1086

1087
    api_regist2("/index/hook/on_stream_not_found",[](API_ARGS2){
xiongziliang committed
1088 1089 1090
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
1091 1092 1093 1094 1095
        //通过内置支持的rtsp/rtmp按需拉流
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       /** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
1096 1097 1098
                       "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
                       true,/* 开启hls转发 */
                       false,/* 禁用MP4录制 */
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
                       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
1109 1110
    });

1111
    api_regist1("/index/hook/on_record_mp4",[](API_ARGS1){
xiongziliang committed
1112 1113 1114
        //录制mp4分片完毕事件
    });

1115
    api_regist1("/index/hook/on_shell_login",[](API_ARGS1){
1116 1117
        //shell登录调试事件
    });
1118

1119
    api_regist1("/index/hook/on_stream_none_reader",[](API_ARGS1){
1120 1121 1122 1123
        //无人观看流默认关闭
        val["close"] = true;
    });

1124 1125 1126 1127 1128
    static auto checkAccess = [](const string &params){
        //我们假定大家都要权限访问
        return true;
    };

1129
    api_regist1("/index/hook/on_http_access",[](API_ARGS1){
1130 1131 1132
        //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
        if(!checkAccess(allArgs["params"])){
            //无访问权限
1133 1134
            val["err"] = "无访问权限";
            //仅限制访问当前目录
1135
            val["path"] = "";
1136
            //标记该客户端无权限1分钟
1137 1138 1139 1140
            val["second"] = 60;
            return;
        }

1141 1142 1143 1144 1145
        //可以访问
        val["err"] = "";
        //只能访问当前目录
        val["path"] = "";
        //该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
1146
        val["second"] = 10 * 60;
1147 1148
    });

1149

1150
    api_regist1("/index/hook/on_server_started",[](API_ARGS1){
1151 1152 1153 1154
        //服务器重启报告
    });


1155
}
xiongziliang committed
1156

1157
void unInstallWebApi(){
1158 1159
    RtpSelector::Instance().clear();

1160 1161 1162 1163 1164 1165 1166 1167 1168
    {
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        s_proxyMap.clear();
    }

    {
        lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
        s_ffmpegMap.clear();
    }
1169

1170 1171 1172 1173 1174 1175
    {
#if defined(ENABLE_RTPPROXY)
        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
        s_rtpServerMap.clear();
#endif
    }
1176
}