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

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

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

xiongziliang committed
56 57 58 59

typedef map<string,variant,StrCaseCompare> ApiArgsType;


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

#define API_REGIST(field, name, ...) \
    s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ \
         static auto lam = [&](API_ARGS) __VA_ARGS__ ;  \
69
         lam(sender,headerIn, headerOut, allArgs, val); \
70 71 72
         invoker("200 OK", headerOut, val.toStyledString()); \
     });

xiongziliang committed
73 74
#define API_ARGS_VALUE sender,headerIn,headerOut,allArgs,val,invoker

75 76 77 78 79 80 81 82 83 84
#define API_REGIST_INVOKER(field, name, ...) \
    s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__);

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

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

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

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

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

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

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

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

131 132

//获取HTTP请求中url参数、content参数
xiongziliang committed
133 134
static ApiArgsType getAllArgs(const Parser &parser) {
    ApiArgsType allArgs;
135
    if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
136 137
        auto contentArgs = parser.parseArgs(parser.Content());
        for (auto &pr : contentArgs) {
xiongziliang committed
138
            allArgs[pr.first] = HttpSession::urlDecode(pr.second);
139
        }
140
    } else if (parser["Content-Type"].find("application/json") == 0) {
xiongziliang committed
141 142 143 144 145
        try {
            stringstream ss(parser.Content());
            Value jsonArgs;
            ss >> jsonArgs;
            auto keys = jsonArgs.getMemberNames();
146
            for (auto key = keys.begin(); key != keys.end(); ++key) {
xiongziliang committed
147 148
                allArgs[*key] = jsonArgs[*key].asString();
            }
149
        } catch (std::exception &ex) {
xiongziliang committed
150
            WarnL << ex.what();
151
        }
152
    } else if (!parser["Content-Type"].empty()) {
xiongziliang committed
153
        WarnL << "invalid Content-Type:" << parser["Content-Type"];
154
    }
xiongziliang committed
155

156 157
    for (auto &pr :  parser.getUrlArgs()) {
        allArgs[pr.first] = pr.second;
xiongziliang committed
158 159
    }
    return std::move(allArgs);
160 161 162
}

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

189 190 191 192 193 194
                //body默认为空
                int64_t size = 0;
                if (body && body->remainSize()) {
                    //有body,获取body大小
                    size = body->remainSize();
                }
195

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
                if(size < 4 * 1024){
                    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);
                }
212 213 214
            };
            ((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
        }
215

216
        try {
217
            it->second(sender,headerIn, headerOut, allArgs, val, invoker);
218 219 220 221
        }  catch(ApiRetException &ex){
            val["code"] = ex.code();
            val["msg"] = ex.what();
            invoker("200 OK", headerOut, val.toStyledString());
xiongziliang committed
222 223 224
        }
#ifdef ENABLE_MYSQL
        catch(SqlException &ex){
225 226 227 228
            val["code"] = API::SqlFailed;
            val["msg"] = StrPrinter << "操作数据库失败:" << ex.what() << ":" << ex.getSql();
            WarnL << ex.what() << ":" << ex.getSql();
            invoker("200 OK", headerOut, val.toStyledString());
xiongziliang committed
229 230 231
        }
#endif// ENABLE_MYSQL
        catch (std::exception &ex) {
232 233 234 235
            val["code"] = API::OtherFailed;
            val["msg"] = ex.what();
            invoker("200 OK", headerOut, val.toStyledString());
        }
236 237 238
    });
}

239
template <typename Args,typename First>
xiongziliang committed
240
bool checkArgs(Args &&args,First &&first){
241 242 243 244
    return !args[first].empty();
}

template <typename Args,typename First,typename ...KeyTypes>
xiongziliang committed
245 246
bool checkArgs(Args &&args,First &&first,KeyTypes && ...keys){
    return !args[first].empty() && checkArgs(std::forward<Args>(args),std::forward<KeyTypes>(keys)...);
247 248 249
}

