HttpSession.cpp 32 KB
Newer Older
xiongziliang committed
1
/*
xiongziliang committed
2
 * MIT License
xzl committed
3
 *
xiongziliang committed
4
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
xiongziliang committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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.
xzl committed
25
 */
26 27 28 29
#if !defined(_WIN32)
#include <dirent.h>
#endif //!defined(_WIN32)

xzl committed
30 31 32
#include <stdio.h>
#include <sys/stat.h>
#include <algorithm>
33
#include <iomanip>
xzl committed
34

xiongziliang committed
35
#include "Common/config.h"
xzl committed
36 37 38 39 40 41
#include "strCoding.h"
#include "HttpSession.h"
#include "Util/File.h"
#include "Util/util.h"
#include "Util/TimeTicker.h"
#include "Util/onceToken.h"
xiongzilaing committed
42
#include "Util/mini.h"
xzl committed
43
#include "Util/NoticeCenter.h"
xiongziliang committed
44 45
#include "Util/base64.h"
#include "Util/SHA1.h"
xiongziliang committed
46
#include "Rtmp/utils.h"
xiongziliang committed
47
using namespace toolkit;
48

xiongziliang committed
49
namespace mediakit {
xzl committed
50

51
static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE;
52
static int kHlsCookieSecond = 10 * 60;
53
static const string kCookieName = "ZL_COOKIE";
54 55
static const string kCookiePathKey = "kCookiePathKey";
static const string kAccessErrKey = "kAccessErrKey";
56

xzl committed
57 58 59 60 61 62 63 64 65 66
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, '.');
xiongziliang committed
67
	static HttpSession::KeyValue mapType;
xzl committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
	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");
xiongziliang committed
94
        mapType.emplace(".flv","video/x-flv");
xzl committed
95 96 97 98
	}, nullptr);
	if(!dot){
		return "text/plain";
	}
xiongziliang committed
99
	auto it = mapType.find(dot);
xzl committed
100 101 102 103 104 105 106
	if (it == mapType.end()) {
		return "text/plain";
	}
	return it->second.data();
}


xiongziliang committed
107
HttpSession::HttpSession(const Socket::Ptr &pSock) : TcpSession(pSock) {
xiongziliang committed
108
    TraceP(this);
109 110
    GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
    pSock->setSendTimeOutSecond(keep_alive_sec);
111 112
	//起始接收buffer缓存设置为4K,节省内存
	pSock->setReadBuffer(std::make_shared<BufferRaw>(4 * 1024));
xzl committed
113 114 115
}

HttpSession::~HttpSession() {
xiongziliang committed
116
    TraceP(this);
xzl committed
117 118
}

119
int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) {
xiongziliang committed
120
	typedef void (HttpSession::*HttpCMDHandle)(int64_t &);
xiongziliang committed
121 122 123 124 125 126
	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);

127 128 129
	_parser.Parse(header);
	urlDecode(_parser);
	string cmd = _parser.Method();
xzl committed
130 131 132
	auto it = g_mapCmdIndex.find(cmd);
	if (it == g_mapCmdIndex.end()) {
		sendResponse("403 Forbidden", makeHttpHeader(true), "");
xiongziliang committed
133 134
        shutdown(SockException(Err_shutdown,StrPrinter << "403 Forbidden:" << cmd));
        return 0;
135 136 137 138 139
	}

	//默认后面数据不是content而是header
	int64_t content_len = 0;
	auto &fun = it->second;
xiongziliang committed
140 141 142 143 144 145 146 147 148 149
    try {
        (this->*fun)(content_len);
    }catch (SockException &ex){
        if(ex){
            shutdown(ex);
        }
    }catch (exception &ex){
        shutdown(SockException(Err_shutdown,ex.what()));
    }

150
	//清空解析器节省内存
151
	_parser.Clear();
152 153
	//返回content长度
	return content_len;
xzl committed
154
}
155

156
void HttpSession::onRecvContent(const char *data,uint64_t len) {
157 158 159
	if(_contentCallBack){
		if(!_contentCallBack(data,len)){
			_contentCallBack = nullptr;
160 161 162 163 164
		}
	}
}

void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
165
    _ticker.resetTime();
166
    input(pBuf->data(),pBuf->size());
167 168
}

