WebApi.cpp 51.1 KB
Newer Older
xiongziliang committed
1
/*
xiongziliang committed
2
 * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
xiongziliang committed
3
 *
4
 * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
xiongziliang committed
5
 *
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
#include <sys/stat.h>
ziyue committed
12
#include <math.h>
13 14 15 16 17 18 19
#include <signal.h>
#include <functional>
#include <unordered_map>
#include "Util/util.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/NoticeCenter.h"
xiongziliang committed
20
#ifdef ENABLE_MYSQL
21
#include "Util/SqlPool.h"
xiongziliang committed
22
#endif //ENABLE_MYSQL
23 24 25 26 27
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Http/HttpRequester.h"
#include "Http/HttpSession.h"
#include "Network/TcpServer.h"
xiongziliang committed
28
#include "Player/PlayerProxy.h"
29 30
#include "Util/MD5.h"
#include "WebApi.h"
ziyue committed
31
#include "WebHook.h"
32
#include "Thread/WorkThreadPool.h"
33
#include "Rtp/RtpSelector.h"
xiongziliang committed
34
#include "FFmpegSource.h"
35 36 37
#if defined(ENABLE_RTPPROXY)
#include "Rtp/RtpServer.h"
#endif
38
#ifdef ENABLE_WEBRTC
ziyue committed
39
#include "../webrtc/WebRtcTransport.h"
40
#endif
41

42 43 44 45 46
using namespace toolkit;
using namespace mediakit;

namespace API {
#define API_FIELD "api."
47 48
const string kApiDebug = API_FIELD"apiDebug";
const string kSecret = API_FIELD"secret";
49
const string kSnapRoot = API_FIELD"snapRoot";
50
const string kDefaultSnap = API_FIELD"defaultSnap";
51

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

60
using HttpApi = function<void(const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender)>;
61
//http api列表
62
static map<string, HttpApi> s_map_api;
63

64 65 66 67
static void responseApi(const Json::Value &res, const HttpSession::HttpResponseInvoker &invoker){
    GET_CONFIG(string, charSet, Http::kCharSet);
    HttpSession::KeyValue headerOut;
    headerOut["Content-Type"] = string("application/json; charset=") + charSet;
68
    invoker(200, headerOut, res.toStyledString());
69 70 71 72 73 74 75 76 77 78 79
};

static void responseApi(int code, const string &msg, const HttpSession::HttpResponseInvoker &invoker){
    Json::Value res;
    res["code"] = code;
    res["msg"] = msg;
    responseApi(res, invoker);
}

static ApiArgsType getAllArgs(const Parser &parser);

80
static HttpApi toApi(const function<void(API_ARGS_MAP_ASYNC)> &cb) {
81 82 83 84 85 86 87 88
    return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
        GET_CONFIG(string, charSet, Http::kCharSet);
        HttpSession::KeyValue headerOut;
        headerOut["Content-Type"] = string("application/json; charset=") + charSet;

        Json::Value val;
        val["code"] = API::Success;

89
        //参数解析成map
90 91 92 93 94
        auto args = getAllArgs(parser);
        cb(sender, parser.getHeader(), headerOut, args, val, invoker);
    };
}

95 96 97
static HttpApi toApi(const function<void(API_ARGS_MAP)> &cb) {
    return toApi([cb](API_ARGS_MAP_ASYNC) {
        cb(API_ARGS_VALUE);
98
        invoker(200, headerOut, val.toStyledString());
99 100 101
    });
}

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
static HttpApi toApi(const function<void(API_ARGS_JSON_ASYNC)> &cb) {
    return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
        GET_CONFIG(string, charSet, Http::kCharSet);
        HttpSession::KeyValue headerOut;
        headerOut["Content-Type"] = string("application/json; charset=") + charSet;

        Json::Value out;
        out["code"] = API::Success;

        if (parser["Content-Type"].find("application/json") == string::npos) {
            throw InvalidArgsException("该接口只支持json格式的请求");
        }
        //参数解析成json对象然后处理
        Json::Value in;
        Json::Reader reader;
        reader.parse(parser.Content(), in);

        cb(sender, parser.getHeader(), headerOut, in, out, invoker);
    };
}

static HttpApi toApi(const function<void(API_ARGS_JSON)> &cb) {
    return toApi([cb](API_ARGS_JSON_ASYNC) {
        cb(API_ARGS_VALUE);
126
        invoker(200, headerOut, val.toStyledString());
127 128 129
    });
}

ziyue committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
static HttpApi toApi(const function<void(API_ARGS_STRING_ASYNC)> &cb) {
    return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
        GET_CONFIG(string, charSet, Http::kCharSet);
        HttpSession::KeyValue headerOut;
        headerOut["Content-Type"] = string("application/json; charset=") + charSet;

        Json::Value val;
        val["code"] = API::Success;

        cb(sender, parser.getHeader(), headerOut, parser, val, invoker);
    };
}

static HttpApi toApi(const function<void(API_ARGS_STRING)> &cb) {
    return toApi([cb](API_ARGS_STRING_ASYNC) {
        cb(API_ARGS_VALUE);
        invoker(200, headerOut, val.toStyledString());
    });
}

150 151 152 153 154 155 156 157 158 159
void api_regist(const string &api_path, const function<void(API_ARGS_MAP)> &func) {
    s_map_api.emplace(api_path, toApi(func));
}

void api_regist(const string &api_path, const function<void(API_ARGS_MAP_ASYNC)> &func) {
    s_map_api.emplace(api_path, toApi(func));
}

void api_regist(const string &api_path, const function<void(API_ARGS_JSON)> &func) {
    s_map_api.emplace(api_path, toApi(func));
160
}
161

162 163 164
void api_regist(const string &api_path, const function<void(API_ARGS_JSON_ASYNC)> &func) {
    s_map_api.emplace(api_path, toApi(func));
}
165

ziyue committed
166 167 168 169 170 171 172 173
void api_regist(const string &api_path, const function<void(API_ARGS_STRING)> &func){
    s_map_api.emplace(api_path, toApi(func));
}

void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYNC)> &func){
    s_map_api.emplace(api_path, toApi(func));
}

174
//获取HTTP请求中url参数、content参数
xiongziliang committed
175 176
static ApiArgsType getAllArgs(const Parser &parser) {
    ApiArgsType allArgs;
177
    if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
178 179
        auto contentArgs = parser.parseArgs(parser.Content());
        for (auto &pr : contentArgs) {
xiongziliang committed
180
            allArgs[pr.first] = HttpSession::urlDecode(pr.second);
181
        }
182
    } else if (parser["Content-Type"].find("application/json") == 0) {
xiongziliang committed
183 184 185 186 187
        try {
            stringstream ss(parser.Content());
            Value jsonArgs;
            ss >> jsonArgs;
            auto keys = jsonArgs.getMemberNames();
188
            for (auto key = keys.begin(); key != keys.end(); ++key) {
xiongziliang committed
189 190
                allArgs[*key] = jsonArgs[*key].asString();
            }
191
        } catch (std::exception &ex) {
xiongziliang committed
192
            WarnL << ex.what();
193
        }
194
    } else if (!parser["Content-Type"].empty()) {
xiongziliang committed
195
        WarnL << "invalid Content-Type:" << parser["Content-Type"];
196
    }
xiongziliang committed
197

198 199
    for (auto &pr :  parser.getUrlArgs()) {
        allArgs[pr.first] = pr.second;
xiongziliang committed
200
    }
201
    return allArgs;
202 203
}

204 205
extern uint64_t getTotalMemUsage();

206
static inline void addHttpListener(){
207
    GET_CONFIG(bool, api_debug, API::kApiDebug);
208 209 210 211 212 213 214 215
    //注册监听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;
216

217
        if(api_debug){
218
            auto newInvoker = [invoker, parser](int code,
219 220
                                                const HttpSession::KeyValue &headerOut,
                                                const HttpBody::Ptr &body) {
221

222
                //body默认为空
223
                ssize_t size = 0;
224 225 226 227
                if (body && body->remainSize()) {
                    //有body,获取body大小
                    size = body->remainSize();
                }
228

229
                if (size && size < 4 * 1024) {
230 231 232 233 234
                    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"
                           << "# response:\r\n"
                           << contentOut << "\r\n";
235
                    invoker(code, headerOut, contentOut);
236
                } else {
237 238 239
                    DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
                           << "# content:\r\n" << parser.Content() << "\r\n"
                           << "# response size:"
240
                           << size << "\r\n";
241
                    invoker(code, headerOut, body);
242
                }
243
            };
244
            ((HttpSession::HttpResponseInvoker &) invoker) = newInvoker;
245
        }
246

247
        try {
248 249 250
            it->second(parser, invoker, sender);
        } catch (ApiRetException &ex) {
            responseApi(ex.code(), ex.what(), invoker);
xiongziliang committed
251 252 253
        }
#ifdef ENABLE_MYSQL
        catch(SqlException &ex){
254
            responseApi(API::SqlFailed, StrPrinter << "操作数据库失败:" << ex.what() << ":" << ex.getSql(), invoker);
xiongziliang committed
255 256 257
        }
#endif// ENABLE_MYSQL
        catch (std::exception &ex) {
258
            responseApi(API::Exception, ex.what(), invoker);
259
        }
260 261 262
    });
}

263
//拉流代理器列表
264
static unordered_map<string ,PlayerProxy::Ptr> s_proxyMap;
265 266
static recursive_mutex s_proxyMapMtx;

267
//FFmpeg拉流代理器列表
268 269 270
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
static recursive_mutex s_ffmpegMapMtx;

271 272
#if defined(ENABLE_RTPPROXY)
//rtp服务器列表
273
static unordered_map<string, RtpServer::Ptr> s_rtpServerMap;
274 275 276 277 278 279 280
static recursive_mutex s_rtpServerMapMtx;
#endif

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

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
Value makeMediaSourceJson(MediaSource &media){
    Value item;
    item["schema"] = media.getSchema();
    item["vhost"] = media.getVhost();
    item["app"] = media.getApp();
    item["stream"] = media.getId();
    item["createStamp"] = (Json::UInt64) media.getCreateStamp();
    item["aliveSecond"] = (Json::UInt64) media.getAliveSecond();
    item["bytesSpeed"] = media.getBytesSpeed();
    item["readerCount"] = media.readerCount();
    item["totalReaderCount"] = media.totalReaderCount();
    item["originType"] = (int) media.getOriginType();
    item["originTypeStr"] = getOriginTypeString(media.getOriginType());
    item["originUrl"] = media.getOriginUrl();
    auto originSock = media.getOriginSock();
    if (originSock) {
        item["originSock"]["local_ip"] = originSock->get_local_ip();
        item["originSock"]["local_port"] = originSock->get_local_port();
        item["originSock"]["peer_ip"] = originSock->get_peer_ip();
        item["originSock"]["peer_port"] = originSock->get_peer_port();
        item["originSock"]["identifier"] = originSock->getIdentifier();
    } else {
        item["originSock"] = Json::nullValue;
    }

306
    for(auto &track : media.getTracks(false)){
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
        Value obj;
        auto codec_type = track->getTrackType();
        obj["codec_id"] = track->getCodecId();
        obj["codec_id_name"] = track->getCodecName();
        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();
                obj["fps"] = round(video_track->getVideoFps());
                break;
            }
            default:
                break;
        }
        item["tracks"].append(obj);
    }
    return item;
}

336 337 338 339 340
/**
 * 安装api接口
 * 所有api都支持GET和POST两种方式
 * POST方式参数支持application/json和application/x-www-form-urlencoded方式
 */