#define CHECK_ARGS(...)  \
xiongziliang committed
250
    if(!checkArgs(allArgs,##__VA_ARGS__)){ \
251
        throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
252 253
    }

254
#define CHECK_SECRET() \
255 256 257 258 259
    if(sender.get_peer_ip() != "127.0.0.1"){ \
        CHECK_ARGS("secret"); \
        if(api_secret != allArgs["secret"]){ \
            throw AuthException("secret错误"); \
        } \
260 261
    }

262
static unordered_map<string ,PlayerProxy::Ptr> s_proxyMap;
263
static recursive_mutex s_proxyMapMtx;
264 265 266
static inline string getProxyKey(const string &vhost,const string &app,const string &stream){
    return vhost + "/" + app + "/" + stream;
}
267

268 269 270
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
static recursive_mutex s_ffmpegMapMtx;

271 272 273 274 275
/**
 * 安装api接口
 * 所有api都支持GET和POST两种方式
 * POST方式参数支持application/json和application/x-www-form-urlencoded方式
 */
276 277 278
void installWebApi() {
    addHttpListener();

279
    GET_CONFIG(string,api_secret,API::kSecret);
280

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

    //获取后台工作线程负载
    //测试url http://127.0.0.1/index/api/getWorkThreadsLoad
    API_REGIST_INVOKER(api, getWorkThreadsLoad, {
        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;
309 310 311
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
312
            val["code"] = API::Success;
313 314 315 316
            invoker("200 OK", headerOut, val.toStyledString());
        });
    });

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

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

354 355 356

    //获取服务器api列表
    //测试url http://127.0.0.1/index/api/getApiList
357
    API_REGIST(api,getApiList,{
358
        CHECK_SECRET();
359 360 361 362 363
        for(auto &pr : s_map_api){
            val["data"].append(pr.first);
        }
    });

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

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

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


386 387 388 389
    //获取流列表,可选筛选参数
    //测试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
390
    API_REGIST(api,getMediaList,{
391
        CHECK_SECRET();
392
        //获取所有MediaSource列表
xiongziliang committed
393 394
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
395 396
                return;
            }
xiongziliang committed
397
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
398 399
                return;
            }
xiongziliang committed
400
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
401 402 403
                return;
            }
            Value item;
xiongziliang committed
404 405 406 407
            item["schema"] = media->getSchema();
            item["vhost"] = media->getVhost();
            item["app"] = media->getApp();
            item["stream"] = media->getId();
408
            item["readerCount"] = media->readerCount();
409
            item["totalReaderCount"] = media->totalReaderCount();
410 411 412 413 414 415 416
            for(auto &track : media->getTracks()){
                Value obj;
                obj["codec_id"] = track->getCodecId();
                obj["codec_type"] = track->getTrackType();
                obj["ready"] = track->ready();
                item["tracks"].append(obj);
            }
xiongziliang committed
417
            val["data"].append(item);
418 419 420
        });
    });

421 422 423 424 425 426 427
    //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
    API_REGIST(api,isMediaOnline,{
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
        val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false));
    });

428 429 430 431 432 433 434 435 436 437 438
    //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
    API_REGIST(api,getMediaInfo,{
        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;
        }
        val["online"] = true;
        val["readerCount"] = src->readerCount();
439
        val["totalReaderCount"] = src->totalReaderCount();
440 441 442 443 444 445 446 447 448
        for(auto &track : src->getTracks()){
            Value obj;
            obj["codec_id"] = track->getCodecId();
            obj["codec_type"] = track->getTrackType();
            obj["ready"] = track->ready();
            val["tracks"].append(obj);
        }
    });

449
    //主动关断流,包括关断拉流、推流
450
    //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
