WebApi.cpp 42.5 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 std::move(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 398 399 400 401 402 403
    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();
        item["readerCount"] = media->readerCount();
        item["totalReaderCount"] = media->totalReaderCount();
        for(auto &track : media->getTracks()){
            Value obj;
            auto codec_type = track->getTrackType();
            obj["codec_id"] = track->getCodecId();
xiongziliang committed
404
            obj["codec_id_name"] = track->getCodecName();
405 406 407 408 409 410 411 412 413 414 415 416 417 418
            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
419
                    obj["fps"] = round(video_track->getVideoFps());
420 421 422 423 424 425 426 427 428 429
                    break;
                }
                default:
                    break;
            }
            item["tracks"].append(obj);
        }
        return item;
    };

430 431 432 433
    //获取流列表,可选筛选参数
    //测试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
434
    api_regist1("/index/api/getMediaList",[](API_ARGS1){
435
        CHECK_SECRET();
436
        //获取所有MediaSource列表
xiongziliang committed
437 438
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
439 440
                return;
            }
xiongziliang committed
441
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
442 443
                return;
            }
xiongziliang committed
444
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
445 446
                return;
            }
447
            val["data"].append(makeMediaSourceJson(media));
448 449 450
        });
    });

451
    //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
452
    api_regist1("/index/api/isMediaOnline",[](API_ARGS1){
453 454
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
455
        val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]));
456 457
    });

458
    //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
459
    api_regist1("/index/api/getMediaInfo",[](API_ARGS1){
460 461
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
462
        auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]);
463 464 465 466
        if(!src){
            val["online"] = false;
            return;
        }
467
        val = makeMediaSourceJson(src);
468
        val["online"] = true;
469
        val["code"] = API::Success;
470 471
    });

472
    //主动关断流,包括关断拉流、推流
473
    //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
474
    api_regist1("/index/api/close_stream",[](API_ARGS1){
475
        CHECK_SECRET();
476
        CHECK_ARGS("schema","vhost","app","stream");
477 478 479 480 481 482
        //踢掉推流器
        auto src = MediaSource::find(allArgs["schema"],
                                     allArgs["vhost"],
                                     allArgs["app"],
                                     allArgs["stream"]);
        if(src){
483
            bool flag = src->close(allArgs["force"].as<bool>());
484
            val["result"] = flag ? 0 : -1;
485
            val["msg"] = flag ? "success" : "close failed";
xiongziliang committed
486
            val["code"] = API::OtherFailed;
487
        }else{
488
            val["result"] = -2;
489
            val["msg"] = "can not find the stream";
xiongziliang committed
490
            val["code"] = API::OtherFailed;
491 492 493
        }
    });

494 495
    //批量主动关断流,包括关断拉流、推流
    //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
496
    api_regist1("/index/api/close_streams",[](API_ARGS1){
497 498 499 500
        CHECK_SECRET();
        //筛选命中个数
        int count_hit = 0;
        int count_closed = 0;
501
        list<MediaSource::Ptr> media_list;
xiongziliang committed
502 503
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
504 505
                return;
            }
xiongziliang committed
506
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
507 508
                return;
            }
xiongziliang committed
509
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
510 511
                return;
            }
xiongziliang committed
512
            if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
513 514 515
                return;
            }
            ++count_hit;
516 517 518 519 520 521
            media_list.emplace_back(media);
        });

        bool force = allArgs["force"].as<bool>();
        for(auto &media : media_list){
            if(media->close(force)){
522 523
                ++count_closed;
            }
524
        }
525 526 527 528
        val["count_hit"] = count_hit;
        val["count_closed"] = count_closed;
    });

529 530 531
    //获取所有TcpSession列表信息
    //可以根据本地端口和远端ip来筛选
    //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
532
    api_regist1("/index/api/getAllSession",[](API_ARGS1){
533 534 535 536 537 538
        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
539
            if(local_port != 0 && local_port != session->get_local_port()){
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
                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
557
    api_regist1("/index/api/kick_session",[](API_ARGS1){
558
        CHECK_SECRET();
559
        CHECK_ARGS("id");
560
        //踢掉tcp会话
xiongziliang committed
561
        auto session = SessionMap::Instance().get(allArgs["id"]);
562
        if(!session){
563
            throw ApiRetException("can not find the target",API::OtherFailed);
564 565 566 567
        }
        session->safeShutdown();
    });

568 569 570

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

577
        list<TcpSession::Ptr> session_list;
578 579 580 581 582 583 584
        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;
            }
585
            session_list.emplace_back(session);
586 587
            ++count_hit;
        });