341 342
void installWebApi() {
    addHttpListener();
343
    GET_CONFIG(string,api_secret,API::kSecret);
344

345 346
    //获取线程负载
    //测试url http://127.0.0.1/index/api/getThreadsLoad
347
    api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){
348 349 350
        EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
            Value val;
            auto vec = EventPollerPool::Instance().getExecutorLoad();
xiongziliang committed
351
            int i = API::Success;
352 353 354
            for (auto load : vec) {
                Value obj(objectValue);
                obj["load"] = load;
355 356 357
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
358
            val["code"] = API::Success;
359
            invoker(200, headerOut, val.toStyledString());
360 361 362 363 364
        });
    });

    //获取后台工作线程负载
    //测试url http://127.0.0.1/index/api/getWorkThreadsLoad
365
    api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){
366 367 368 369 370 371 372
        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;
373 374 375
                obj["delay"] = vecDelay[i++];
                val["data"].append(obj);
            }
376
            val["code"] = API::Success;
377
            invoker(200, headerOut, val.toStyledString());
378 379 380
        });
    });

381 382
    //获取服务器配置
    //测试url http://127.0.0.1/index/api/getServerConfig
383
    api_regist("/index/api/getServerConfig",[](API_ARGS_MAP){
384
        CHECK_SECRET();
385 386 387 388 389 390 391
        Value obj;
        for (auto &pr : mINI::Instance()) {
            obj[pr.first] = (string &) pr.second;
        }
        val["data"].append(obj);
    });

