WebApi.cpp 38.8 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 13 14
#include <signal.h>
#include <functional>
#include <sstream>
#include <unordered_map>
xiongziliang committed
15
#include <math.h>
16 17 18 19 20
#include "jsoncpp/json.h"
#include "Util/util.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/NoticeCenter.h"
xiongziliang committed
21
#ifdef ENABLE_MYSQL
22
#include "Util/SqlPool.h"
xiongziliang committed
23
#endif //ENABLE_MYSQL
24 25 26 27 28
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Http/HttpRequester.h"
#include "Http/HttpSession.h"
#include "Network/TcpServer.h"
xiongziliang committed
29
#include "Player/PlayerProxy.h"
30 31
#include "Util/MD5.h"
#include "WebApi.h"
32
#include "WebHook.h"
33
#include "Thread/WorkThreadPool.h"
34
#include "Rtp/RtpSelector.h"
xiongziliang committed
35
#include "FFmpegSource.h"
36 37 38 39 40 41
using namespace Json;
using namespace toolkit;
using namespace mediakit;

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

#define API_FIELD "api."
51 52
const string kApiDebug = API_FIELD"apiDebug";
const string kSecret = API_FIELD"secret";
53
const string kSnapRoot = API_FIELD"snapRoot";
54

55
static onceToken token([]() {
xiongziliang committed
56
    mINI::Instance()[kApiDebug] = "1";
57
    mINI::Instance()[kSecret] = "035c73f7-bb6b-4889-a715-d9eb2d1925cc";
58 59
    mINI::Instance()[kSnapRoot] = "./www/snap/";

60 61 62
});
}//namespace API

63

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
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;
};

81
class InvalidArgsException: public ApiRetException {
82
public:
83 84 85 86 87 88 89 90
    InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
    ~InvalidArgsException() = default;
};

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

93
#define API_ARGS1 SockInfo &sender,HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, ApiArgsType &allArgs, Json::Value &val
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
#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));
}
114 115

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

139 140
    for (auto &pr :  parser.getUrlArgs()) {
        allArgs[pr.first] = pr.second;
xiongziliang committed
141 142
    }
    return std::move(allArgs);
143 144 145
}

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

173 174 175 176 177 178
                //body默认为空
                int64_t size = 0;
                if (body && body->remainSize()) {
                    //有body,获取body大小
                    size = body->remainSize();
                }
179

180
                if(size && size < 4 * 1024){
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
                    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);
                }
196 197 198
            };
            ((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
        }
199

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

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

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

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

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

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

252 253 254
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
static recursive_mutex s_ffmpegMapMtx;

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

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

    //获取后台工作线程负载
    //测试url http://127.0.0.1/index/api/getWorkThreadsLoad
284
    api_regist2("/index/api/getWorkThreadsLoad", [](API_ARGS2){
285 286 287 288 289 290 291
        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;
292 293 294
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
295
            val["code"] = API::Success;
296 297 298 299
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

300 301
    //获取服务器配置
    //测试url http://127.0.0.1/index/api/getServerConfig
302
    api_regist1("/index/api/getServerConfig",[](API_ARGS1){
303
        CHECK_SECRET();
304 305 306 307 308 309 310
        Value obj;
        for (auto &pr : mINI::Instance()) {
            obj[pr.first] = (string &) pr.second;
        }
        val["data"].append(obj);
    });

311 312 313
    //设置服务器配置
    //测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
    //你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参
314
    api_regist1("/index/api/setServerConfig",[](API_ARGS1){
315
        CHECK_SECRET();
316
        auto &ini = mINI::Instance();
xiongziliang committed
317
        int changed = API::Success;
318 319 320 321 322 323 324 325 326 327 328 329 330 331
        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);
332
            ini.dumpFile(g_ini_file);
333 334 335 336
        }
        val["changed"] = changed;
    });

337

338
    static auto s_get_api_list = [](API_ARGS1){
339
        CHECK_SECRET();
340 341 342
        for(auto &pr : s_map_api){
            val["data"].append(pr.first);
        }
343 344 345 346 347 348 349 350 351 352 353 354
    };

    //获取服务器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);
355 356
    });