xzl committed
169
void HttpSession::onError(const SockException& err) {
xiongziliang committed
170 171 172 173 174
    if(_ticker.createdTime() < 10 * 1000){
        TraceP(this) << err.what();
    }else{
        WarnP(this) << err.what();
    }
175

xiongziliang committed
176
    GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
177
    if(_ui64TotalBytes > iFlowThreshold * 1024){
178
        NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport,
179 180 181
										   _mediaInfo,
										   _ui64TotalBytes,
										   _ticker.createdTime()/1000,
182
										   true,
183
										   *this);
184
    }
xzl committed
185 186 187
}

void HttpSession::onManager() {
188
    GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
189

190
    if(_ticker.elapsedTime() > keepAliveSec * 1000){
xzl committed
191
		//1分钟超时
xiongziliang committed
192
		shutdown(SockException(Err_timeout,"session timeouted"));
xzl committed
193 194
	}
}
xiongziliang committed
195 196 197


inline bool HttpSession::checkWebSocket(){
198
	auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
199
	if(Sec_WebSocket_Key.empty()){
xiongziliang committed
200 201 202 203 204 205 206 207
		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;
xiongziliang committed
208 209 210
	if(!_parser["Sec-WebSocket-Protocol"].empty()){
		headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
	}
xiongziliang committed
211 212 213
	sendResponse("101 Switching Protocols",headerOut,"");
	return true;
}
214 215
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
xiongziliang committed
216
inline bool HttpSession::checkLiveFlvStream(){
217
	auto pos = strrchr(_parser.Url().data(),'.');
xiongziliang committed
218 219 220 221
	if(!pos){
		//未找到".flv"后缀
		return false;
	}
xiongziliang committed
222
	if(strcasecmp(pos,".flv") != 0){
xiongziliang committed
223 224 225
		//未找到".flv"后缀
		return false;
	}
226

227 228 229 230 231
	//这是个.flv的流
    _mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
	if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
	    //url不合法
        return false;
xiongziliang committed
232
	}
233
    _mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
xiongziliang committed
234

235
    GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
236
    bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
237 238

    weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
239
    MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this](const MediaSource::Ptr &src){
240 241
        auto strongSelf = weakSelf.lock();
        if(!strongSelf){
242 243 244 245 246 247 248 249
            //本对象已经销毁
            return;
        }
        auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
        if(!rtmp_src){
            //未找到该流
            sendNotFound(bClose);
            if(bClose){
xiongziliang committed
250
                shutdown(SockException(Err_shutdown,"flv stream not found"));
251
            }
252 253
            return;
        }
254 255 256 257 258
        //找到流了
        auto onRes = [this,rtmp_src](const string &err){
            bool authSuccess = err.empty();
            if(!authSuccess){
                sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err);
xiongziliang committed
259
                shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err));
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
                return ;
            }

            //找到rtmp源,发送http头,负载后续发送
            sendResponse("200 OK", makeHttpHeader(false,0,get_mime_type(".flv")), "");

            //开始发送rtmp负载
            //关闭tcp_nodelay ,优化性能
            SockUtil::setNoDelay(_sock->rawFD(),false);
            (*this) << SocketFlags(kSockFlags);

            try{
                start(getPoller(),rtmp_src);
            }catch (std::exception &ex){
                //该rtmp源不存在
xiongziliang committed
275
                shutdown(SockException(Err_shutdown,"rtmp mediasource released"));
276 277 278 279 280
            }
        };

        weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
        Broadcast::AuthInvoker invoker = [weakSelf,onRes](const string &err){
281 282 283 284
            auto strongSelf = weakSelf.lock();
            if(!strongSelf){
                return;
            }
285 286 287 288 289 290 291 292 293 294 295 296 297 298
            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("");
        }
    });
299
    return true;
xiongziliang committed
300
}
301

302
inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) ;
303

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
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 "";
}

322
inline string HttpSession::getClientUid(){
323 324 325 326 327 328 329
    //如果http客户端不支持cookie,那么我们可以通过url参数来追踪用户
    //如果url参数也没有,那么只能通过ip+端口号来追踪用户
    //追踪用户的目的是为了减少http短链接情况的重复鉴权验证,通过缓存记录鉴权结果,提高性能
    string uid = _parser.Params();
    if(uid.empty()){
        uid = StrPrinter << get_peer_ip() << ":" << get_peer_port();
    }
330
    return uid;
331
}
332 333 334 335 336 337 338 339 340 341 342 343 344


//字符串是否以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;
345
    }