392 393 394
    //设置服务器配置
    //测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
    //你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参
395
    api_regist("/index/api/setServerConfig",[](API_ARGS_MAP){
396
        CHECK_SECRET();
397
        auto &ini = mINI::Instance();
xiongziliang committed
398
        int changed = API::Success;
399 400 401 402 403 404 405 406 407 408 409 410 411 412
        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);
413
            ini.dumpFile(g_ini_file);
414 415 416 417
        }
        val["changed"] = changed;
    });

418

419
    static auto s_get_api_list = [](API_ARGS_MAP){
420
        CHECK_SECRET();
421 422 423
        for(auto &pr : s_map_api){
            val["data"].append(pr.first);
        }
424 425 426 427
    };

    //获取服务器api列表
    //测试url http://127.0.0.1/index/api/getApiList
428 429
    api_regist("/index/api/getApiList",[](API_ARGS_MAP){
        s_get_api_list(API_ARGS_VALUE);
430 431 432 433
    });

    //获取服务器api列表
    //测试url http://127.0.0.1/index/
434 435
    api_regist("/index/",[](API_ARGS_MAP){
        s_get_api_list(API_ARGS_VALUE);
436 437
    });

xiongziliang committed
438
#if !defined(_WIN32)
439 440
    //重启服务器,只有Daemon方式才能重启,否则是直接关闭!
    //测试url http://127.0.0.1/index/api/restartServer
441
    api_regist("/index/api/restartServer",[](API_ARGS_MAP){
442
        CHECK_SECRET();
443 444 445 446 447 448 449 450 451 452 453 454 455 456
        EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
            //尝试正常退出
            ::kill(getpid(), SIGINT);

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

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

459 460 461 462
    //获取流列表,可选筛选参数
    //测试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
463
    api_regist("/index/api/getMediaList",[](API_ARGS_MAP){
464
        CHECK_SECRET();
465
        //获取所有MediaSource列表
xiongziliang committed
466
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
467
            if (!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()) {
468 469
                return;
            }
470
            if (!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()) {
471 472
                return;
            }
473 474 475 476
            if (!allArgs["app"].empty() && allArgs["app"] != media->getApp()) {
                return;
            }
            if (!allArgs["stream"].empty() && allArgs["stream"] != media->getId()) {
477 478
                return;
            }
479
            val["data"].append(makeMediaSourceJson(*media));
480 481 482
        });
    });

483
    //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
484
    api_regist("/index/api/isMediaOnline",[](API_ARGS_MAP){
485 486
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
487
        val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]));
488 489
    });

490
    //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
491
    api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP){
492 493
        CHECK_SECRET();
        CHECK_ARGS("schema","vhost","app","stream");
494
        auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]);
495 496 497 498
        if(!src){
            val["online"] = false;
            return;
        }
499
        val = makeMediaSourceJson(*src);
500
        val["online"] = true;
501
        val["code"] = API::Success;
502 503
    });

504
    //主动关断流,包括关断拉流、推流
505
    //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
506
    api_regist("/index/api/close_stream",[](API_ARGS_MAP){
507
        CHECK_SECRET();
508
        CHECK_ARGS("schema","vhost","app","stream");
509 510 511 512 513
        //踢掉推流器
        auto src = MediaSource::find(allArgs["schema"],
                                     allArgs["vhost"],
                                     allArgs["app"],
                                     allArgs["stream"]);
514
        if (src) {
515
            bool flag = src->close(allArgs["force"].as<bool>());
516
            val["result"] = flag ? 0 : -1;
517
            val["msg"] = flag ? "success" : "close failed";
518 519
            val["code"] = flag ? API::Success : API::OtherFailed;
        } else {
520
            val["result"] = -2;
521
            val["msg"] = "can not find the stream";
xiongziliang committed
522
            val["code"] = API::OtherFailed;
523 524 525
        }
    });

