2017-10-09 22:11:01 +08:00
|
|
|
|
/*
|
2017-09-27 16:20:30 +08:00
|
|
|
|
* MIT License
|
2017-04-01 16:35:56 +08:00
|
|
|
|
*
|
2019-05-08 15:40:07 +08:00
|
|
|
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
2017-09-27 16:20:30 +08:00
|
|
|
|
*
|
|
|
|
|
* 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.
|
2017-04-01 16:35:56 +08:00
|
|
|
|
*/
|
2017-08-10 14:06:51 +08:00
|
|
|
|
#if !defined(_WIN32)
|
|
|
|
|
#include <dirent.h>
|
|
|
|
|
#endif //!defined(_WIN32)
|
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <algorithm>
|
2019-04-17 10:32:49 +08:00
|
|
|
|
#include <iomanip>
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2017-05-02 17:15:12 +08:00
|
|
|
|
#include "Common/config.h"
|
2017-04-01 16:35:56 +08:00
|
|
|
|
#include "strCoding.h"
|
|
|
|
|
#include "HttpSession.h"
|
|
|
|
|
#include "Util/File.h"
|
|
|
|
|
#include "Util/util.h"
|
|
|
|
|
#include "Util/TimeTicker.h"
|
|
|
|
|
#include "Util/onceToken.h"
|
2017-04-25 11:35:41 +08:00
|
|
|
|
#include "Util/mini.h"
|
2017-04-01 16:35:56 +08:00
|
|
|
|
#include "Util/NoticeCenter.h"
|
2018-09-20 18:20:43 +08:00
|
|
|
|
#include "Util/base64.h"
|
|
|
|
|
#include "Util/SHA1.h"
|
2017-12-15 16:01:21 +08:00
|
|
|
|
#include "Rtmp/utils.h"
|
2018-10-24 17:17:55 +08:00
|
|
|
|
using namespace toolkit;
|
2017-08-10 14:06:51 +08:00
|
|
|
|
|
2018-10-24 17:17:55 +08:00
|
|
|
|
namespace mediakit {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2018-09-26 23:12:03 +08:00
|
|
|
|
static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE;
|
2019-06-14 18:42:09 +08:00
|
|
|
|
static int kHlsCookieSecond = 10 * 60;
|
2019-06-12 17:53:48 +08:00
|
|
|
|
static const string kCookieName = "ZL_COOKIE";
|
2019-06-14 18:42:09 +08:00
|
|
|
|
static const string kCookiePathKey = "kCookiePathKey";
|
|
|
|
|
static const string kAccessErrKey = "kAccessErrKey";
|
2018-02-06 16:34:02 +08:00
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
string dateStr() {
|
|
|
|
|
char buf[64];
|
|
|
|
|
time_t tt = time(NULL);
|
|
|
|
|
strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
static const char*
|
|
|
|
|
get_mime_type(const char* name) {
|
|
|
|
|
const char* dot;
|
|
|
|
|
dot = strrchr(name, '.');
|
2017-12-15 16:01:21 +08:00
|
|
|
|
static HttpSession::KeyValue mapType;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
static onceToken token([&]() {
|
|
|
|
|
mapType.emplace(".html","text/html");
|
|
|
|
|
mapType.emplace(".htm","text/html");
|
|
|
|
|
mapType.emplace(".mp4","video/mp4");
|
|
|
|
|
mapType.emplace(".m3u8","application/vnd.apple.mpegurl");
|
|
|
|
|
mapType.emplace(".jpg","image/jpeg");
|
|
|
|
|
mapType.emplace(".jpeg","image/jpeg");
|
|
|
|
|
mapType.emplace(".gif","image/gif");
|
|
|
|
|
mapType.emplace(".png","image/png");
|
|
|
|
|
mapType.emplace(".ico","image/x-icon");
|
|
|
|
|
mapType.emplace(".css","text/css");
|
|
|
|
|
mapType.emplace(".js","application/javascript");
|
|
|
|
|
mapType.emplace(".au","audio/basic");
|
|
|
|
|
mapType.emplace(".wav","audio/wav");
|
|
|
|
|
mapType.emplace(".avi","video/x-msvideo");
|
|
|
|
|
mapType.emplace(".mov","video/quicktime");
|
|
|
|
|
mapType.emplace(".qt","video/quicktime");
|
|
|
|
|
mapType.emplace(".mpeg","video/mpeg");
|
|
|
|
|
mapType.emplace(".mpe","video/mpeg");
|
|
|
|
|
mapType.emplace(".vrml","model/vrml");
|
|
|
|
|
mapType.emplace(".wrl","model/vrml");
|
|
|
|
|
mapType.emplace(".midi","audio/midi");
|
|
|
|
|
mapType.emplace(".mid","audio/midi");
|
|
|
|
|
mapType.emplace(".mp3","audio/mpeg");
|
|
|
|
|
mapType.emplace(".ogg","application/ogg");
|
|
|
|
|
mapType.emplace(".pac","application/x-ns-proxy-autoconfig");
|
2017-12-15 16:01:21 +08:00
|
|
|
|
mapType.emplace(".flv","video/x-flv");
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}, nullptr);
|
|
|
|
|
if(!dot){
|
|
|
|
|
return "text/plain";
|
|
|
|
|
}
|
2017-12-15 16:01:21 +08:00
|
|
|
|
auto it = mapType.find(dot);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if (it == mapType.end()) {
|
|
|
|
|
return "text/plain";
|
|
|
|
|
}
|
|
|
|
|
return it->second.data();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-12-20 10:42:51 +08:00
|
|
|
|
HttpSession::HttpSession(const Socket::Ptr &pSock) : TcpSession(pSock) {
|
2019-05-29 18:08:50 +08:00
|
|
|
|
TraceP(this);
|
2019-05-30 10:41:25 +08:00
|
|
|
|
GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
|
|
|
|
|
pSock->setSendTimeOutSecond(keep_alive_sec);
|
2019-04-23 11:52:40 +08:00
|
|
|
|
//起始接收buffer缓存设置为4K,节省内存
|
|
|
|
|
pSock->setReadBuffer(std::make_shared<BufferRaw>(4 * 1024));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HttpSession::~HttpSession() {
|
2019-05-29 18:08:50 +08:00
|
|
|
|
TraceP(this);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-23 00:55:00 +08:00
|
|
|
|
int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) {
|
2019-05-30 10:59:14 +08:00
|
|
|
|
typedef void (HttpSession::*HttpCMDHandle)(int64_t &);
|
2018-08-10 11:55:18 +08:00
|
|
|
|
static unordered_map<string, HttpCMDHandle> g_mapCmdIndex;
|
|
|
|
|
static onceToken token([]() {
|
|
|
|
|
g_mapCmdIndex.emplace("GET",&HttpSession::Handle_Req_GET);
|
|
|
|
|
g_mapCmdIndex.emplace("POST",&HttpSession::Handle_Req_POST);
|
|
|
|
|
}, nullptr);
|
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_parser.Parse(header);
|
|
|
|
|
urlDecode(_parser);
|
|
|
|
|
string cmd = _parser.Method();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
auto it = g_mapCmdIndex.find(cmd);
|
|
|
|
|
if (it == g_mapCmdIndex.end()) {
|
|
|
|
|
sendResponse("403 Forbidden", makeHttpHeader(true), "");
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,StrPrinter << "403 Forbidden:" << cmd));
|
|
|
|
|
return 0;
|
2018-09-20 17:50:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-30 16:07:00 +08:00
|
|
|
|
//跨域
|
|
|
|
|
_origin = _parser["Origin"];
|
|
|
|
|
|
|
|
|
|
//默认后面数据不是content而是header
|
2018-09-20 17:50:58 +08:00
|
|
|
|
int64_t content_len = 0;
|
|
|
|
|
auto &fun = it->second;
|
2019-05-30 10:59:14 +08:00
|
|
|
|
try {
|
|
|
|
|
(this->*fun)(content_len);
|
|
|
|
|
}catch (SockException &ex){
|
|
|
|
|
if(ex){
|
|
|
|
|
shutdown(ex);
|
|
|
|
|
}
|
|
|
|
|
}catch (exception &ex){
|
|
|
|
|
shutdown(SockException(Err_shutdown,ex.what()));
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-20 17:50:58 +08:00
|
|
|
|
//清空解析器节省内存
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_parser.Clear();
|
2018-09-20 17:50:58 +08:00
|
|
|
|
//返回content长度
|
|
|
|
|
return content_len;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-09-20 17:50:58 +08:00
|
|
|
|
|
2018-09-23 00:55:00 +08:00
|
|
|
|
void HttpSession::onRecvContent(const char *data,uint64_t len) {
|
2018-10-24 15:43:52 +08:00
|
|
|
|
if(_contentCallBack){
|
|
|
|
|
if(!_contentCallBack(data,len)){
|
|
|
|
|
_contentCallBack = nullptr;
|
2018-09-20 17:50:58 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_ticker.resetTime();
|
2018-12-19 16:54:11 +08:00
|
|
|
|
input(pBuf->data(),pBuf->size());
|
2018-09-20 17:50:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
void HttpSession::onError(const SockException& err) {
|
2019-05-29 18:08:50 +08:00
|
|
|
|
if(_ticker.createdTime() < 10 * 1000){
|
|
|
|
|
TraceP(this) << err.what();
|
|
|
|
|
}else{
|
|
|
|
|
WarnP(this) << err.what();
|
|
|
|
|
}
|
2018-02-09 11:42:55 +08:00
|
|
|
|
|
2019-05-29 18:08:50 +08:00
|
|
|
|
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
|
2018-10-24 15:43:52 +08:00
|
|
|
|
if(_ui64TotalBytes > iFlowThreshold * 1024){
|
2018-10-09 09:36:03 +08:00
|
|
|
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport,
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_mediaInfo,
|
|
|
|
|
_ui64TotalBytes,
|
|
|
|
|
_ticker.createdTime()/1000,
|
2019-05-20 17:46:06 +08:00
|
|
|
|
true,
|
2018-10-09 09:36:03 +08:00
|
|
|
|
*this);
|
2018-02-06 15:28:27 +08:00
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpSession::onManager() {
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
2018-02-09 11:42:55 +08:00
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
if(_ticker.elapsedTime() > keepAliveSec * 1000){
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//1分钟超时
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_timeout,"session timeouted"));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-20 18:20:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline bool HttpSession::checkWebSocket(){
|
2018-10-24 15:43:52 +08:00
|
|
|
|
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
2018-09-29 11:08:02 +08:00
|
|
|
|
if(Sec_WebSocket_Key.empty()){
|
2018-09-20 18:20:43 +08:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
|
|
|
|
|
|
|
|
|
KeyValue headerOut;
|
|
|
|
|
headerOut["Upgrade"] = "websocket";
|
|
|
|
|
headerOut["Connection"] = "Upgrade";
|
|
|
|
|
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
|
2019-04-02 15:07:54 +08:00
|
|
|
|
if(!_parser["Sec-WebSocket-Protocol"].empty()){
|
|
|
|
|
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
|
|
|
|
}
|
2019-08-23 09:45:01 +08:00
|
|
|
|
|
|
|
|
|
auto res_cb = [this,headerOut](){
|
|
|
|
|
_flv_over_websocket = true;
|
|
|
|
|
sendResponse("101 Switching Protocols",headerOut,"");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//判断是否为websocket-flv
|
|
|
|
|
if(checkLiveFlvStream(res_cb)){
|
|
|
|
|
//这里是websocket-flv直播请求
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接
|
|
|
|
|
sendResponse("101 Switching Protocols",headerOut,"");
|
2018-09-20 18:20:43 +08:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-02-05 15:56:44 +08:00
|
|
|
|
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
|
|
|
|
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
2019-08-23 09:45:01 +08:00
|
|
|
|
inline bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
2018-10-24 15:43:52 +08:00
|
|
|
|
auto pos = strrchr(_parser.Url().data(),'.');
|
2017-12-15 16:01:21 +08:00
|
|
|
|
if(!pos){
|
|
|
|
|
//未找到".flv"后缀
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-11-27 11:05:44 +08:00
|
|
|
|
if(strcasecmp(pos,".flv") != 0){
|
2017-12-15 16:01:21 +08:00
|
|
|
|
//未找到".flv"后缀
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-02-02 18:06:08 +08:00
|
|
|
|
|
2019-05-27 22:49:30 +08:00
|
|
|
|
//这是个.flv的流
|
|
|
|
|
_mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
|
|
|
|
|
if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
|
|
|
|
|
//url不合法
|
|
|
|
|
return false;
|
2017-12-15 16:01:21 +08:00
|
|
|
|
}
|
2019-05-27 22:49:30 +08:00
|
|
|
|
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
2017-12-15 16:01:21 +08:00
|
|
|
|
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
2019-05-27 22:49:30 +08:00
|
|
|
|
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
2018-02-05 15:56:44 +08:00
|
|
|
|
|
|
|
|
|
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
2019-08-23 09:45:01 +08:00
|
|
|
|
MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this,cb](const MediaSource::Ptr &src){
|
2018-02-05 15:56:44 +08:00
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if(!strongSelf){
|
2019-05-27 22:49:30 +08:00
|
|
|
|
//本对象已经销毁
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
|
|
|
|
if(!rtmp_src){
|
|
|
|
|
//未找到该流
|
2019-08-23 09:45:01 +08:00
|
|
|
|
sendNotFound(bClose);
|
2019-05-27 22:49:30 +08:00
|
|
|
|
if(bClose){
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,"flv stream not found"));
|
2019-05-27 22:49:30 +08:00
|
|
|
|
}
|
2018-02-05 15:56:44 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-27 22:49:30 +08:00
|
|
|
|
//找到流了
|
2019-08-23 09:45:01 +08:00
|
|
|
|
auto onRes = [this,rtmp_src,cb](const string &err){
|
2019-05-27 22:49:30 +08:00
|
|
|
|
bool authSuccess = err.empty();
|
|
|
|
|
if(!authSuccess){
|
2019-08-23 09:45:01 +08:00
|
|
|
|
sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err);
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err));
|
2019-05-27 22:49:30 +08:00
|
|
|
|
return ;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-23 09:45:01 +08:00
|
|
|
|
if(!cb) {
|
2019-08-12 18:14:51 +08:00
|
|
|
|
//找到rtmp源,发送http头,负载后续发送
|
|
|
|
|
sendResponse("200 OK", makeHttpHeader(false, 0, get_mime_type(".flv")), "");
|
2019-08-23 09:45:01 +08:00
|
|
|
|
}else{
|
|
|
|
|
cb();
|
2019-08-12 18:14:51 +08:00
|
|
|
|
}
|
2019-05-27 22:49:30 +08:00
|
|
|
|
|
|
|
|
|
//开始发送rtmp负载
|
|
|
|
|
//关闭tcp_nodelay ,优化性能
|
|
|
|
|
SockUtil::setNoDelay(_sock->rawFD(),false);
|
|
|
|
|
(*this) << SocketFlags(kSockFlags);
|
|
|
|
|
try{
|
|
|
|
|
start(getPoller(),rtmp_src);
|
|
|
|
|
}catch (std::exception &ex){
|
|
|
|
|
//该rtmp源不存在
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,"rtmp mediasource released"));
|
2019-05-27 22:49:30 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
|
|
|
|
Broadcast::AuthInvoker invoker = [weakSelf,onRes](const string &err){
|
2018-02-05 15:56:44 +08:00
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if(!strongSelf){
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-27 22:49:30 +08:00
|
|
|
|
strongSelf->async([weakSelf,onRes,err](){
|
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if(!strongSelf){
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
onRes(err);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,_mediaInfo,invoker,*this);
|
|
|
|
|
if(!flag){
|
|
|
|
|
//该事件无人监听,默认不鉴权
|
|
|
|
|
onRes("");
|
|
|
|
|
}
|
|
|
|
|
});
|
2018-02-05 15:56:44 +08:00
|
|
|
|
return true;
|
2017-12-15 16:01:21 +08:00
|
|
|
|
}
|
2019-05-16 17:09:29 +08:00
|
|
|
|
|
2019-05-28 17:14:36 +08:00
|
|
|
|
inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) ;
|
2019-05-16 17:09:29 +08:00
|
|
|
|
|
2019-05-16 17:31:48 +08:00
|
|
|
|
inline static string findIndexFile(const string &dir){
|
|
|
|
|
DIR *pDir;
|
|
|
|
|
dirent *pDirent;
|
|
|
|
|
if ((pDir = opendir(dir.data())) == NULL) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
set<string> setFile;
|
|
|
|
|
while ((pDirent = readdir(pDir)) != NULL) {
|
|
|
|
|
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"};
|
|
|
|
|
if(indexSet.find(pDirent->d_name) != indexSet.end()){
|
|
|
|
|
closedir(pDir);
|
|
|
|
|
return pDirent->d_name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
closedir(pDir);
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 17:53:48 +08:00
|
|
|
|
inline string HttpSession::getClientUid(){
|
2019-06-14 15:19:02 +08:00
|
|
|
|
//如果http客户端不支持cookie,那么我们可以通过url参数来追踪用户
|
|
|
|
|
//如果url参数也没有,那么只能通过ip+端口号来追踪用户
|
|
|
|
|
//追踪用户的目的是为了减少http短链接情况的重复鉴权验证,通过缓存记录鉴权结果,提高性能
|
|
|
|
|
string uid = _parser.Params();
|
|
|
|
|
if(uid.empty()){
|
|
|
|
|
uid = StrPrinter << get_peer_ip() << ":" << get_peer_port();
|
|
|
|
|
}
|
2019-06-12 18:55:23 +08:00
|
|
|
|
return uid;
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
2019-06-14 15:19:02 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//字符串是否以xx结尾
|
|
|
|
|
static inline bool end_of(const string &str, const string &substr){
|
|
|
|
|
auto pos = str.rfind(substr);
|
|
|
|
|
return pos != string::npos && pos == str.size() - substr.size();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//拦截hls的播放请求
|
|
|
|
|
static inline bool checkHls(BroadcastHttpAccessArgs){
|
|
|
|
|
if(!end_of(args._streamid,("/hls.m3u8"))) {
|
|
|
|
|
//不是hls
|
|
|
|
|
return false;
|
2019-06-12 18:55:23 +08:00
|
|
|
|
}
|
2019-06-14 15:19:02 +08:00
|
|
|
|
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
|
|
|
|
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
|
2019-06-14 18:42:09 +08:00
|
|
|
|
//cookie有效期为kHlsCookieSecond
|
|
|
|
|
invoker(err,"",kHlsCookieSecond);
|
2019-06-14 15:19:02 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto args_copy = args;
|
|
|
|
|
replace(args_copy._streamid,"/hls.m3u8","");
|
|
|
|
|
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-14 18:42:09 +08:00
|
|
|
|
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback_in){
|
2019-06-12 17:53:48 +08:00
|
|
|
|
auto path = path_in;
|
|
|
|
|
replace(const_cast<string &>(path),"//","/");
|
|
|
|
|
|
2019-06-14 18:42:09 +08:00
|
|
|
|
auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
2019-06-12 17:53:48 +08:00
|
|
|
|
try {
|
2019-06-14 18:42:09 +08:00
|
|
|
|
callback_in(errMsg,cookie);
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}catch (SockException &ex){
|
|
|
|
|
if(ex){
|
|
|
|
|
shutdown(ex);
|
|
|
|
|
}
|
|
|
|
|
}catch (exception &ex){
|
|
|
|
|
shutdown(SockException(Err_shutdown,ex.what()));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-14 15:19:02 +08:00
|
|
|
|
//获取用户唯一id
|
|
|
|
|
auto uid = getClientUid();
|
|
|
|
|
//先根据http头中的cookie字段获取cookie
|
|
|
|
|
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, _parser.getValues());
|
|
|
|
|
if(!cookie){
|
|
|
|
|
//客户端请求中无cookie,再根据该用户的用户id获取cookie
|
|
|
|
|
cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-14 15:19:02 +08:00
|
|
|
|
if(cookie){
|
2019-06-14 18:42:09 +08:00
|
|
|
|
//找到了cookie,对cookie上锁先
|
|
|
|
|
auto lck = cookie->getLock();
|
|
|
|
|
auto accessErr = (*cookie)[kAccessErrKey];
|
2019-06-14 21:33:41 +08:00
|
|
|
|
if(path.find((*cookie)[kCookiePathKey]) == 0){
|
|
|
|
|
//上次cookie是限定本目录
|
|
|
|
|
if(accessErr.empty()){
|
|
|
|
|
//上次鉴权成功
|
|
|
|
|
callback("", nullptr);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
//上次鉴权失败,如果url发生变更,那么也重新鉴权
|
|
|
|
|
if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
|
|
|
|
|
//url参数未变,那么判断无权限访问
|
2019-08-20 12:09:43 +08:00
|
|
|
|
callback(accessErr.empty() ? "无权限访问该目录" : accessErr.get<string>(), nullptr);
|
2019-06-14 21:33:41 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
2019-06-14 21:33:41 +08:00
|
|
|
|
//如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权
|
2019-06-14 18:42:09 +08:00
|
|
|
|
HttpCookieManager::Instance().delCookie(cookie);
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
|
|
|
|
|
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
2019-06-14 18:42:09 +08:00
|
|
|
|
HttpAccessPathInvoker accessPathInvoker = [weakSelf,callback,uid,path,is_dir] (const string &errMsg,const string &cookie_path_in, int cookieLifeSecond) {
|
|
|
|
|
HttpServerCookie::Ptr cookie ;
|
|
|
|
|
if(cookieLifeSecond) {
|
|
|
|
|
//本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
|
2019-06-14 18:47:28 +08:00
|
|
|
|
string cookie_path = cookie_path_in;
|
|
|
|
|
if(cookie_path.empty()){
|
|
|
|
|
//如果未设置鉴权目录,那么我们采用当前目录
|
|
|
|
|
cookie_path = is_dir ? path : path.substr(0,path.rfind("/") + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-14 18:51:50 +08:00
|
|
|
|
cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
|
|
|
|
|
//对cookie上锁
|
|
|
|
|
auto lck = cookie->getLock();
|
2019-06-14 18:42:09 +08:00
|
|
|
|
//记录用户能访问的路径
|
2019-08-20 12:09:43 +08:00
|
|
|
|
(*cookie)[kCookiePathKey].set<string>(cookie_path);
|
2019-06-14 18:42:09 +08:00
|
|
|
|
//记录能否访问
|
2019-08-20 12:09:43 +08:00
|
|
|
|
(*cookie)[kAccessErrKey].set<string>(errMsg);
|
2019-06-14 18:42:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 17:53:48 +08:00
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
//自己已经销毁
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-06-14 18:42:09 +08:00
|
|
|
|
strongSelf->async([weakSelf,callback,cookie,errMsg]() {
|
2019-06-12 17:53:48 +08:00
|
|
|
|
//切换到自己线程
|
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
//自己已经销毁
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-06-14 18:42:09 +08:00
|
|
|
|
callback(errMsg, cookie);
|
2019-06-12 17:53:48 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-14 15:19:02 +08:00
|
|
|
|
if(checkHls(_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this)){
|
|
|
|
|
//是hls的播放鉴权,拦截之
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 17:53:48 +08:00
|
|
|
|
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this);
|
|
|
|
|
if(!flag){
|
|
|
|
|
//此事件无人监听,我们默认都有权限访问
|
2019-06-14 18:42:09 +08:00
|
|
|
|
callback("", nullptr);
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2019-05-16 17:31:48 +08:00
|
|
|
|
|
2019-05-30 10:59:14 +08:00
|
|
|
|
inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
2018-09-20 18:20:43 +08:00
|
|
|
|
//先看看是否为WebSocket请求
|
|
|
|
|
if(checkWebSocket()){
|
|
|
|
|
content_len = -1;
|
2018-10-24 15:43:52 +08:00
|
|
|
|
auto parserCopy = _parser;
|
|
|
|
|
_contentCallBack = [this,parserCopy](const char *data,uint64_t len){
|
2018-09-23 00:55:00 +08:00
|
|
|
|
onRecvWebSocketData(parserCopy,data,len);
|
2018-10-24 15:43:52 +08:00
|
|
|
|
//_contentCallBack是可持续的,后面还要处理后续数据
|
2018-09-20 18:20:43 +08:00
|
|
|
|
return true;
|
|
|
|
|
};
|
2019-05-30 10:59:14 +08:00
|
|
|
|
return;
|
2018-09-20 18:20:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-08 22:37:17 +08:00
|
|
|
|
//先看看该http事件是否被拦截
|
|
|
|
|
if(emitHttpEvent(false)){
|
2019-05-30 10:59:14 +08:00
|
|
|
|
return;
|
2017-10-10 00:04:07 +08:00
|
|
|
|
}
|
2018-09-20 18:20:43 +08:00
|
|
|
|
|
2018-03-14 22:32:36 +08:00
|
|
|
|
//再看看是否为http-flv直播请求
|
2019-08-23 09:45:01 +08:00
|
|
|
|
if(checkLiveFlvStream()){
|
2019-05-30 10:59:14 +08:00
|
|
|
|
return;
|
2017-12-15 16:01:21 +08:00
|
|
|
|
}
|
2018-02-02 18:06:08 +08:00
|
|
|
|
|
2018-09-20 18:20:43 +08:00
|
|
|
|
//事件未被拦截,则认为是http下载请求
|
2018-10-24 15:43:52 +08:00
|
|
|
|
auto fullUrl = string(HTTP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl();
|
|
|
|
|
_mediaInfo.parse(fullUrl);
|
2018-02-05 15:56:44 +08:00
|
|
|
|
|
2017-12-08 22:37:17 +08:00
|
|
|
|
/////////////HTTP连接是否需要被关闭////////////////
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
|
|
|
|
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
|
|
|
|
GET_CONFIG(string,rootPath,Http::kRootPath);
|
2019-05-16 17:09:29 +08:00
|
|
|
|
string strFile = enableVhost ? rootPath + "/" + _mediaInfo._vhost + _parser.Url() :rootPath + _parser.Url();
|
2018-11-27 11:05:44 +08:00
|
|
|
|
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
2019-05-16 17:31:48 +08:00
|
|
|
|
|
|
|
|
|
do{
|
|
|
|
|
//访问的是文件夹
|
|
|
|
|
if (strFile.back() == '/' || File::is_dir(strFile.data())) {
|
|
|
|
|
auto indexFile = findIndexFile(strFile);
|
|
|
|
|
if(!indexFile.empty()){
|
|
|
|
|
//发现该文件夹下有index文件
|
|
|
|
|
strFile = strFile + "/" + indexFile;
|
2019-06-12 17:53:48 +08:00
|
|
|
|
_parser.setUrl(_parser.Url() + "/" + indexFile);
|
2019-05-16 17:31:48 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
string strMeun;
|
2019-06-12 17:53:48 +08:00
|
|
|
|
//生成文件夹菜单索引
|
2019-05-28 17:14:36 +08:00
|
|
|
|
if (!makeMeun(_parser.Url(),strFile,strMeun)) {
|
2019-05-16 17:31:48 +08:00
|
|
|
|
//文件夹不存在
|
|
|
|
|
sendNotFound(bClose);
|
2019-05-30 10:59:14 +08:00
|
|
|
|
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder");
|
2019-05-16 17:31:48 +08:00
|
|
|
|
}
|
2019-06-12 17:53:48 +08:00
|
|
|
|
|
|
|
|
|
//判断是否有权限访问该目录
|
2019-06-14 18:47:28 +08:00
|
|
|
|
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
2019-06-14 18:42:09 +08:00
|
|
|
|
if(!errMsg.empty()){
|
|
|
|
|
const_cast<string &>(strMeun) = errMsg;
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
|
|
|
|
auto headerOut = makeHttpHeader(bClose,strMeun.size());
|
|
|
|
|
if(cookie){
|
2019-08-20 12:09:43 +08:00
|
|
|
|
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
2019-06-12 17:53:48 +08:00
|
|
|
|
}
|
2019-06-14 18:42:09 +08:00
|
|
|
|
sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
|
2019-06-12 17:53:48 +08:00
|
|
|
|
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
|
|
|
|
|
});
|
|
|
|
|
return;
|
2019-05-16 17:31:48 +08:00
|
|
|
|
}
|
|
|
|
|
}while(0);
|
|
|
|
|
|
2017-12-08 22:37:17 +08:00
|
|
|
|
//访问的是文件
|
2017-04-01 16:35:56 +08:00
|
|
|
|
struct stat tFileStat;
|
|
|
|
|
if (0 != stat(strFile.data(), &tFileStat)) {
|
2017-12-08 22:37:17 +08:00
|
|
|
|
//文件不存在
|
2017-04-01 16:35:56 +08:00
|
|
|
|
sendNotFound(bClose);
|
2019-05-30 10:59:14 +08:00
|
|
|
|
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on file");
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-03-14 22:32:36 +08:00
|
|
|
|
//文件智能指针,防止退出时未关闭
|
|
|
|
|
std::shared_ptr<FILE> pFilePtr(fopen(strFile.data(), "rb"), [](FILE *pFile) {
|
|
|
|
|
if(pFile){
|
|
|
|
|
fclose(pFile);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!pFilePtr) {
|
2017-12-08 22:37:17 +08:00
|
|
|
|
//打开文件失败
|
2017-04-01 16:35:56 +08:00
|
|
|
|
sendNotFound(bClose);
|
2019-05-30 10:59:14 +08:00
|
|
|
|
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on open file failed");
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2018-03-14 22:32:36 +08:00
|
|
|
|
|
2019-06-12 17:53:48 +08:00
|
|
|
|
auto parser = _parser;
|
|
|
|
|
//判断是否有权限访问该文件
|
2019-06-14 18:42:09 +08:00
|
|
|
|
canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
|
|
|
|
if(!errMsg.empty()){
|
|
|
|
|
auto headerOut = makeHttpHeader(bClose,errMsg.size());
|
|
|
|
|
if(cookie){
|
2019-08-20 12:09:43 +08:00
|
|
|
|
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
2019-06-14 18:42:09 +08:00
|
|
|
|
}
|
|
|
|
|
sendResponse("401 Unauthorized" , headerOut, errMsg);
|
|
|
|
|
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed");
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 17:53:48 +08:00
|
|
|
|
//判断是不是分节下载
|
|
|
|
|
auto &strRange = parser["Range"];
|
|
|
|
|
int64_t iRangeStart = 0, iRangeEnd = 0;
|
|
|
|
|
iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
|
|
|
|
iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data());
|
|
|
|
|
if (iRangeEnd == 0) {
|
|
|
|
|
iRangeEnd = tFileStat.st_size - 1;
|
|
|
|
|
}
|
|
|
|
|
const char *pcHttpResult = NULL;
|
|
|
|
|
if (strRange.size() == 0) {
|
|
|
|
|
//全部下载
|
|
|
|
|
pcHttpResult = "200 OK";
|
|
|
|
|
} else {
|
|
|
|
|
//分节下载
|
|
|
|
|
pcHttpResult = "206 Partial Content";
|
|
|
|
|
fseek(pFilePtr.get(), iRangeStart, SEEK_SET);
|
|
|
|
|
}
|
2019-06-14 18:42:09 +08:00
|
|
|
|
auto httpHeader = makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()));
|
2019-06-12 17:53:48 +08:00
|
|
|
|
if (strRange.size() != 0) {
|
|
|
|
|
//分节下载返回Content-Range头
|
|
|
|
|
httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl);
|
|
|
|
|
}
|
|
|
|
|
auto Origin = parser["Origin"];
|
|
|
|
|
if(!Origin.empty()){
|
|
|
|
|
httpHeader["Access-Control-Allow-Origin"] = Origin;
|
|
|
|
|
httpHeader["Access-Control-Allow-Credentials"] = "true";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//先回复HTTP头部分
|
2019-06-14 18:42:09 +08:00
|
|
|
|
sendResponse(pcHttpResult,httpHeader,"");
|
|
|
|
|
|
|
|
|
|
if (iRangeEnd - iRangeStart < 0) {
|
2019-06-12 17:53:48 +08:00
|
|
|
|
//文件是空的!
|
|
|
|
|
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file");
|
|
|
|
|
}
|
|
|
|
|
//回复Content部分
|
|
|
|
|
std::shared_ptr<int64_t> piLeft(new int64_t(iRangeEnd - iRangeStart + 1));
|
|
|
|
|
|
|
|
|
|
GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize);
|
|
|
|
|
|
|
|
|
|
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
|
|
|
|
auto onFlush = [pFilePtr,bClose,weakSelf,piLeft]() {
|
|
|
|
|
TimeTicker();
|
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
while(*piLeft && strongSelf){
|
|
|
|
|
//更新超时定时器
|
|
|
|
|
strongSelf->_ticker.resetTime();
|
|
|
|
|
//从循环池获取一个内存片
|
|
|
|
|
auto sendBuf = strongSelf->obtainBuffer();
|
|
|
|
|
sendBuf->setCapacity(sendBufSize);
|
|
|
|
|
//本次需要读取文件字节数
|
|
|
|
|
int64_t iReq = MIN(sendBufSize,*piLeft);
|
|
|
|
|
//读文件
|
|
|
|
|
int iRead;
|
|
|
|
|
do{
|
|
|
|
|
iRead = fread(sendBuf->data(), 1, iReq, pFilePtr.get());
|
|
|
|
|
}while(-1 == iRead && UV_EINTR == get_uv_error(false));
|
|
|
|
|
//文件剩余字节数
|
|
|
|
|
*piLeft -= iRead;
|
|
|
|
|
|
|
|
|
|
if (iRead < iReq || !*piLeft) {
|
|
|
|
|
//文件读完
|
|
|
|
|
if(iRead>0) {
|
|
|
|
|
sendBuf->setSize(iRead);
|
|
|
|
|
strongSelf->send(sendBuf);
|
|
|
|
|
}
|
|
|
|
|
if(bClose) {
|
|
|
|
|
strongSelf->shutdown(SockException(Err_shutdown,"read file eof"));
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
//文件还未读完
|
|
|
|
|
sendBuf->setSize(iRead);
|
|
|
|
|
int iSent = strongSelf->send(sendBuf);
|
|
|
|
|
if(iSent == -1) {
|
|
|
|
|
//套机制销毁
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if(strongSelf->isSocketBusy()){
|
|
|
|
|
//套接字忙,那么停止继续写
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
//继续写套接字
|
2018-03-14 14:51:54 +08:00
|
|
|
|
}
|
2019-06-12 17:53:48 +08:00
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
//关闭tcp_nodelay ,优化性能
|
|
|
|
|
SockUtil::setNoDelay(_sock->rawFD(),false);
|
|
|
|
|
//设置MSG_MORE,优化性能
|
|
|
|
|
(*this) << SocketFlags(kSockFlags);
|
|
|
|
|
|
|
|
|
|
onFlush();
|
|
|
|
|
_sock->setOnFlush(onFlush);
|
|
|
|
|
});
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-28 17:14:36 +08:00
|
|
|
|
inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
string strPathPrefix(strFullPath);
|
2019-04-17 10:32:49 +08:00
|
|
|
|
string last_dir_name;
|
|
|
|
|
if(strPathPrefix.back() == '/'){
|
|
|
|
|
strPathPrefix.pop_back();
|
|
|
|
|
}else{
|
|
|
|
|
last_dir_name = split(strPathPrefix,"/").back();
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if (!File::is_dir(strPathPrefix.data())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-04-17 10:32:49 +08:00
|
|
|
|
stringstream ss;
|
|
|
|
|
ss << "<html>\r\n"
|
2017-04-01 16:35:56 +08:00
|
|
|
|
"<head>\r\n"
|
|
|
|
|
"<title>文件索引</title>\r\n"
|
|
|
|
|
"</head>\r\n"
|
|
|
|
|
"<body>\r\n"
|
|
|
|
|
"<h1>文件索引:";
|
|
|
|
|
|
2019-05-16 17:09:29 +08:00
|
|
|
|
ss << httpPath;
|
2019-04-17 10:32:49 +08:00
|
|
|
|
ss << "</h1>\r\n";
|
2019-05-16 17:09:29 +08:00
|
|
|
|
if (httpPath != "/") {
|
2019-04-17 10:32:49 +08:00
|
|
|
|
ss << "<li><a href=\"";
|
|
|
|
|
ss << "/";
|
|
|
|
|
ss << "\">";
|
|
|
|
|
ss << "根目录";
|
|
|
|
|
ss << "</a></li>\r\n";
|
|
|
|
|
|
|
|
|
|
ss << "<li><a href=\"";
|
|
|
|
|
if(!last_dir_name.empty()){
|
|
|
|
|
ss << "./";
|
|
|
|
|
}else{
|
|
|
|
|
ss << "../";
|
|
|
|
|
}
|
|
|
|
|
ss << "\">";
|
|
|
|
|
ss << "上级目录";
|
|
|
|
|
ss << "</a></li>\r\n";
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DIR *pDir;
|
|
|
|
|
dirent *pDirent;
|
|
|
|
|
if ((pDir = opendir(strPathPrefix.data())) == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
set<string> setFile;
|
|
|
|
|
while ((pDirent = readdir(pDir)) != NULL) {
|
|
|
|
|
if (File::is_special_dir(pDirent->d_name)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(pDirent->d_name[0] == '.'){
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
setFile.emplace(pDirent->d_name);
|
|
|
|
|
}
|
2019-04-17 10:32:49 +08:00
|
|
|
|
int i = 0;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
for(auto &strFile :setFile ){
|
|
|
|
|
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
2019-04-17 10:32:49 +08:00
|
|
|
|
bool isDir = File::is_dir(strAbsolutePath.data());
|
|
|
|
|
ss << "<li><span>" << i++ << "</span>\t";
|
|
|
|
|
ss << "<a href=\"";
|
|
|
|
|
if(!last_dir_name.empty()){
|
|
|
|
|
ss << last_dir_name << "/" << strFile;
|
|
|
|
|
}else{
|
|
|
|
|
ss << strFile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(isDir){
|
|
|
|
|
ss << "/";
|
|
|
|
|
}
|
|
|
|
|
ss << "\">";
|
|
|
|
|
ss << strFile;
|
|
|
|
|
if (isDir) {
|
|
|
|
|
ss << "/</a></li>\r\n";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
//是文件
|
|
|
|
|
struct stat fileData;
|
|
|
|
|
if (0 == stat(strAbsolutePath.data(), &fileData)) {
|
|
|
|
|
auto &fileSize = fileData.st_size;
|
|
|
|
|
if (fileSize < 1024) {
|
|
|
|
|
ss << " (" << fileData.st_size << "B)" << endl;
|
|
|
|
|
} else if (fileSize < 1024 * 1024) {
|
|
|
|
|
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024.0 << "KB)";
|
|
|
|
|
} else if (fileSize < 1024 * 1024 * 1024) {
|
|
|
|
|
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024.0 << "MB)";
|
|
|
|
|
} else {
|
|
|
|
|
ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024 / 1024.0 << "GB)";
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 10:32:49 +08:00
|
|
|
|
ss << "</a></li>\r\n";
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
closedir(pDir);
|
2019-04-17 10:32:49 +08:00
|
|
|
|
ss << "<ul>\r\n";
|
|
|
|
|
ss << "</ul>\r\n</body></html>";
|
|
|
|
|
ss.str().swap(strRet);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
inline void HttpSession::sendResponse(const char* pcStatus, const KeyValue& header, const string& strContent) {
|
|
|
|
|
_StrPrinter printer;
|
2018-11-13 17:59:12 +08:00
|
|
|
|
printer << "HTTP/1.1 " << pcStatus << "\r\n";
|
2017-04-01 16:35:56 +08:00
|
|
|
|
for (auto &pr : header) {
|
|
|
|
|
printer << pr.first << ": " << pr.second << "\r\n";
|
|
|
|
|
}
|
|
|
|
|
printer << "\r\n" << strContent;
|
|
|
|
|
auto strSend = printer << endl;
|
|
|
|
|
send(strSend);
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_ticker.resetTime();
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
inline HttpSession::KeyValue HttpSession::makeHttpHeader(bool bClose, int64_t iContentSize,const char* pcContentType) {
|
|
|
|
|
KeyValue headerOut;
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(string,charSet,Http::kCharSet);
|
|
|
|
|
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
|
|
|
|
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2017-12-15 16:01:21 +08:00
|
|
|
|
headerOut.emplace("Date", dateStr());
|
2018-02-02 18:06:08 +08:00
|
|
|
|
headerOut.emplace("Server", SERVER_NAME);
|
2017-12-09 16:24:07 +08:00
|
|
|
|
headerOut.emplace("Connection", bClose ? "close" : "keep-alive");
|
2017-12-15 16:01:21 +08:00
|
|
|
|
if(!bClose){
|
|
|
|
|
headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl);
|
|
|
|
|
}
|
|
|
|
|
if(pcContentType){
|
2017-04-01 16:35:56 +08:00
|
|
|
|
auto strContentType = StrPrinter << pcContentType << "; charset=" << charSet << endl;
|
2017-12-15 16:01:21 +08:00
|
|
|
|
headerOut.emplace("Content-Type",strContentType);
|
|
|
|
|
}
|
|
|
|
|
if(iContentSize > 0){
|
2017-04-01 16:35:56 +08:00
|
|
|
|
headerOut.emplace("Content-Length", StrPrinter<<iContentSize<<endl);
|
|
|
|
|
}
|
2019-07-30 16:07:00 +08:00
|
|
|
|
|
|
|
|
|
if(!_origin.empty()){
|
|
|
|
|
headerOut.emplace("Access-Control-Allow-Origin",_origin);
|
|
|
|
|
headerOut.emplace("Access-Control-Allow-Credentials", "true");
|
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
return headerOut;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-08 22:37:17 +08:00
|
|
|
|
string HttpSession::urlDecode(const string &str){
|
2019-05-27 12:13:27 +08:00
|
|
|
|
auto ret = strCoding::UrlDecode(str);
|
2017-10-10 00:04:07 +08:00
|
|
|
|
#ifdef _WIN32
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(string,charSet,Http::kCharSet);
|
2018-11-27 11:05:44 +08:00
|
|
|
|
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
|
2017-10-10 00:04:07 +08:00
|
|
|
|
if (isGb2312) {
|
2017-12-08 22:37:17 +08:00
|
|
|
|
ret = strCoding::UTF8ToGB2312(ret);
|
2017-10-10 00:04:07 +08:00
|
|
|
|
}
|
|
|
|
|
#endif // _WIN32
|
2017-12-08 22:37:17 +08:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
2017-10-10 00:04:07 +08:00
|
|
|
|
|
2017-12-08 22:37:17 +08:00
|
|
|
|
inline void HttpSession::urlDecode(Parser &parser){
|
|
|
|
|
parser.setUrl(urlDecode(parser.Url()));
|
2018-10-24 15:43:52 +08:00
|
|
|
|
for(auto &pr : _parser.getUrlArgs()){
|
2017-12-08 22:37:17 +08:00
|
|
|
|
const_cast<string &>(pr.second) = urlDecode(pr.second);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2017-12-08 22:37:17 +08:00
|
|
|
|
inline bool HttpSession::emitHttpEvent(bool doInvoke){
|
|
|
|
|
///////////////////是否断开本链接///////////////////////
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
|
2018-02-09 11:42:55 +08:00
|
|
|
|
|
2018-11-27 11:05:44 +08:00
|
|
|
|
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
2017-12-08 22:37:17 +08:00
|
|
|
|
/////////////////////异步回复Invoker///////////////////////////////
|
2017-04-10 17:24:06 +08:00
|
|
|
|
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
2019-07-30 16:07:00 +08:00
|
|
|
|
HttpResponseInvoker invoker = [weakSelf,bClose](const string &codeOut, const KeyValue &headerOut, const string &contentOut){
|
2017-04-10 17:24:06 +08:00
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if(!strongSelf) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-30 16:07:00 +08:00
|
|
|
|
strongSelf->async([weakSelf,bClose,codeOut,headerOut,contentOut]() {
|
2017-04-10 17:24:06 +08:00
|
|
|
|
auto strongSelf = weakSelf.lock();
|
|
|
|
|
if(!strongSelf) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-30 16:07:00 +08:00
|
|
|
|
strongSelf->responseDelay(bClose,codeOut,headerOut,contentOut);
|
2017-04-10 17:24:06 +08:00
|
|
|
|
if(bClose){
|
2019-05-29 18:08:50 +08:00
|
|
|
|
strongSelf->shutdown(SockException(Err_shutdown,"Connection: close"));
|
2017-04-10 17:24:06 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
2017-12-08 22:37:17 +08:00
|
|
|
|
///////////////////广播HTTP事件///////////////////////////
|
|
|
|
|
bool consumed = false;//该事件是否被消费
|
2018-10-24 17:17:55 +08:00
|
|
|
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,*this);
|
2017-12-08 22:37:17 +08:00
|
|
|
|
if(!consumed && doInvoke){
|
|
|
|
|
//该事件无人消费,所以返回404
|
2017-04-10 17:24:06 +08:00
|
|
|
|
invoker("404 Not Found",KeyValue(),"");
|
2018-09-21 09:59:04 +08:00
|
|
|
|
if(bClose){
|
|
|
|
|
//close类型,回复完毕,关闭连接
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,"404 Not Found"));
|
2018-09-21 09:59:04 +08:00
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2017-12-08 22:37:17 +08:00
|
|
|
|
return consumed;
|
|
|
|
|
}
|
2019-05-30 10:59:14 +08:00
|
|
|
|
inline void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize);
|
|
|
|
|
GET_CONFIG(int,maxReqCnt,Http::kMaxReqCount);
|
2018-09-20 23:05:32 +08:00
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
int64_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
|
2018-09-20 23:05:32 +08:00
|
|
|
|
|
2018-09-21 09:41:40 +08:00
|
|
|
|
if(totalContentLen == 0){
|
2018-09-21 09:59:04 +08:00
|
|
|
|
//content为空
|
|
|
|
|
//emitHttpEvent内部会选择是否关闭连接
|
2018-09-20 23:05:32 +08:00
|
|
|
|
emitHttpEvent(true);
|
2019-05-30 10:59:14 +08:00
|
|
|
|
return;
|
2018-09-20 17:50:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-23 12:22:59 +08:00
|
|
|
|
//根据Content-Length设置接收缓存大小
|
|
|
|
|
if(totalContentLen > 0){
|
|
|
|
|
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
|
|
|
|
}else{
|
|
|
|
|
//不定长度的Content-Length
|
|
|
|
|
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
2018-09-20 17:50:58 +08:00
|
|
|
|
//返回固定长度的content
|
2018-09-21 09:41:40 +08:00
|
|
|
|
content_len = totalContentLen;
|
2018-10-24 15:43:52 +08:00
|
|
|
|
auto parserCopy = _parser;
|
|
|
|
|
_contentCallBack = [this,parserCopy](const char *data,uint64_t len){
|
2018-09-20 17:50:58 +08:00
|
|
|
|
//恢复http头
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_parser = parserCopy;
|
2018-09-20 17:50:58 +08:00
|
|
|
|
//设置content
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_parser.setContent(string(data,len));
|
2018-09-21 09:59:04 +08:00
|
|
|
|
//触发http事件,emitHttpEvent内部会选择是否关闭连接
|
2018-09-20 17:50:58 +08:00
|
|
|
|
emitHttpEvent(true);
|
|
|
|
|
//清空数据,节省内存
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_parser.Clear();
|
2018-09-21 09:59:04 +08:00
|
|
|
|
//content已经接收完毕
|
2018-09-20 17:50:58 +08:00
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
}else{
|
|
|
|
|
//返回不固定长度的content
|
|
|
|
|
content_len = -1;
|
2018-10-24 15:43:52 +08:00
|
|
|
|
auto parserCopy = _parser;
|
2018-09-23 00:55:00 +08:00
|
|
|
|
std::shared_ptr<uint64_t> recvedContentLen = std::make_shared<uint64_t>(0);
|
2018-11-27 11:05:44 +08:00
|
|
|
|
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > maxReqCnt);
|
2018-09-21 09:59:04 +08:00
|
|
|
|
|
2018-10-24 15:43:52 +08:00
|
|
|
|
_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,uint64_t len){
|
2018-09-23 00:55:00 +08:00
|
|
|
|
*(recvedContentLen) += len;
|
2018-09-21 09:41:40 +08:00
|
|
|
|
|
2018-09-23 00:55:00 +08:00
|
|
|
|
onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen));
|
2018-09-21 09:41:40 +08:00
|
|
|
|
|
|
|
|
|
if(*(recvedContentLen) < totalContentLen){
|
|
|
|
|
//数据还没接收完毕
|
2018-10-24 15:43:52 +08:00
|
|
|
|
//_contentCallBack是可持续的,后面还要处理后续content数据
|
2018-09-21 09:41:40 +08:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//数据接收完毕
|
|
|
|
|
if(!bClose){
|
|
|
|
|
//keep-alive类型连接
|
|
|
|
|
//content接收完毕,后续都是http header
|
|
|
|
|
setContentLen(0);
|
2018-09-21 09:59:04 +08:00
|
|
|
|
//content已经接收完毕
|
2018-09-21 09:41:40 +08:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//连接类型是close类型,收完content就关闭连接
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,"recv http content completed"));
|
2018-09-21 09:59:04 +08:00
|
|
|
|
//content已经接收完毕
|
2018-09-21 09:41:40 +08:00
|
|
|
|
return false ;
|
2018-09-20 17:50:58 +08:00
|
|
|
|
};
|
|
|
|
|
}
|
2018-09-21 09:59:04 +08:00
|
|
|
|
//有后续content数据要处理,暂时不关闭连接
|
2017-04-10 17:24:06 +08:00
|
|
|
|
}
|
2019-07-30 16:07:00 +08:00
|
|
|
|
void HttpSession::responseDelay(bool bClose,
|
|
|
|
|
const string &codeOut,
|
|
|
|
|
const KeyValue &headerOut,
|
2017-12-09 16:43:47 +08:00
|
|
|
|
const string &contentOut){
|
2017-04-10 17:24:06 +08:00
|
|
|
|
if(codeOut.empty()){
|
|
|
|
|
sendNotFound(bClose);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-30 16:07:00 +08:00
|
|
|
|
auto headerOther = makeHttpHeader(bClose,contentOut.size(),"text/plain");
|
2019-07-17 14:54:14 +08:00
|
|
|
|
for (auto &pr : headerOther){
|
|
|
|
|
//添加默认http头,默认http头不能覆盖用户自定义的头
|
|
|
|
|
const_cast<KeyValue &>(headerOut).emplace(pr.first,pr.second);
|
2019-07-17 14:50:24 +08:00
|
|
|
|
}
|
2019-07-17 14:54:14 +08:00
|
|
|
|
sendResponse(codeOut.data(), headerOut, contentOut);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
inline void HttpSession::sendNotFound(bool bClose) {
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(string,notFound,Http::kNotFound);
|
2018-02-09 11:42:55 +08:00
|
|
|
|
sendResponse("404 Not Found", makeHttpHeader(bClose, notFound.size()), notFound);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-30 19:29:54 +08:00
|
|
|
|
|
|
|
|
|
void HttpSession::onWrite(const Buffer::Ptr &buffer) {
|
2019-03-21 22:28:12 +08:00
|
|
|
|
_ticker.resetTime();
|
2019-08-12 18:14:51 +08:00
|
|
|
|
if(!_flv_over_websocket){
|
|
|
|
|
_ui64TotalBytes += buffer->size();
|
|
|
|
|
send(buffer);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WebSocketHeader header;
|
|
|
|
|
header._fin = true;
|
|
|
|
|
header._reserved = 0;
|
|
|
|
|
header._opcode = WebSocketHeader::BINARY;
|
|
|
|
|
header._mask_flag = false;
|
2019-08-13 12:00:21 +08:00
|
|
|
|
WebSocketSplitter::encode(header,buffer);
|
2019-08-12 18:14:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 12:00:21 +08:00
|
|
|
|
void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
|
|
|
|
_ui64TotalBytes += buffer->size();
|
|
|
|
|
send(buffer);
|
2017-12-15 16:01:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-30 19:29:54 +08:00
|
|
|
|
void HttpSession::onDetach() {
|
2019-05-29 18:08:50 +08:00
|
|
|
|
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
2018-08-30 19:29:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr(){
|
|
|
|
|
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
|
2018-01-30 11:47:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-24 17:17:55 +08:00
|
|
|
|
} /* namespace mediakit */
|