346 347
    //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
    Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
348 349
        //cookie有效期为kHlsCookieSecond
        invoker(err,"",kHlsCookieSecond);
350 351 352 353 354 355 356
    };

    auto args_copy = args;
    replace(args_copy._streamid,"/hls.m3u8","");
    return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
}

357
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback_in){
358 359 360
    auto path = path_in;
    replace(const_cast<string &>(path),"//","/");

361
    auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){
362
        try {
363
            callback_in(errMsg,cookie);
364 365 366 367 368 369 370 371 372
        }catch (SockException &ex){
            if(ex){
                shutdown(ex);
            }
        }catch (exception &ex){
            shutdown(SockException(Err_shutdown,ex.what()));
        }
    };

373 374 375 376 377 378 379
    //获取用户唯一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);
380 381
    }

382
    if(cookie){
383 384 385
        //找到了cookie,对cookie上锁先
        auto lck = cookie->getLock();
        auto accessErr = (*cookie)[kAccessErrKey];
386 387 388 389 390 391 392 393 394 395 396 397 398
        if(path.find((*cookie)[kCookiePathKey]) == 0){
            //上次cookie是限定本目录
            if(accessErr.empty()){
                //上次鉴权成功
                callback("", nullptr);
                return;
            }
            //上次鉴权失败,如果url发生变更,那么也重新鉴权
            if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
                //url参数未变,那么判断无权限访问
                callback(accessErr.empty() ? "无权限访问该目录" : accessErr, nullptr);
                return;
            }
399
        }
400
        //如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权
401
        HttpCookieManager::Instance().delCookie(cookie);
402 403 404 405
    }

    //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
    weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
406 407 408 409
    HttpAccessPathInvoker accessPathInvoker = [weakSelf,callback,uid,path,is_dir] (const string &errMsg,const string &cookie_path_in, int cookieLifeSecond) {
        HttpServerCookie::Ptr cookie ;
        if(cookieLifeSecond) {
            //本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
xiongziliang committed
410 411 412 413 414 415
            string cookie_path = cookie_path_in;
            if(cookie_path.empty()){
                //如果未设置鉴权目录,那么我们采用当前目录
                cookie_path = is_dir ? path : path.substr(0,path.rfind("/") + 1);
            }

xiongziliang committed
416 417 418
            cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
            //对cookie上锁
            auto lck = cookie->getLock();
419 420 421 422 423 424
            //记录用户能访问的路径
            (*cookie)[kCookiePathKey] = cookie_path;
            //记录能否访问
            (*cookie)[kAccessErrKey] = errMsg;
        }

425 426 427 428 429
        auto strongSelf = weakSelf.lock();
        if (!strongSelf) {
            //自己已经销毁
            return;
        }
430
        strongSelf->async([weakSelf,callback,cookie,errMsg]() {
431 432 433 434 435 436
            //切换到自己线程
            auto strongSelf = weakSelf.lock();
            if (!strongSelf) {
                //自己已经销毁
                return;
            }
437
            callback(errMsg, cookie);
438 439 440
        });
    };

441 442 443 444 445
    if(checkHls(_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this)){
        //是hls的播放鉴权,拦截之
        return;
    }

446 447 448
    bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this);
    if(!flag){
        //此事件无人监听,我们默认都有权限访问
449
        callback("", nullptr);
450 451 452
    }

}
453

xiongziliang committed
454
inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
xiongziliang committed
455 456 457
	//先看看是否为WebSocket请求
	if(checkWebSocket()){
		content_len = -1;
458 459
		auto parserCopy = _parser;
		_contentCallBack = [this,parserCopy](const char *data,uint64_t len){
460
			onRecvWebSocketData(parserCopy,data,len);
461
			//_contentCallBack是可持续的,后面还要处理后续数据
xiongziliang committed
462 463
			return true;
		};
xiongziliang committed
464
		return;
xiongziliang committed
465 466
	}

xiongziliang committed
467 468
	//先看看该http事件是否被拦截
	if(emitHttpEvent(false)){
xiongziliang committed
469
		return;
470
	}
xiongziliang committed
471

472
    //再看看是否为http-flv直播请求
xiongziliang committed
473
	if(checkLiveFlvStream()){
xiongziliang committed
474
		return;
xiongziliang committed
475
	}
476

xiongziliang committed
477
	//事件未被拦截,则认为是http下载请求