588 589 590 591

        for(auto &session : session_list){
            session->safeShutdown();
        }
592 593 594
        val["count_hit"] = (Json::UInt64)count_hit;
    });

595 596 597 598 599 600 601 602 603
    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);
604 605 606 607 608 609
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        if(s_proxyMap.find(key) != s_proxyMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }
xiongziliang committed
610
        //添加拉流代理
611
        PlayerProxy::Ptr player(new PlayerProxy(vhost, app, stream, enable_hls, enable_mp4));
612 613
        s_proxyMap[key] = player;
        
xiongziliang committed
614
        //指定RTP over TCP(播放rtsp时有效)
615
        (*player)[kRtpType] = rtp_type;
616
        //开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
617
        player->setPlayCallbackOnce([cb,key](const SockException &ex){
618
            if(ex){
619
                lock_guard<recursive_mutex> lck(s_proxyMapMtx);
620
                s_proxyMap.erase(key);
621
            }
622
            cb(ex,key);
623
        });
624 625 626 627 628 629

        //被主动关闭拉流
        player->setOnClose([key](){
            lock_guard<recursive_mutex> lck(s_proxyMapMtx);
            s_proxyMap.erase(key);
        });
630 631 632 633
        player->play(url);
    };

    //动态添加rtsp/rtmp拉流代理
634
    //测试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
635
    api_regist2("/index/api/addStreamProxy",[](API_ARGS2){
636
        CHECK_SECRET();
637
        CHECK_ARGS("vhost","app","stream","url");
638 639 640 641
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       allArgs["url"],
642 643
                       allArgs["enable_hls"],/* 是否hls转发 */
                       allArgs["enable_mp4"],/* 是否MP4录制 */
644 645 646 647 648 649 650 651 652 653
                       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
654 655
    });

656 657
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
658
    api_regist1("/index/api/delStreamProxy",[](API_ARGS1){
659
        CHECK_SECRET();
660
        CHECK_ARGS("key");
xiongziliang committed
661
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
662
        val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
xiongziliang committed
663 664
    });

xiongziliang committed
665
    static auto addFFmpegSource = [](const string &src_url,
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
                                     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
695
    api_regist2("/index/api/addFFmpegSource",[](API_ARGS2){
696 697 698 699 700 701
        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
702
        addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
703 704 705 706 707 708 709 710 711 712
            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
713

714
    static auto api_delFFmpegSource = [](API_ARGS1){
715 716 717 718
        CHECK_SECRET();
        CHECK_ARGS("key");
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
xiongziliang committed
719 720 721 722
    };

    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
723 724
    api_regist1("/index/api/delFFmpegSource",[](API_ARGS1){
        api_delFFmpegSource(API_ARGS_VALUE1);
xiongziliang committed
725 726 727
    });

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

732 733
    //新增http api下载可执行程序文件接口
    //测试url http://127.0.0.1/index/api/downloadBin
734
    api_regist2("/index/api/downloadBin",[](API_ARGS2){
735
        CHECK_SECRET();
xiongziliang committed
736
        invoker.responseFile(headerIn,StrCaseMap(),exePath());
737 738
    });

739
#if defined(ENABLE_RTPPROXY)
740
    api_regist1("/index/api/getRtpInfo",[](API_ARGS1){
741
        CHECK_SECRET();
742
        CHECK_ARGS("stream_id");
743

744 745
        auto process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
        if (!process) {
746 747 748 749 750 751
            val["exist"] = false;
            return;
        }
        val["exist"] = true;
        val["peer_ip"] = process->get_peer_ip();
        val["peer_port"] = process->get_peer_port();
xiongziliang committed
752 753
        val["local_port"] = process->get_local_port();
        val["local_ip"] = process->get_local_ip();
754
    });
755 756 757

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

760
        auto stream_id = allArgs["stream_id"];
761 762 763 764 765 766 767

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

768
        RtpServer::Ptr server = std::make_shared<RtpServer>();
769 770
        server->start(allArgs["port"], stream_id, allArgs["enable_tcp"].as<bool>());
        server->setOnDetach([stream_id]() {
771 772
            //设置rtp超时移除事件
            lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
773
            s_rtpServerMap.erase(stream_id);
774
        });
775

776 777
        //保存对象
        s_rtpServerMap.emplace(stream_id, server);
778
        //回复json
779
        val["port"] = server->getPort();
780 781 782 783
    });

    api_regist1("/index/api/closeRtpServer",[](API_ARGS1){
        CHECK_SECRET();
784
        CHECK_ARGS("stream_id");
785 786

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
787 788 789 790 791 792 793 794
        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;
795 796
    });

797 798 799 800
    api_regist1("/index/api/listRtpServer",[](API_ARGS1){
        CHECK_SECRET();

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
801 802 803 804 805
        for (auto &pr : s_rtpServerMap) {
            Value obj;
            obj["stream_id"] = pr.first;
            obj["port"] = pr.second->getPort();
            val["data"].append(obj);
806 807
        }
    });
808

809 810 811 812
    api_regist2("/index/api/startSendRtp",[](API_ARGS2){
        CHECK_SECRET();
        CHECK_ARGS("vhost", "app", "stream", "ssrc", "dst_url", "dst_port", "is_udp");

813
        auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
        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");

831
        auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
832 833 834 835
        if (!src) {
            throw ApiRetException("该媒体流不存在", API::OtherFailed);
        }

836 837 838
        if (!src->stopSendRtp()) {
            throw ApiRetException("尚未开始推流,停止失败", API::OtherFailed);
        }
839 840 841
    });


842
#endif//ENABLE_RTPPROXY
843

844 845 846 847
    // 开始录制hls或MP4
    api_regist1("/index/api/startRecord",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
848
        auto result = Recorder::startRecord((Recorder::type) allArgs["type"].as<int>(),
849 850 851 852
                                              allArgs["vhost"],
                                              allArgs["app"],
                                              allArgs["stream"],
                                              allArgs["customized_path"]);
xiongziliang committed
853 854
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
855
        val["msg"] = result ? "success" :  "start record failed";
856 857 858 859 860 861
    });

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

    // 获取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"]);
    });