526 527
    //批量主动关断流,包括关断拉流、推流
    //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
528
    api_regist("/index/api/close_streams",[](API_ARGS_MAP){
529 530 531 532
        CHECK_SECRET();
        //筛选命中个数
        int count_hit = 0;
        int count_closed = 0;
533
        list<MediaSource::Ptr> media_list;
xiongziliang committed
534 535
        MediaSource::for_each_media([&](const MediaSource::Ptr &media){
            if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
536 537
                return;
            }
xiongziliang committed
538
            if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
539 540
                return;
            }
xiongziliang committed
541
            if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
542 543
                return;
            }
xiongziliang committed
544
            if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
545 546 547
                return;
            }
            ++count_hit;
548 549 550 551 552 553
            media_list.emplace_back(media);
        });

        bool force = allArgs["force"].as<bool>();
        for(auto &media : media_list){
            if(media->close(force)){
554 555
                ++count_closed;
            }
556
        }
557 558 559 560
        val["count_hit"] = count_hit;
        val["count_closed"] = count_closed;
    });

561 562 563
    //获取所有TcpSession列表信息
    //可以根据本地端口和远端ip来筛选
    //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
564
    api_regist("/index/api/getAllSession",[](API_ARGS_MAP){
565 566 567 568 569 570
        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
571
            if(local_port != 0 && local_port != session->get_local_port()){
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
                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
589
    api_regist("/index/api/kick_session",[](API_ARGS_MAP){
590
        CHECK_SECRET();
591
        CHECK_ARGS("id");
592
        //踢掉tcp会话
xiongziliang committed
593
        auto session = SessionMap::Instance().get(allArgs["id"]);
594
        if(!session){
595
            throw ApiRetException("can not find the target",API::OtherFailed);
596 597 598 599
        }
        session->safeShutdown();
    });

600 601 602

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

609
        list<TcpSession::Ptr> session_list;
610 611 612 613 614 615 616
        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;
            }
617
            session_list.emplace_back(session);
618 619
            ++count_hit;
        });
620 621 622 623

        for(auto &session : session_list){
            session->safeShutdown();
        }
624 625 626
        val["count_hit"] = (Json::UInt64)count_hit;
    });

627 628 629 630 631 632 633 634 635
    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);
636 637 638 639 640 641
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        if(s_proxyMap.find(key) != s_proxyMap.end()){
            //已经在拉流了
            cb(SockException(Err_success),key);
            return;
        }
xiongziliang committed
642
        //添加拉流代理
643
        PlayerProxy::Ptr player(new PlayerProxy(vhost, app, stream, enable_hls, enable_mp4));
644 645
        s_proxyMap[key] = player;
        
xiongziliang committed
646
        //指定RTP over TCP(播放rtsp时有效)
647
        (*player)[kRtpType] = rtp_type;
648
        //开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
649
        player->setPlayCallbackOnce([cb,key](const SockException &ex){
650
            if(ex){
651
                lock_guard<recursive_mutex> lck(s_proxyMapMtx);
652
                s_proxyMap.erase(key);
653
            }
654
            cb(ex,key);
655
        });
656 657 658 659 660 661

        //被主动关闭拉流
        player->setOnClose([key](){
            lock_guard<recursive_mutex> lck(s_proxyMapMtx);
            s_proxyMap.erase(key);
        });
662 663 664 665
        player->play(url);
    };

    //动态添加rtsp/rtmp拉流代理
666
    //测试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
667
    api_regist("/index/api/addStreamProxy",[](API_ARGS_MAP_ASYNC){
668
        CHECK_SECRET();
669
        CHECK_ARGS("vhost","app","stream","url");
670 671 672 673
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       allArgs["url"],
674 675
                       allArgs["enable_hls"],/* 是否hls转发 */
                       allArgs["enable_mp4"],/* 是否MP4录制 */
676
                       allArgs["rtp_type"],
xiongziliang committed
677 678 679 680 681 682
                       [invoker,val,headerOut](const SockException &ex,const string &key) mutable{
                           if (ex) {
                               val["code"] = API::OtherFailed;
                               val["msg"] = ex.what();
                           } else {
                               val["data"]["key"] = key;
683
                           }
684
                           invoker(200, headerOut, val.toStyledString());
685
                       });
xiongziliang committed
686 687
    });

688 689
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
690
    api_regist("/index/api/delStreamProxy",[](API_ARGS_MAP){
691
        CHECK_SECRET();
692
        CHECK_ARGS("key");
xiongziliang committed
693
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
694
        val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
xiongziliang committed
695 696
    });

697 698
    static auto addFFmpegSource = [](const string &ffmpeg_cmd_key,
                                     const string &src_url,
699 700
                                     const string &dst_url,
                                     int timeout_ms,
701 702 703
                                     bool enable_hls,
                                     bool enable_mp4,
                                     const function<void(const SockException &ex, const string &key)> &cb) {
704 705
        auto key = MD5(dst_url).hexdigest();
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
706
        if (s_ffmpegMap.find(key) != s_ffmpegMap.end()) {
707
            //已经在拉流了
708
            cb(SockException(Err_success), key);
709 710 711 712 713 714
            return;
        }

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

715
        ffmpeg->setOnClose([key]() {
716 717 718
            lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
            s_ffmpegMap.erase(key);
        });
xiongziliang committed
719
        ffmpeg->setupRecordFlag(enable_hls, enable_mp4);
720
        ffmpeg->play(ffmpeg_cmd_key, src_url, dst_url, timeout_ms, [cb, key](const SockException &ex) {
721
            if (ex) {
722 723 724
                lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
                s_ffmpegMap.erase(key);
            }
725
            cb(ex, key);
726 727 728 729 730
        });
    };

    //动态添加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