478 479
	auto fullUrl = string(HTTP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl();
    _mediaInfo.parse(fullUrl);
480

xiongziliang committed
481
	/////////////HTTP连接是否需要被关闭////////////////
482 483 484
    GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
    GET_CONFIG(bool,enableVhost,General::kEnableVhost);
    GET_CONFIG(string,rootPath,Http::kRootPath);
485
    string strFile = enableVhost ?  rootPath + "/" + _mediaInfo._vhost + _parser.Url() :rootPath + _parser.Url();
xiongziliang committed
486
    bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
487 488 489 490 491 492 493 494

    do{
        //访问的是文件夹
        if (strFile.back() == '/' || File::is_dir(strFile.data())) {
            auto indexFile = findIndexFile(strFile);
            if(!indexFile.empty()){
                //发现该文件夹下有index文件
                strFile = strFile + "/" + indexFile;
495
                _parser.setUrl(_parser.Url() + "/" + indexFile);
496 497 498
                break;
            }
            string strMeun;
499
            //生成文件夹菜单索引
500
            if (!makeMeun(_parser.Url(),strFile,strMeun)) {
501 502
                //文件夹不存在
                sendNotFound(bClose);
xiongziliang committed
503
                throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder");
504
            }
505 506

            //判断是否有权限访问该目录
xiongziliang committed
507
            canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](const string &errMsg,const HttpServerCookie::Ptr &cookie){
508 509
                if(!errMsg.empty()){
                    const_cast<string &>(strMeun) = errMsg;
510 511 512
                }
                auto headerOut = makeHttpHeader(bClose,strMeun.size());
                if(cookie){
513
                    headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
514
                }
515
                sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
516 517 518
                throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
            });
            return;
519 520 521
        }
    }while(0);

xiongziliang committed
522
	//访问的是文件
xzl committed
523 524
	struct stat tFileStat;
	if (0 != stat(strFile.data(), &tFileStat)) {
xiongziliang committed
525
		//文件不存在
xzl committed
526
		sendNotFound(bClose);
xiongziliang committed
527
        throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on file");
xzl committed
528
	}
529 530 531 532 533 534 535 536
    //文件智能指针,防止退出时未关闭
    std::shared_ptr<FILE> pFilePtr(fopen(strFile.data(), "rb"), [](FILE *pFile) {
        if(pFile){
            fclose(pFile);
        }
    });

	if (!pFilePtr) {
xiongziliang committed
537
		//打开文件失败
xzl committed
538
		sendNotFound(bClose);
xiongziliang committed
539
        throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on open file failed");
xzl committed
540
	}
541

542 543
	auto parser = _parser;
    //判断是否有权限访问该文件
544 545 546 547 548 549 550 551 552 553
    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){
                headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
            }
            sendResponse("401 Unauthorized" , headerOut, errMsg);
            throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed");
        }

554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
        //判断是不是分节下载
        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);
        }
571
        auto httpHeader =  makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()));
572 573 574 575 576 577 578 579 580 581 582
        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头部分
583 584 585
        sendResponse(pcHttpResult,httpHeader,"");
        
        if (iRangeEnd - iRangeStart < 0) {
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
            //文件是空的!
            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;
                }
                //继续写套接字
xiongziliang committed
637
            }
638 639 640 641 642 643 644 645 646 647
            return false;
        };
        //关闭tcp_nodelay ,优化性能
        SockUtil::setNoDelay(_sock->rawFD(),false);
        //设置MSG_MORE,优化性能
        (*this) << SocketFlags(kSockFlags);

        onFlush();
        _sock->setOnFlush(onFlush);
    });
xzl committed
648 649
}

650
inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) {
xzl committed
651
	string strPathPrefix(strFullPath);
652 653 654 655 656 657 658
	string last_dir_name;
	if(strPathPrefix.back() == '/'){
		strPathPrefix.pop_back();
	}else{
		last_dir_name = split(strPathPrefix,"/").back();
	}

xzl committed
659 660 661
	if (!File::is_dir(strPathPrefix.data())) {
		return false;
	}
662 663
	stringstream ss;
	ss <<   "<html>\r\n"
xzl committed
664 665 666 667 668 669
			"<head>\r\n"
			"<title>文件索引</title>\r\n"
			"</head>\r\n"
			"<body>\r\n"
			"<h1>文件索引:";

670
	ss << httpPath;
671
	ss << "</h1>\r\n";
672
	if (httpPath != "/") {
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
		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";
xzl committed
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
	}

	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);
	}