881 882 883
    //获取录像文件夹列表或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){
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
        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
913

914 915
        val["data"]["rootPath"] = record_path;
        val["data"]["paths"] = paths;
916
    });
liuziloong committed
917

918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
    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);
    };
935 936 937 938 939 940

    //获取截图缓存或者实时截图
    //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");
941 942
        GET_CONFIG(string, snap_root, API::kSnapRoot);

943
        int expire_sec = allArgs["expire_sec"];
944 945 946 947 948 949
        auto scan_path = File::absolutePath(MD5(allArgs["url"]).hexdigest(), snap_root) + "/";
        string snap_path;
        File::scanDir(scan_path, [&](const string &path, bool isDir) {
            if (isDir) {
                //忽略文件夹
                return true;
950
            }
951 952 953 954 955 956 957 958 959 960 961 962

            //找到截图
            auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg");
            if (atoll(tm.data()) + expire_sec < time(NULL)) {
                //截图已经过期,删除之,后面重新生成
                File::delete_file(path.data());
                return true;
            }

            //截图未过期,中断遍历,返回上次生成的截图
            snap_path = path;
            return false;
963 964
        });

965
        if(!snap_path.empty()){
966
            responseSnap(snap_path, headerIn, invoker);
967
            return;
968 969 970
        }

        //无截图或者截图已经过期
971
        snap_path = StrPrinter << scan_path << time(NULL) << ".jpeg";
972

973 974 975 976 977
        //生成一个空文件,目的是顺便创建文件夹路径,
        //同时防止在FFmpeg生成截图途中不停的尝试调用该api启动FFmpeg生成相同的截图
        auto file = File::create_file(snap_path.data(), "wb");
        if (file) {
            fclose(file);
978 979
        }

980
        //启动FFmpeg进程,开始截图
981
        FFmpegSnap::makeSnap(allArgs["url"],snap_path,allArgs["timeout_sec"],[invoker,headerIn,snap_path](bool success){
982 983
            if(!success){
                //生成截图失败,可能残留空文件
984
                File::delete_file(snap_path.data());
985
            }
986
            responseSnap(snap_path, headerIn, invoker);
987 988 989
        });
    });

990
    ////////////以下是注册的Hook API////////////
991
    api_regist1("/index/hook/on_publish",[](API_ARGS1){
992
        //开始推流事件
993 994 995 996 997 998
        //转换成rtsp或rtmp
        val["enableRtxp"] = true;
        //转换hls
        val["enableHls"] = true;
        //不录制mp4
        val["enableMP4"] = false;
999 1000
    });