xiongziliang committed
357
#if !defined(_WIN32)
358 359
    //重启服务器,只有Daemon方式才能重启,否则是直接关闭!
    //测试url http://127.0.0.1/index/api/restartServer
360
    api_regist1("/index/api/restartServer",[](API_ARGS1){
361
        CHECK_SECRET();
362 363 364 365 366 367 368 369 370 371 372 373 374 375
        EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
            //尝试正常退出
            ::kill(getpid(), SIGINT);

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

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


379 380 381 382 383 384 385 386 387 388 389 390
    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
391
            obj["codec_id_name"] = track->getCodecName();
392 393 394 395 396 397 398 399 400 401 402 403 404 405
            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
406
                    obj["fps"] = round(video_track->getVideoFps());
407 408 409 410 411 412 413 414 415 416
                    break;
                }
                default:
                    break;
            }
            item["tracks"].append(obj);
        }
        return item;
    };

417 418 419 420
    //获取流列表,可选筛选参数
    //测试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
421
    api_regist1("/index/api/getMediaList",[](API_ARGS1){
422
        CHECK_SECRET();
423
        //获取所有MediaSource列表
xiongziliang committed
424 425
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
426 427
                return;
            }
xiongziliang committed
428
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
429 430
                return;
            }
xiongziliang committed
431
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
432 433
                return;
            }
434
            val["data"].append(makeMediaSourceJson(media));
435 436 437
        });
    });

438
    //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
439
    api_regist1("/index/api/isMediaOnline",[](API_ARGS1){
440 441 442 443 444
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
        val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false));
    });

445
    //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
446
    api_regist1("/index/api/getMediaInfo",[](API_ARGS1){
447 448 449 450 451 452 453
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
        auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false);
        if(!src){
            val["online"] = false;
            return;
        }
454
        val = makeMediaSourceJson(src);
455
        val["online"] = true;
456
        val["code"] = API::Success;
457 458
    });

459
    //主动关断流,包括关断拉流、推流
460
    //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
461
    api_regist1("/index/api/close_stream",[](API_ARGS1){
462
        CHECK_SECRET();
463
        CHECK_ARGS("schema","vhost","app","stream");
464 465 466 467 468 469
        //踢掉推流器
        auto src = MediaSource::find(allArgs["schema"],
                                     allArgs["vhost"],
                                     allArgs["app"],
                                     allArgs["stream"]);
        if(src){
470
            bool flag = src->close(allArgs["force"].as<bool>());
471
            val["result"] = flag ? 0 : -1;
472
            val["msg"] = flag ? "success" : "close failed";
xiongziliang committed
473
            val["code"] = API::OtherFailed;
474
        }else{
475
            val["result"] = -2;
476
            val["msg"] = "can not find the stream";
xiongziliang committed
477
            val["code"] = API::OtherFailed;
478 479 480
        }
    });

481 482
    //批量主动关断流,包括关断拉流、推流
    //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
483
    api_regist1("/index/api/close_streams",[](API_ARGS1){
484 485 486 487
        CHECK_SECRET();
        //筛选命中个数
        int count_hit = 0;
        int count_closed = 0;
488
        list<MediaSource::Ptr> media_list;
xiongziliang committed
489 490
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
491 492
                return;
            }
xiongziliang committed
493
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
494 495
                return;
            }
xiongziliang committed
496
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
497 498
                return;
            }
xiongziliang committed
499
            if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
500 501 502
                return;
            }
            ++count_hit;
503 504 505 506 507 508
            media_list.emplace_back(media);
        });

        bool force = allArgs["force"].as<bool>();
        for(auto &media : media_list){
            if(media->close(force)){
509 510
                ++count_closed;
            }
511
        }