705
	int i = 0;
xzl committed
706 707
	for(auto &strFile :setFile ){
		string strAbsolutePath = strPathPrefix + "/" + strFile;
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
		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)";
xzl committed
738 739
			}
		}
740
		ss << "</a></li>\r\n";
xzl committed
741 742
	}
	closedir(pDir);
743 744 745
	ss << "<ul>\r\n";
	ss << "</ul>\r\n</body></html>";
	ss.str().swap(strRet);
xzl committed
746 747 748 749
	return true;
}
inline void HttpSession::sendResponse(const char* pcStatus, const KeyValue& header, const string& strContent) {
	_StrPrinter printer;
xiongziliang committed
750
	printer << "HTTP/1.1 " << pcStatus << "\r\n";
xzl committed
751 752 753 754 755 756
	for (auto &pr : header) {
		printer << pr.first << ": " << pr.second << "\r\n";
	}
	printer << "\r\n" << strContent;
	auto strSend = printer << endl;
	send(strSend);
757
	_ticker.resetTime();
xzl committed
758 759 760
}
inline HttpSession::KeyValue HttpSession::makeHttpHeader(bool bClose, int64_t iContentSize,const char* pcContentType) {
	KeyValue headerOut;
761 762 763
    GET_CONFIG(string,charSet,Http::kCharSet);
    GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
    GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
xzl committed
764

xiongziliang committed
765
	headerOut.emplace("Date", dateStr());
766
	headerOut.emplace("Server", SERVER_NAME);
xiongziliang committed
767
	headerOut.emplace("Connection", bClose ? "close" : "keep-alive");
xiongziliang committed
768 769 770 771
	if(!bClose){
		headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl);
	}
	if(pcContentType){
xzl committed
772
		auto strContentType = StrPrinter << pcContentType << "; charset=" << charSet << endl;
xiongziliang committed
773 774 775
		headerOut.emplace("Content-Type",strContentType);
	}
	if(iContentSize > 0){
xzl committed
776 777 778 779 780
		headerOut.emplace("Content-Length", StrPrinter<<iContentSize<<endl);
	}
	return headerOut;
}

xiongziliang committed
781
string HttpSession::urlDecode(const string &str){
xiongziliang committed
782
	auto ret = strCoding::UrlDecode(str);
783
#ifdef _WIN32
784
    GET_CONFIG(string,charSet,Http::kCharSet);
xiongziliang committed
785
	bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
786
	if (isGb2312) {
xiongziliang committed
787
		ret = strCoding::UTF8ToGB2312(ret);
788 789
	}
#endif // _WIN32
xiongziliang committed
790 791
    return ret;
}
792

xiongziliang committed
793 794
inline void HttpSession::urlDecode(Parser &parser){
	parser.setUrl(urlDecode(parser.Url()));
795
	for(auto &pr : _parser.getUrlArgs()){
xiongziliang committed
796 797 798
		const_cast<string &>(pr.second) = urlDecode(pr.second);
	}
}
xzl committed
799

xiongziliang committed
800 801
inline bool HttpSession::emitHttpEvent(bool doInvoke){
	///////////////////是否断开本链接///////////////////////
802
    GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount);
803

xiongziliang committed
804
    bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
805
	auto Origin = _parser["Origin"];
xiongziliang committed
806
	/////////////////////异步回复Invoker///////////////////////////////
807
	weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
xiongziliang committed
808
	HttpResponseInvoker invoker = [weakSelf,bClose,Origin](const string &codeOut, const KeyValue &headerOut, const string &contentOut){
809 810 811 812
		auto strongSelf = weakSelf.lock();
		if(!strongSelf) {
			return;
		}
xiongziliang committed
813
		strongSelf->async([weakSelf,bClose,codeOut,headerOut,contentOut,Origin]() {
814 815 816 817
			auto strongSelf = weakSelf.lock();
			if(!strongSelf) {
				return;
			}
xiongziliang committed
818
			strongSelf->responseDelay(Origin,bClose,codeOut,headerOut,contentOut);
819
			if(bClose){
xiongziliang committed
820
				strongSelf->shutdown(SockException(Err_shutdown,"Connection: close"));
821 822 823
			}
		});
	};
xiongziliang committed
824 825
	///////////////////广播HTTP事件///////////////////////////
	bool consumed = false;//该事件是否被消费