731
    api_regist("/index/api/addFFmpegSource",[](API_ARGS_MAP_ASYNC){
732 733 734 735 736
        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"];
737 738
        auto enable_hls = allArgs["enable_hls"].as<int>();
        auto enable_mp4 = allArgs["enable_mp4"].as<int>();
739

740
        addFFmpegSource(allArgs["ffmpeg_cmd_key"], src_url, dst_url, timeout_ms, enable_hls, enable_mp4,
xiongziliang committed
741
                        [invoker, val, headerOut](const SockException &ex, const string &key) mutable{
742
            if (ex) {
xiongziliang committed
743 744
                val["code"] = API::OtherFailed;
                val["msg"] = ex.what();
745
            } else {
xiongziliang committed
746
                val["data"]["key"] = key;
747
            }
748
            invoker(200, headerOut, val.toStyledString());
749 750 751
        });
    });

xiongziliang committed
752 753
    //关闭拉流代理
    //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
754
    api_regist("/index/api/delFFmpegSource",[](API_ARGS_MAP){
755 756 757 758 759
        CHECK_SECRET();
        CHECK_ARGS("key");
        lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
        val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
    });
xiongziliang committed
760

761 762
    //新增http api下载可执行程序文件接口
    //测试url http://127.0.0.1/index/api/downloadBin
763
    api_regist("/index/api/downloadBin",[](API_ARGS_MAP_ASYNC){
764
        CHECK_SECRET();
xiongziliang committed
765
        invoker.responseFile(headerIn,StrCaseMap(),exePath());
766 767
    });

768
#if defined(ENABLE_RTPPROXY)
769
    api_regist("/index/api/getRtpInfo",[](API_ARGS_MAP){
770
        CHECK_SECRET();
771
        CHECK_ARGS("stream_id");
772

773 774
        auto process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
        if (!process) {
775 776 777 778 779 780
            val["exist"] = false;
            return;
        }
        val["exist"] = true;
        val["peer_ip"] = process->get_peer_ip();
        val["peer_port"] = process->get_peer_port();
xiongziliang committed
781 782
        val["local_port"] = process->get_local_port();
        val["local_ip"] = process->get_local_ip();
783
    });
784

785
    api_regist("/index/api/openRtpServer",[](API_ARGS_MAP){
786
        CHECK_SECRET();
787
        CHECK_ARGS("port", "enable_tcp", "stream_id");
788

789
        auto stream_id = allArgs["stream_id"];
790 791 792 793 794 795 796

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

797
        RtpServer::Ptr server = std::make_shared<RtpServer>();
798 799
        server->start(allArgs["port"], stream_id, allArgs["enable_tcp"].as<bool>());
        server->setOnDetach([stream_id]() {
800 801
            //设置rtp超时移除事件
            lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
802
            s_rtpServerMap.erase(stream_id);
803
        });
804

805 806
        //保存对象
        s_rtpServerMap.emplace(stream_id, server);
807
        //回复json
808
        val["port"] = server->getPort();
809 810
    });

811
    api_regist("/index/api/closeRtpServer",[](API_ARGS_MAP){
812
        CHECK_SECRET();
813
        CHECK_ARGS("stream_id");
814 815

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
816 817 818 819 820 821 822 823
        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;
824 825
    });

826
    api_regist("/index/api/listRtpServer",[](API_ARGS_MAP){
827 828 829
        CHECK_SECRET();

        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
830 831 832 833 834
        for (auto &pr : s_rtpServerMap) {
            Value obj;
            obj["stream_id"] = pr.first;
            obj["port"] = pr.second->getPort();
            val["data"].append(obj);
835 836
        }
    });
837

838
    api_regist("/index/api/startSendRtp",[](API_ARGS_MAP_ASYNC){
839 840 841
        CHECK_SECRET();
        CHECK_ARGS("vhost", "app", "stream", "ssrc", "dst_url", "dst_port", "is_udp");

842
        auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
843 844 845 846
        if (!src) {
            throw ApiRetException("该媒体流不存在", API::OtherFailed);
        }

847
        //src_port为空时,则随机本地端口
xiongziliang committed
848
        src->startSendRtp(allArgs["dst_url"], allArgs["dst_port"], allArgs["ssrc"], allArgs["is_udp"], allArgs["src_port"], [val, headerOut, invoker](uint16_t local_port, const SockException &ex) mutable{
849
            if (ex) {
xiongziliang committed
850 851
                val["code"] = API::OtherFailed;
                val["msg"] = ex.what();
852
            }
xiongziliang committed
853
            val["local_port"] = local_port;
854
            invoker(200, headerOut, val.toStyledString());
855 856 857
        });
    });

858
    api_regist("/index/api/stopSendRtp",[](API_ARGS_MAP){
859 860 861
        CHECK_SECRET();
        CHECK_ARGS("vhost", "app", "stream");

862
        auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
863 864 865 866
        if (!src) {
            throw ApiRetException("该媒体流不存在", API::OtherFailed);
        }

867 868
        //ssrc如果为空,关闭全部
        if (!src->stopSendRtp(allArgs["ssrc"])) {
869 870
            throw ApiRetException("尚未开始推流,停止失败", API::OtherFailed);
        }
871 872
    });

873
    api_regist("/index/api/pauseRtpCheck", [](API_ARGS_MAP) {
874 875 876
        CHECK_SECRET();
        CHECK_ARGS("stream_id");
        //只是暂停流的检查,流媒体服务器做为流负载服务,收流就转发,RTSP/RTMP有自己暂停协议
877 878 879 880 881
        auto rtp_process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
        if (rtp_process) {
            rtp_process->setStopCheckRtp(true);
        } else {
            val["code"] = API::NotFound;
882 883 884
        }
    });