512 513 514 515
        val["count_hit"] = count_hit;
        val["count_closed"] = count_closed;
    });

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

555 556 557

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

564
        list<TcpSession::Ptr> session_list;
565 566 567 568 569 570 571
        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;
            }
572
            session_list.emplace_back(session);
573 574
            ++count_hit;
        });
575 576 577 578

        for(auto &session : session_list){
            session->safeShutdown();
        }
579 580 581
        val["count_hit"] = (Json::UInt64)count_hit;
    });

582 583 584 585
    static auto addStreamProxy = [](const string &vhost,
                                    const string &app,
                                    const string &stream,
                                    const string &url,
586 587
                                    bool enable_rtsp,
                                    bool enable_rtmp,
588 589 590 591 592
                                    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);
593 594 595 596 597 598
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        if(s_proxyMap.find(key) != s_proxyMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }
xiongziliang committed
599
        //添加拉流代理
600
        PlayerProxy::Ptr player(new PlayerProxy(vhost,app,stream,enable_rtsp,enable_rtmp,enable_hls,enable_mp4));
601 602
        s_proxyMap[key] = player;
        
xiongziliang committed
603
        //指定RTP over TCP(播放rtsp时有效)
604
        (*player)[kRtpType] = rtp_type;
605
        //开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
606
        player->setPlayCallbackOnce([cb,key](const SockException &ex){
607
            if(ex){
608
                lock_guard<recursive_mutex> lck(s_proxyMapMtx);
609
                s_proxyMap.erase(key);
610
            }
611
            cb(ex,key);
612
        });
613 614 615 616 617 618

        //被主动关闭拉流
        player->setOnClose([key](){
            lock_guard<recursive_mutex> lck(s_proxyMapMtx);
            s_proxyMap.erase(key);
        });
619 620 621 622
        player->play(url);
    };

    //动态添加rtsp/rtmp拉流代理
623
    //测试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
624
    api_regist2("/index/api/addStreamProxy",[](API_ARGS2){
625
        CHECK_SECRET();
626
        CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
627 628 629 630
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       allArgs["url"],
631 632 633 634
                       allArgs["enable_rtsp"],/* 是否rtsp转发 */
                       allArgs["enable_rtmp"],/* 是否rtmp转发 */
                       allArgs["enable_hls"],/* 是否hls转发 */
                       allArgs["enable_mp4"],/* 是否MP4录制 */
635 636 637 638 639 640 641 642 643 644
                       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
645 646
    });

647 648
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
649
    api_regist1("/index/api/delStreamProxy",[](API_ARGS1){
650
        CHECK_SECRET();
651
        CHECK_ARGS("key");
xiongziliang committed
652
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
653
        val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
xiongziliang committed
654 655
    });

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

705
    static auto api_delFFmpegSource = [](API_ARGS1){
706 707 708 709
        CHECK_SECRET();
        CHECK_ARGS("key");
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
xiongziliang committed
710 711 712 713
    };

    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
714 715
    api_regist1("/index/api/delFFmpegSource",[](API_ARGS1){
        api_delFFmpegSource(API_ARGS_VALUE1);
xiongziliang committed
716 717 718
    });

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

723 724
    //新增http api下载可执行程序文件接口
    //测试url http://127.0.0.1/index/api/downloadBin
725
    api_regist2("/index/api/downloadBin",[](API_ARGS2){
726
        CHECK_SECRET();
xiongziliang committed
727
        invoker.responseFile(headerIn,StrCaseMap(),exePath());
728 729
    });