xiongziliang committed
826
	NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,*this);
xiongziliang committed
827 828
	if(!consumed && doInvoke){
		//该事件无人消费,所以返回404
829
		invoker("404 Not Found",KeyValue(),"");
830 831
		if(bClose){
			//close类型,回复完毕,关闭连接
xiongziliang committed
832
			shutdown(SockException(Err_shutdown,"404 Not Found"));
833
		}
xzl committed
834
	}
xiongziliang committed
835 836
	return consumed;
}
xiongziliang committed
837
inline void HttpSession::Handle_Req_POST(int64_t &content_len) {
838 839
	GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize);
    GET_CONFIG(int,maxReqCnt,Http::kMaxReqCount);
840

841
    int64_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
842

843
	if(totalContentLen == 0){
844 845
		//content为空
		//emitHttpEvent内部会选择是否关闭连接
846
		emitHttpEvent(true);
xiongziliang committed
847
		return;
848 849
	}

850 851 852 853 854 855 856 857 858
    //根据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 ){
859
		//返回固定长度的content
860
		content_len = totalContentLen;
861 862
		auto parserCopy = _parser;
		_contentCallBack = [this,parserCopy](const char *data,uint64_t len){
863
			//恢复http头
864
			_parser = parserCopy;
865
			//设置content
866
			_parser.setContent(string(data,len));
867
			//触发http事件,emitHttpEvent内部会选择是否关闭连接
868 869
			emitHttpEvent(true);
			//清空数据,节省内存
870
			_parser.Clear();
871
			//content已经接收完毕
872 873 874 875 876
			return false;
		};
	}else{
		//返回不固定长度的content
		content_len = -1;
877
		auto parserCopy = _parser;
878
		std::shared_ptr<uint64_t> recvedContentLen = std::make_shared<uint64_t>(0);
xiongziliang committed
879
		bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > maxReqCnt);
880

881
		_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,uint64_t len){
882
		    *(recvedContentLen) += len;
883

884
		    onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen));
885 886 887

			if(*(recvedContentLen) < totalContentLen){
			    //数据还没接收完毕
888
                //_contentCallBack是可持续的,后面还要处理后续content数据
889 890 891 892 893 894 895 896
                return true;
			}

			//数据接收完毕
            if(!bClose){
			    //keep-alive类型连接
				//content接收完毕,后续都是http header
				setContentLen(0);
897
                //content已经接收完毕
898 899 900 901
                return false;
            }

            //连接类型是close类型,收完content就关闭连接
xiongziliang committed
902
            shutdown(SockException(Err_shutdown,"recv http content completed"));
903
            //content已经接收完毕
904
            return false ;
905 906
		};
	}
907
	//有后续content数据要处理,暂时不关闭连接
908
}
xiongziliang committed
909 910 911
void HttpSession::responseDelay(const string &Origin,bool bClose,
								const string &codeOut,const KeyValue &headerOut,
								const string &contentOut){
912 913 914 915
	if(codeOut.empty()){
		sendNotFound(bClose);
		return;
	}
916
	auto headerOther = makeHttpHeader(bClose,contentOut.size(),"text/plain");
xiongziliang committed
917 918 919 920
	if(!Origin.empty()){
		headerOther["Access-Control-Allow-Origin"] = Origin;
		headerOther["Access-Control-Allow-Credentials"] = "true";
	}
921 922 923 924 925
    for (auto &pr : headerOut){
        //替换掉默认的http头
        headerOther[pr.first] = pr.second;
    }
	sendResponse(codeOut.data(), headerOther, contentOut);
xzl committed
926 927
}
inline void HttpSession::sendNotFound(bool bClose) {
928
    GET_CONFIG(string,notFound,Http::kNotFound);
929
    sendResponse("404 Not Found", makeHttpHeader(bClose, notFound.size()), notFound);
xzl committed
930 931
}

xiongziliang committed
932 933

void HttpSession::onWrite(const Buffer::Ptr &buffer) {
934 935 936
	_ticker.resetTime();
	_ui64TotalBytes += buffer->size();
	send(buffer);
xiongziliang committed
937 938
}

xiongziliang committed
939
void HttpSession::onDetach() {
xiongziliang committed
940
	shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
xiongziliang committed
941 942 943 944
}

std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr(){
	return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
945 946
}

xiongziliang committed
947
} /* namespace mediakit */