885
    api_regist("/index/api/resumeRtpCheck", [](API_ARGS_MAP) {
886 887
        CHECK_SECRET();
        CHECK_ARGS("stream_id");
888 889 890 891 892
        auto rtp_process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
        if (rtp_process) {
            rtp_process->setStopCheckRtp(false);
        } else {
            val["code"] = API::NotFound;
893 894
        }
    });
895

896
#endif//ENABLE_RTPPROXY
897

898
    // 开始录制hls或MP4
899
    api_regist("/index/api/startRecord",[](API_ARGS_MAP){
900 901
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
902
        auto result = Recorder::startRecord((Recorder::type) allArgs["type"].as<int>(),
903 904 905 906 907
                                            allArgs["vhost"],
                                            allArgs["app"],
                                            allArgs["stream"],
                                            allArgs["customized_path"],
                                            allArgs["max_second"].as<size_t>());
xiongziliang committed
908 909
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
910
        val["msg"] = result ? "success" :  "start record failed";
911 912 913
    });

    // 停止录制hls或MP4
914
    api_regist("/index/api/stopRecord",[](API_ARGS_MAP){
915 916
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
xiongziliang committed
917
        auto result = Recorder::stopRecord((Recorder::type) allArgs["type"].as<int>(),
918 919 920
                                             allArgs["vhost"],
                                             allArgs["app"],
                                             allArgs["stream"]);
xiongziliang committed
921 922
        val["result"] = result;
        val["code"] = result ? API::Success : API::OtherFailed;
923
        val["msg"] = result ? "success" :  "stop record failed";
924 925 926
    });

    // 获取hls或MP4录制状态
927
    api_regist("/index/api/isRecording",[](API_ARGS_MAP){
928 929 930 931 932 933 934 935
        CHECK_SECRET();
        CHECK_ARGS("type","vhost","app","stream");
        val["status"] = Recorder::isRecording((Recorder::type) allArgs["type"].as<int>(),
                                              allArgs["vhost"],
                                              allArgs["app"],
                                              allArgs["stream"]);
    });

936 937
    //获取录像文件夹列表或mp4文件列表
    //http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
938
    api_regist("/index/api/getMp4RecordFile", [](API_ARGS_MAP){
939 940 941 942 943 944 945 946 947 948 949 950 951 952
        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) {
953
            auto pos = path.rfind('/');
954 955 956 957 958 959 960 961 962 963 964 965 966 967
            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
968

969 970
        val["data"]["rootPath"] = record_path;
        val["data"]["paths"] = paths;
971
    });
liuziloong committed
972

973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989
    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);
    };
990 991 992

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

998
        bool have_old_snap = false, res_old_snap = false;
999
        int expire_sec = allArgs["expire_sec"];
1000
        auto scan_path = File::absolutePath(MD5(allArgs["url"]).hexdigest(), snap_root) + "/";
1001 1002
        string new_snap = StrPrinter << scan_path << time(NULL) << ".jpeg";

1003
        File::scanDir(scan_path, [&](const string &path, bool isDir) {
1004 1005
            if (isDir || !end_with(path, ".jpeg")) {
                //忽略文件夹或其他类型的文件
1006
                return true;
1007
            }
1008 1009 1010 1011

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

1018 1019 1020 1021
            //截图存在,且未过期,那么返回之
            res_old_snap = true;
            responseSnap(path, headerIn, invoker);
            //中断遍历
1022
            return false;
1023 1024
        });

1025 1026
        if (res_old_snap) {
            //已经回复了旧的截图
1027
            return;
1028 1029 1030
        }

        //无截图或者截图已经过期
1031 1032 1033 1034 1035 1036 1037
        if (!have_old_snap) {
            //无过期截图,生成一个空文件,目的是顺便创建文件夹路径
            //同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程
            auto file = File::create_file(new_snap.data(), "wb");
            if (file) {
                fclose(file);
            }
1038 1039
        }

1040 1041 1042 1043
        //启动FFmpeg进程,开始截图,生成临时文件,截图成功后替换为正式文件
        auto new_snap_tmp = new_snap + ".tmp";
        FFmpegSnap::makeSnap(allArgs["url"], new_snap_tmp, allArgs["timeout_sec"], [invoker, headerIn, new_snap, new_snap_tmp](bool success) {
            if (!success) {
1044
                //生成截图失败,可能残留空文件
1045 1046 1047 1048 1049
                File::delete_file(new_snap_tmp.data());
            } else {
                //临时文件改成正式文件
                File::delete_file(new_snap.data());
                rename(new_snap_tmp.data(), new_snap.data());
1050
            }
1051
            responseSnap(new_snap, headerIn, invoker);
1052 1053 1054
        });
    });