451
    API_REGIST(api,close_stream,{
452
        CHECK_SECRET();
453
        CHECK_ARGS("schema","vhost","app","stream");
454 455 456 457 458 459
        //踢掉推流器
        auto src = MediaSource::find(allArgs["schema"],
                                     allArgs["vhost"],
                                     allArgs["app"],
                                     allArgs["stream"]);
        if(src){
460
            bool flag = src->close(allArgs["force"].as<bool>());
461
            val["result"] = flag ? 0 : -1;
462
            val["msg"] = flag ? "success" : "close failed";
463
        }else{
464
            val["result"] = -2;
465
            val["msg"] = "can not find the stream";
466 467 468
        }
    });

469 470 471 472 473 474 475
    //批量主动关断流,包括关断拉流、推流
    //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
    API_REGIST(api,close_streams,{
        CHECK_SECRET();
        //筛选命中个数
        int count_hit = 0;
        int count_closed = 0;
476
        list<MediaSource::Ptr> media_list;
xiongziliang committed
477 478
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
479 480
                return;
            }
xiongziliang committed
481
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
482 483
                return;
            }
xiongziliang committed
484
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
485 486
                return;
            }
xiongziliang committed
487
            if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
488 489 490
                return;
            }
            ++count_hit;
491 492 493 494 495 496
            media_list.emplace_back(media);
        });

        bool force = allArgs["force"].as<bool>();
        for(auto &media : media_list){
            if(media->close(force)){
497 498
                ++count_closed;
            }
499
        }
500 501 502 503
        val["count_hit"] = count_hit;
        val["count_closed"] = count_closed;
    });

504 505 506 507 508 509 510 511 512 513
    //获取所有TcpSession列表信息
    //可以根据本地端口和远端ip来筛选
    //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
    API_REGIST(api,getAllSession,{
        CHECK_SECRET();
        Value jsession;
        uint16_t local_port = allArgs["local_port"].as<uint16_t>();
        string &peer_ip = allArgs["peer_ip"];

        SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){
xiongziliang committed
514
            if(local_port != 0 && local_port != session->get_local_port()){
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
                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
532
    API_REGIST(api,kick_session,{
533
        CHECK_SECRET();
534
        CHECK_ARGS("id");
535
        //踢掉tcp会话
xiongziliang committed
536
        auto session = SessionMap::Instance().get(allArgs["id"]);
537
        if(!session){
538
            throw ApiRetException("can not find the target",API::OtherFailed);
539 540 541 542
        }
        session->safeShutdown();
    });

543 544 545 546 547 548 549 550 551

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

552
        list<TcpSession::Ptr> session_list;
553 554 555 556 557 558 559
        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;
            }
560
            session_list.emplace_back(session);
561 562
            ++count_hit;
        });
563 564 565 566

        for(auto &session : session_list){
            session->safeShutdown();
        }
567 568 569
        val["count_hit"] = (Json::UInt64)count_hit;
    });

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

        //被主动关闭拉流
        player->setOnClose([key](){
            lock_guard<recursive_mutex> lck(s_proxyMapMtx);
            s_proxyMap.erase(key);
        });
607 608 609 610
        player->play(url);
    };

    //动态添加rtsp/rtmp拉流代理
611
    //测试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
612 613
    API_REGIST_INVOKER(api,addStreamProxy,{
        CHECK_SECRET();
614
        CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
615 616 617 618
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       allArgs["url"],
619 620 621 622
                       allArgs["enable_rtsp"],/* 是否rtsp转发 */
                       allArgs["enable_rtmp"],/* 是否rtmp转发 */
                       allArgs["enable_hls"],/* 是否hls转发 */
                       allArgs["enable_mp4"],/* 是否MP4录制 */
623 624 625 626 627 628 629 630 631 632
                       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
633 634
    });

635 636
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
xiongziliang committed
637
    API_REGIST(api,delStreamProxy,{
638
        CHECK_SECRET();
639
        CHECK_ARGS("key");
xiongziliang committed
640
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
641
        val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
xiongziliang committed
642 643
    });