1001
    api_regist1("/index/hook/on_play",[](API_ARGS1){
1002 1003 1004
        //开始播放事件
    });

1005
    api_regist1("/index/hook/on_flow_report",[](API_ARGS1){
1006 1007
        //流量统计hook api
    });
xiongziliang committed
1008

1009
    api_regist1("/index/hook/on_rtsp_realm",[](API_ARGS1){
1010
        //rtsp是否需要鉴权,默认需要鉴权
xiongziliang committed
1011
        val["code"] = API::Success;
xiongziliang committed
1012 1013 1014
        val["realm"] = "zlmediakit_reaml";
    });

1015
    api_regist1("/index/hook/on_rtsp_auth",[](API_ARGS1){
xiongziliang committed
1016 1017
        //rtsp鉴权密码,密码等于用户名
        //rtsp可以有双重鉴权!后面还会触发on_play事件
1018
        CHECK_ARGS("user_name");
xiongziliang committed
1019
        val["code"] = API::Success;
xiongziliang committed
1020
        val["encrypted"] = false;
1021
        val["passwd"] = allArgs["user_name"].data();
xiongziliang committed
1022 1023
    });

1024
    api_regist1("/index/hook/on_stream_changed",[](API_ARGS1){
xiongziliang committed
1025 1026 1027
        //媒体注册或反注册事件
    });

xiongziliang committed
1028 1029

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

1038 1039 1040 1041 1042 1043 1044
        string dst_url = StrPrinter
                << "rtmp://127.0.0.1:"
                << rtmp_port << "/"
                << allArgs["app"] << "/"
                << allArgs["stream"] << "?vhost="
                << allArgs["vhost"];

xiongziliang committed
1045
        addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
1046
                        dst_url,
1047
                        (1000 * timeout_sec) - 500,
1048 1049 1050 1051 1052 1053 1054 1055 1056
                        [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
1057
    });
1058
#endif//!defined(_WIN32)
xiongziliang committed
1059

1060
    api_regist2("/index/hook/on_stream_not_found",[](API_ARGS2){
xiongziliang committed
1061 1062 1063
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
1064 1065 1066 1067 1068
        //通过内置支持的rtsp/rtmp按需拉流
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       /** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
1069 1070 1071
                       "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
                       true,/* 开启hls转发 */
                       false,/* 禁用MP4录制 */
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081
                       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
1082 1083
    });

1084
    api_regist1("/index/hook/on_record_mp4",[](API_ARGS1){
xiongziliang committed
1085 1086 1087
        //录制mp4分片完毕事件
    });

1088
    api_regist1("/index/hook/on_shell_login",[](API_ARGS1){
1089 1090
        //shell登录调试事件
    });
1091

1092
    api_regist1("/index/hook/on_stream_none_reader",[](API_ARGS1){
1093 1094 1095 1096
        //无人观看流默认关闭
        val["close"] = true;
    });

1097 1098 1099 1100 1101
    static auto checkAccess = [](const string &params){
        //我们假定大家都要权限访问
        return true;
    };

1102
    api_regist1("/index/hook/on_http_access",[](API_ARGS1){
1103 1104 1105
        //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
        if(!checkAccess(allArgs["params"])){
            //无访问权限
1106 1107
            val["err"] = "无访问权限";
            //仅限制访问当前目录
1108
            val["path"] = "";
1109
            //标记该客户端无权限1分钟
1110 1111 1112 1113
            val["second"] = 60;
            return;
        }

1114 1115 1116 1117 1118
        //可以访问
        val["err"] = "";
        //只能访问当前目录
        val["path"] = "";
        //该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
1119
        val["second"] = 10 * 60;
1120 1121
    });

1122

1123
    api_regist1("/index/hook/on_server_started",[](API_ARGS1){
1124 1125 1126 1127
        //服务器重启报告
    });


1128
}
xiongziliang committed
1129

1130
void unInstallWebApi(){
1131 1132 1133 1134 1135 1136 1137 1138 1139
    {
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        s_proxyMap.clear();
    }

    {
        lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
        s_ffmpegMap.clear();
    }
1140 1141 1142 1143 1144 1145
    {
#if defined(ENABLE_RTPPROXY)
        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
        s_rtpServerMap.clear();
#endif
    }
1146
}