1055 1056 1057 1058 1059
    api_regist("/index/api/getStatistic",[](API_ARGS_MAP){
        CHECK_SECRET();
        val["data"]["MediaSource"] = (Json::UInt64)(ObjectStatistic<MediaSource>::count());
        val["data"]["MultiMediaSourceMuxer"] = (Json::UInt64)(ObjectStatistic<MultiMediaSourceMuxer>::count());

xiongziliang committed
1060
        val["data"]["TcpServer"] = (Json::UInt64)(ObjectStatistic<TcpServer>::count());
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
        val["data"]["TcpSession"] = (Json::UInt64)(ObjectStatistic<TcpSession>::count());
        val["data"]["TcpClient"] = (Json::UInt64)(ObjectStatistic<TcpClient>::count());
        val["data"]["Socket"] = (Json::UInt64)(ObjectStatistic<Socket>::count());

        val["data"]["FrameImp"] = (Json::UInt64)(ObjectStatistic<FrameImp>::count());
        val["data"]["Frame"] = (Json::UInt64)(ObjectStatistic<Frame>::count());

        val["data"]["Buffer"] = (Json::UInt64)(ObjectStatistic<Buffer>::count());
        val["data"]["BufferRaw"] = (Json::UInt64)(ObjectStatistic<BufferRaw>::count());
        val["data"]["BufferLikeString"] = (Json::UInt64)(ObjectStatistic<BufferLikeString>::count());
        val["data"]["BufferList"] = (Json::UInt64)(ObjectStatistic<BufferList>::count());
xia-chu committed
1072 1073 1074

        val["data"]["RtpPacket"] = (Json::UInt64)(ObjectStatistic<RtpPacket>::count());
        val["data"]["RtmpPacket"] = (Json::UInt64)(ObjectStatistic<RtmpPacket>::count());
1075 1076 1077 1078 1079
#ifdef ENABLE_MEM_DEBUG
        auto bytes = getTotalMemUsage();
        val["data"]["totalMemUsage"] = (Json::UInt64)bytes;
        val["data"]["totalMemUsageMB"] = (int)(bytes / 1024 / 1024);
#endif
1080 1081
    });

1082
#ifdef ENABLE_WEBRTC
1083
    api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
1084
        CHECK_ARGS("app", "stream");
1085 1086 1087 1088 1089 1090 1091 1092

        auto offer_sdp = allArgs.Content();
        auto type = allArgs.getUrlArgs()["type"];
        MediaInfo info(StrPrinter << "rtc://" << headerIn["Host"] << "/" << allArgs.getUrlArgs()["app"] << "/" << allArgs.getUrlArgs()["stream"] << "?" << allArgs.Params());

        //设置返回类型
        headerOut["Content-Type"] = HttpFileManager::getContentType(".json");
        //设置跨域
1093
        headerOut["Access-Control-Allow-Origin"] = "*";
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105

        if (type.empty() || !strcasecmp(type.data(), "play")) {
            Broadcast::AuthInvoker authInvoker = [invoker, offer_sdp, val, info, headerOut](const string &err) mutable {
                try {
                    auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA, info._vhost, info._app, info._streamid));
                    if (!src) {
                        throw runtime_error("流不存在");
                    }
                    if (!err.empty()) {
                        throw runtime_error(StrPrinter << "播放鉴权失败:" << err);
                    }
                    auto rtc = WebRtcTransportImp::create(EventPollerPool::Instance().getPoller());
xia-chu committed
1106
                    rtc->attach(src, info, true);
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
                    val["sdp"] = rtc->getAnswerSdp(offer_sdp);
                    val["type"] = "answer";
                    invoker(200, headerOut, val.toStyledString());
                } catch (std::exception &ex) {
                    val["code"] = API::Exception;
                    val["msg"] = ex.what();
                    invoker(200, headerOut, val.toStyledString());
                }
            };

            //广播通用播放url鉴权事件
            auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, info, authInvoker, sender);
            if (!flag) {
                //该事件无人监听,默认不鉴权
                authInvoker("");
            }
            return;
        }

        if (!strcasecmp(type.data(), "push")) {
            Broadcast::PublishAuthInvoker authInvoker = [invoker, offer_sdp, val, info, headerOut](const string &err, bool enableHls, bool enableMP4) mutable {
                try {
                    auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA, info._vhost, info._app, info._streamid));
                    if (src) {
                        throw std::runtime_error("已经在推流");
                    }
                    if (!err.empty()) {
                        throw runtime_error(StrPrinter << "推流鉴权失败:" << err);
                    }
                    auto push_src = std::make_shared<RtspMediaSourceImp>(info._vhost, info._app, info._streamid);
                    push_src->setProtocolTranslation(enableHls, enableMP4);
                    auto rtc = WebRtcTransportImp::create(EventPollerPool::Instance().getPoller());
1139
                    push_src->setListener(rtc);
xia-chu committed
1140
                    rtc->attach(push_src, info, false);
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
                    val["sdp"] = rtc->getAnswerSdp(offer_sdp);
                    val["type"] = "answer";
                    invoker(200, headerOut, val.toStyledString());
                } catch (std::exception &ex) {
                    val["code"] = API::Exception;
                    val["msg"] = ex.what();
                    invoker(200, headerOut, val.toStyledString());
                }
            };

            //rtsp推流需要鉴权
            auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, info, authInvoker, sender);
            if (!flag) {
                //该事件无人监听,默认不鉴权
                GET_CONFIG(bool, toHls, General::kPublishToHls);
                GET_CONFIG(bool, toMP4, General::kPublishToMP4);
                authInvoker("", toHls, toMP4);
            }
            return;
        }

        throw ApiRetException("不支持该类型", API::InvalidArgs);
1163 1164 1165
    });
#endif

1166
    ////////////以下是注册的Hook API////////////
1167
    api_regist("/index/hook/on_publish",[](API_ARGS_MAP){
1168
        //开始推流事件
1169 1170 1171 1172 1173 1174
        //转换成rtsp或rtmp
        val["enableRtxp"] = true;
        //转换hls
        val["enableHls"] = true;
        //不录制mp4
        val["enableMP4"] = false;
1175 1176
    });

1177
    api_regist("/index/hook/on_play",[](API_ARGS_MAP){
1178 1179 1180
        //开始播放事件
    });

1181
    api_regist("/index/hook/on_flow_report",[](API_ARGS_MAP){
1182 1183
        //流量统计hook api
    });