xiongziliang committed
644
    static auto addFFmpegSource = [](const string &src_url,
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
                                     const string &dst_url,
                                     int timeout_ms,
                                     const function<void(const SockException &ex,const string &key)> &cb){
        auto key = MD5(dst_url).hexdigest();
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        if(s_ffmpegMap.find(key) != s_ffmpegMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }

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

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

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

xiongziliang committed
681
        addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
682 683 684 685 686 687 688 689 690 691
            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
692 693

    static auto api_delFFmpegSource = [](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){
694 695 696 697
        CHECK_SECRET();
        CHECK_ARGS("key");
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
xiongziliang committed
698 699 700 701 702 703 704 705 706 707 708
    };

    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
    API_REGIST(api,delFFmpegSource,{
        api_delFFmpegSource(API_ARGS_VALUE);
    });

    //此处为了兼容之前的拼写错误
    API_REGIST(api,delFFmepgSource,{
        api_delFFmpegSource(API_ARGS_VALUE);
709
    });
xiongziliang committed
710

711 712 713 714
    //新增http api下载可执行程序文件接口
    //测试url http://127.0.0.1/index/api/downloadBin
    API_REGIST_INVOKER(api,downloadBin,{
        CHECK_SECRET();
xiongziliang committed
715
        invoker.responseFile(headerIn,StrCaseMap(),exePath());
716 717
    });

718
#if defined(ENABLE_RTPPROXY)
719 720 721
    API_REGIST(api,getSsrcInfo,{
        CHECK_SECRET();
        CHECK_ARGS("ssrc");
722 723 724 725 726
        uint32_t ssrc = 0;
        stringstream ss(allArgs["ssrc"]);
        ss >> std::hex >> ssrc;

        auto process = RtpSelector::Instance().getProcess(ssrc,false);
727 728 729 730 731 732 733 734
        if(!process){
            val["exist"] = false;
            return;
        }
        val["exist"] = true;
        val["peer_ip"] = process->get_peer_ip();
        val["peer_port"] = process->get_peer_port();
    });
735
#endif//ENABLE_RTPPROXY
736

737
    // 开始录制hls或MP4
738
    API_REGIST(api,startRecord,{
739 740
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream","wait_for_record","continue_record");
741

742 743 744 745
        int result = Recorder::startRecord((Recorder::type)allArgs["type"].as<int>(),
                                           allArgs["vhost"],
                                           allArgs["app"],
                                           allArgs["stream"],
746
                                           allArgs["customized_path"],
747 748 749 750 751 752
                                           allArgs["wait_for_record"],
                                           allArgs["continue_record"]);
        val["result"] = result;
    });

    // 停止录制hls或MP4
753
    API_REGIST(api,stopRecord,{
754 755
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
756 757 758 759 760
        int result = Recorder::stopRecord((Recorder::type)allArgs["type"].as<int>(),
                                          allArgs["vhost"],
                                          allArgs["app"],
                                          allArgs["stream"]);
        val["result"] = result;
761 762 763
    });

    // 获取hls或MP4录制状态
764
    API_REGIST(api,getRecordStatus,{
765 766 767 768 769 770 771 772 773
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
        auto status = Recorder::getRecordStatus((Recorder::type)allArgs["type"].as<int>(),
                                                allArgs["vhost"],
                                                allArgs["app"],
                                                allArgs["stream"]);
        val["status"] = (int)status;
    });

774 775 776
    ////////////以下是注册的Hook API////////////
    API_REGIST(hook,on_publish,{
        //开始推流事件
777 778 779 780 781 782
        //转换成rtsp或rtmp
        val["enableRtxp"] = true;
        //转换hls
        val["enableHls"] = true;
        //不录制mp4
        val["enableMP4"] = false;
783 784 785 786
    });

    API_REGIST(hook,on_play,{
        //开始播放事件
787
        throw SuccessException();
788 789 790 791
    });

    API_REGIST(hook,on_flow_report,{
        //流量统计hook api
792
        throw SuccessException();
793
    });
xiongziliang committed
794 795

    API_REGIST(hook,on_rtsp_realm,{
796
        //rtsp是否需要鉴权,默认需要鉴权
xiongziliang committed
797
        val["code"] = API::Success;
xiongziliang committed
798 799 800 801 802 803
        val["realm"] = "zlmediakit_reaml";
    });

    API_REGIST(hook,on_rtsp_auth,{
        //rtsp鉴权密码,密码等于用户名
        //rtsp可以有双重鉴权!后面还会触发on_play事件
804
        CHECK_ARGS("user_name");
xiongziliang committed
805
        val["code"] = API::Success;
xiongziliang committed
806
        val["encrypted"] = false;
807
        val["passwd"] = allArgs["user_name"].data();
xiongziliang committed
808 809 810 811
    });

    API_REGIST(hook,on_stream_changed,{
        //媒体注册或反注册事件
812
        throw SuccessException();
xiongziliang committed
813 814
    });

xiongziliang committed
815 816

#if !defined(_WIN32)
817
    API_REGIST_INVOKER(hook,on_stream_not_found_ffmpeg,{
818
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
819 820
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
821 822
        //通过FFmpeg按需拉流
        GET_CONFIG(int,rtmp_port,Rtmp::kPort);
823 824
        GET_CONFIG(int,timeout_sec,Hook::kTimeoutSec);

825 826 827 828 829 830 831
        string dst_url = StrPrinter
                << "rtmp://127.0.0.1:"
                << rtmp_port << "/"
                << allArgs["app"] << "/"
                << allArgs["stream"] << "?vhost="
                << allArgs["vhost"];

xiongziliang committed
832
        addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
833
                        dst_url,
834
                        (1000 * timeout_sec) - 500,
835 836 837 838 839 840 841 842 843
                        [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
844
    });
845
#endif//!defined(_WIN32)
xiongziliang committed
846 847 848 849 850

    API_REGIST_INVOKER(hook,on_stream_not_found,{
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
851 852 853 854 855
        //通过内置支持的rtsp/rtmp按需拉流
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       /** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
856 857 858 859 860
                       "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
                       true,/* 开启rtsp转发 */
                       true,/* 开启rtmp转发 */
                       true,/* 开启hls转发 */
                       false,/* 禁用MP4录制 */
861 862 863 864 865 866 867 868 869 870
                       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
871 872 873 874
    });

    API_REGIST(hook,on_record_mp4,{
        //录制mp4分片完毕事件
875
        throw SuccessException();
xiongziliang committed
876 877
    });

878 879 880 881
    API_REGIST(hook,on_shell_login,{
        //shell登录调试事件
        throw SuccessException();
    });
882 883 884 885 886 887

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

888 889 890 891 892
    static auto checkAccess = [](const string &params){
        //我们假定大家都要权限访问
        return true;
    };

893
    API_REGIST(hook,on_http_access,{
894 895 896
        //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
        if(!checkAccess(allArgs["params"])){
            //无访问权限
897 898
            val["err"] = "无访问权限";
            //仅限制访问当前目录
899
            val["path"] = "";
900
            //标记该客户端无权限1分钟
901 902 903 904
            val["second"] = 60;
            return;
        }

905 906 907 908 909
        //可以访问
        val["err"] = "";
        //只能访问当前目录
        val["path"] = "";
        //该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
910
        val["second"] = 10 * 60;
911 912
    });

913

914 915 916 917 918 919
    API_REGIST(hook,on_server_started,{
        //服务器重启报告
        throw SuccessException();
    });


920
}
xiongziliang committed
921

922
void unInstallWebApi(){
923 924 925 926 927 928 929 930 931
    {
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        s_proxyMap.clear();
    }

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