730
#if defined(ENABLE_RTPPROXY)
731
    api_regist1("/index/api/getSsrcInfo",[](API_ARGS1){
732 733
        CHECK_SECRET();
        CHECK_ARGS("ssrc");
734 735 736 737 738
        uint32_t ssrc = 0;
        stringstream ss(allArgs["ssrc"]);
        ss >> std::hex >> ssrc;

        auto process = RtpSelector::Instance().getProcess(ssrc,false);
739 740 741 742 743 744 745 746
        if(!process){
            val["exist"] = false;
            return;
        }
        val["exist"] = true;
        val["peer_ip"] = process->get_peer_ip();
        val["peer_port"] = process->get_peer_port();
    });
747
#endif//ENABLE_RTPPROXY
748

749 750 751 752
    // 开始录制hls或MP4
    api_regist1("/index/api/startRecord",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
753
        auto result = Recorder::startRecord((Recorder::type) allArgs["type"].as<int>(),
754 755 756 757
                                              allArgs["vhost"],
                                              allArgs["app"],
                                              allArgs["stream"],
                                              allArgs["customized_path"]);
xiongziliang committed
758 759
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
760
        val["msg"] = result ? "success" :  "start record failed";
761 762 763 764 765 766
    });

    // 停止录制hls或MP4
    api_regist1("/index/api/stopRecord",[](API_ARGS1){
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
767
        auto result = Recorder::stopRecord((Recorder::type) allArgs["type"].as<int>(),
768 769 770
                                             allArgs["vhost"],
                                             allArgs["app"],
                                             allArgs["stream"]);
xiongziliang committed
771 772
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
773
        val["msg"] = result ? "success" :  "stop record failed";
774 775 776 777 778 779 780 781 782 783 784 785
    });

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

786 787 788
    //获取录像文件夹列表或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){
789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
        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
818

819 820
        val["data"]["rootPath"] = record_path;
        val["data"]["paths"] = paths;
821
    });
liuziloong committed
822

823 824 825 826 827 828 829 830
    GET_CONFIG(string, snap_root, API::kSnapRoot);

    //获取截图缓存或者实时截图
    //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");
        int expire_sec = allArgs["expire_sec"];
831 832 833 834 835 836
        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;
837
            }
838 839 840 841 842 843 844 845 846 847 848 849

            //找到截图
            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;
850 851
        });

852
        if(!snap_path.empty()){
853 854
            StrCaseMap headerOut;
            headerOut["Content-Type"] = HttpFileManager::getContentType(".jpeg");
855 856
            invoker.responseFile(headerIn,headerOut,snap_path);
            return ;
857 858 859
        }

        //无截图或者截图已经过期
860
        snap_path = StrPrinter << scan_path << time(NULL) << ".jpeg";
861 862 863 864 865 866 867 868 869 870 871

        {
            //生成一个空文件,目的是顺便创建文件夹路径,
            //同时防止在FFmpeg生成截图途中不停的尝试调用该api启动FFmpeg生成相同的截图
            //当然,我们可以拷贝一个"正在截图中"的图来替换这个空图,这需要开发者自己实现
            auto file = File::create_file(snap_path.data(), "wb");
            if(file){
                fclose(file);
            }
        }

872
        FFmpegSnap::makeSnap(allArgs["url"],snap_path,allArgs["timeout_sec"],[invoker,headerIn,snap_path](bool success){
873 874
            if(!success){
                //生成截图失败,可能残留空文件
875
                File::delete_file(snap_path.data());
876 877 878 879
            }

            StrCaseMap headerOut;
            headerOut["Content-Type"] = HttpFileManager::getContentType(".jpeg");
880
            invoker.responseFile(headerIn, headerOut, snap_path);
881 882 883
        });
    });

884
    ////////////以下是注册的Hook API////////////
885
    api_regist1("/index/hook/on_publish",[](API_ARGS1){
886
        //开始推流事件
887 888 889 890 891 892
        //转换成rtsp或rtmp
        val["enableRtxp"] = true;
        //转换hls
        val["enableHls"] = true;
        //不录制mp4
        val["enableMP4"] = false;
893 894
    });

895
    api_regist1("/index/hook/on_play",[](API_ARGS1){
896 897 898
        //开始播放事件
    });