xiongziliang committed
1184

1185
    api_regist("/index/hook/on_rtsp_realm",[](API_ARGS_MAP){
1186
        //rtsp是否需要鉴权,默认需要鉴权
xiongziliang committed
1187
        val["code"] = API::Success;
xiongziliang committed
1188 1189 1190
        val["realm"] = "zlmediakit_reaml";
    });

1191
    api_regist("/index/hook/on_rtsp_auth",[](API_ARGS_MAP){
xiongziliang committed
1192 1193
        //rtsp鉴权密码,密码等于用户名
        //rtsp可以有双重鉴权!后面还会触发on_play事件
1194
        CHECK_ARGS("user_name");
xiongziliang committed
1195
        val["code"] = API::Success;
xiongziliang committed
1196
        val["encrypted"] = false;
1197
        val["passwd"] = allArgs["user_name"].data();
xiongziliang committed
1198 1199
    });

1200
    api_regist("/index/hook/on_stream_changed",[](API_ARGS_JSON){
xiongziliang committed
1201 1202 1203
        //媒体注册或反注册事件
    });

xiongziliang committed
1204 1205

#if !defined(_WIN32)
1206
    api_regist("/index/hook/on_stream_not_found_ffmpeg",[](API_ARGS_MAP_ASYNC){
1207
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
1208 1209
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
1210 1211
        //通过FFmpeg按需拉流
        GET_CONFIG(int,rtmp_port,Rtmp::kPort);
1212 1213
        GET_CONFIG(int,timeout_sec,Hook::kTimeoutSec);

1214 1215 1216 1217 1218 1219 1220
        string dst_url = StrPrinter
                << "rtmp://127.0.0.1:"
                << rtmp_port << "/"
                << allArgs["app"] << "/"
                << allArgs["stream"] << "?vhost="
                << allArgs["vhost"];

1221
        addFFmpegSource("", "http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
1222
                        dst_url,
1223
                        (1000 * timeout_sec) - 500,
1224 1225
                        false,
                        false,
xiongziliang committed
1226
                        [invoker,val,headerOut](const SockException &ex,const string &key) mutable{
1227
                            if(ex){
xiongziliang committed
1228 1229
                                val["code"] = API::OtherFailed;
                                val["msg"] = ex.what();
1230
                            }else{
xiongziliang committed
1231
                                val["data"]["key"] = key;
1232
                            }
1233
                            invoker(200, headerOut, val.toStyledString());
1234
                        });
xiongziliang committed
1235
    });
1236
#endif//!defined(_WIN32)
xiongziliang committed
1237

1238
    api_regist("/index/hook/on_stream_not_found",[](API_ARGS_MAP_ASYNC){
xiongziliang committed
1239 1240 1241
        //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
        CHECK_SECRET();
        CHECK_ARGS("vhost","app","stream");
1242 1243 1244 1245 1246
        //通过内置支持的rtsp/rtmp按需拉流
        addStreamProxy(allArgs["vhost"],
                       allArgs["app"],
                       allArgs["stream"],
                       /** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
1247 1248 1249
                       "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
                       true,/* 开启hls转发 */
                       false,/* 禁用MP4录制 */
1250
                       0,//rtp over tcp方式拉流
xiongziliang committed
1251
                       [invoker,val,headerOut](const SockException &ex,const string &key) mutable{
1252
                           if(ex){
xiongziliang committed
1253 1254
                               val["code"] = API::OtherFailed;
                               val["msg"] = ex.what();
1255
                           }else{
xiongziliang committed
1256
                               val["data"]["key"] = key;
1257
                           }
1258
                           invoker(200, headerOut, val.toStyledString());
1259
                       });
xiongziliang committed
1260 1261
    });

1262
    api_regist("/index/hook/on_record_mp4",[](API_ARGS_MAP){
xiongziliang committed
1263 1264 1265
        //录制mp4分片完毕事件
    });

1266
    api_regist("/index/hook/on_shell_login",[](API_ARGS_MAP){
1267 1268
        //shell登录调试事件
    });
1269

1270
    api_regist("/index/hook/on_stream_none_reader",[](API_ARGS_MAP){
1271 1272 1273 1274
        //无人观看流默认关闭
        val["close"] = true;
    });

1275 1276 1277 1278 1279
    static auto checkAccess = [](const string &params){
        //我们假定大家都要权限访问
        return true;
    };

1280
    api_regist("/index/hook/on_http_access",[](API_ARGS_MAP){
1281 1282 1283
        //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
        if(!checkAccess(allArgs["params"])){
            //无访问权限
1284 1285
            val["err"] = "无访问权限";
            //仅限制访问当前目录
1286
            val["path"] = "";
1287
            //标记该客户端无权限1分钟
1288 1289 1290 1291
            val["second"] = 60;
            return;
        }

1292 1293 1294 1295 1296
        //可以访问
        val["err"] = "";
        //只能访问当前目录
        val["path"] = "";
        //该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
1297
        val["second"] = 10 * 60;
1298 1299
    });

1300

1301
    api_regist("/index/hook/on_server_started",[](API_ARGS_MAP){
1302 1303
        //服务器重启报告
    });
1304
}
xiongziliang committed
1305

1306
void unInstallWebApi(){
1307 1308 1309 1310 1311 1312 1313 1314 1315
    {
        lock_guard<recursive_mutex> lck(s_proxyMapMtx);
        s_proxyMap.clear();
    }

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

1317 1318
    {
#if defined(ENABLE_RTPPROXY)
1319
        RtpSelector::Instance().clear();
1320 1321 1322 1323
        lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
        s_rtpServerMap.clear();
#endif
    }
1324
}