899
    api_regist1("/index/hook/on_flow_report",[](API_ARGS1){
900 901
        //流量统计hook api
    });
xiongziliang committed
902

903
    api_regist1("/index/hook/on_rtsp_realm",[](API_ARGS1){
904
        //rtsp是否需要鉴权,默认需要鉴权
xiongziliang committed
905
        val["code"] = API::Success;
xiongziliang committed
906 907 908
        val["realm"] = "zlmediakit_reaml";
    });

909
    api_regist1("/index/hook/on_rtsp_auth",[](API_ARGS1){
xiongziliang committed
910 911
        //rtsp鉴权密码,密码等于用户名
        //rtsp可以有双重鉴权!后面还会触发on_play事件
912
        CHECK_ARGS("user_name");
xiongziliang committed
913
        val["code"] = API::Success;
xiongziliang committed
914
        val["encrypted"] = false;
915
        val["passwd"] = allArgs["user_name"].data();
xiongziliang committed
916 917
    });

918
    api_regist1("/index/hook/on_stream_changed",[](API_ARGS1){
xiongziliang committed
919 920 921
        //媒体注册或反注册事件
    });

xiongziliang committed
922 923

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

932 933 934 935 936 937 938
        string dst_url = StrPrinter
                << "rtmp://127.0.0.1:"
                << rtmp_port << "/"
                << allArgs["app"] << "/"
                << allArgs["stream"] << "?vhost="
                << allArgs["vhost"];

xiongziliang committed
939
        addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
940
                        dst_url,
941
                        (1000 * timeout_sec) - 500,
942 943 944 945 946 947 948 949 950
                        [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
951
    });
952
#endif//!defined(_WIN32)
xiongziliang committed
953

954
    api_regist2("/index/hook/on_stream_not_found",[](API_ARGS2){
xiongziliang committed
955 956 957
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
958 959 960 961 962
        //通过内置支持的rtsp/rtmp按需拉流
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       /** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
963 964 965 966 967
                       "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
                       true,/* 开启rtsp转发 */
                       true,/* 开启rtmp转发 */
                       true,/* 开启hls转发 */
                       false,/* 禁用MP4录制 */
968 969 970 971 972 973 974 975 976 977
                       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
978 979
    });

980
    api_regist1("/index/hook/on_record_mp4",[](API_ARGS1){
xiongziliang committed
981 982 983
        //录制mp4分片完毕事件
    });

984
    api_regist1("/index/hook/on_shell_login",[](API_ARGS1){
985 986
        //shell登录调试事件
    });
987

988
    api_regist1("/index/hook/on_stream_none_reader",[](API_ARGS1){
989 990 991 992
        //无人观看流默认关闭
        val["close"] = true;
    });

993 994 995 996 997
    static auto checkAccess = [](const string &params){
        //我们假定大家都要权限访问
        return true;
    };

998
    api_regist1("/index/hook/on_http_access",[](API_ARGS1){
999 1000 1001
        //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
        if(!checkAccess(allArgs["params"])){
            //无访问权限
1002 1003
            val["err"] = "无访问权限";
            //仅限制访问当前目录
1004
            val["path"] = "";
1005
            //标记该客户端无权限1分钟
1006 1007 1008 1009
            val["second"] = 60;
            return;
        }

1010 1011 1012 1013 1014
        //可以访问
        val["err"] = "";
        //只能访问当前目录
        val["path"] = "";
        //该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
1015
        val["second"] = 10 * 60;
1016 1017
    });

1018

1019
    api_regist1("/index/hook/on_server_started",[](API_ARGS1){
1020 1021 1022 1023
        //服务器重启报告
    });


1024
}
xiongziliang committed
1025

1026
void unInstallWebApi(){
1027 1028 1029 1030 1031 1032 1033 1034 1035
    {
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        s_proxyMap.clear();
    }

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