ZLMediaKit/src/Http/HttpSession.cpp

411 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* HttpSession.cpp
*
* Created on: 2016年9月22日
* Author: xzl
*/
#include <stdio.h>
#include <sys/stat.h>
#include <algorithm>
#include <dirent.h>
#include "config.h"
#include "strCoding.h"
#include "HttpSession.h"
#include "Util/File.h"
#include "Util/util.h"
#include "Util/TimeTicker.h"
#include "Util/onceToken.h"
#include "Util/mini.hpp"
#include "Util/NoticeCenter.h"
using namespace ZL::Util;
namespace ZL {
namespace Http {
unordered_map<string, HttpSession::HttpCMDHandle> HttpSession::g_mapCmdIndex;
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, '.');
static unordered_map<string, string> mapType;
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");
}, nullptr);
if(!dot){
return "text/plain";
}
string strDot(dot);
transform(strDot.begin(), strDot.end(), strDot.begin(), (int (*)(int))tolower);
auto it = mapType.find(strDot);
if (it == mapType.end()) {
return "text/plain";
}
return it->second.data();
}
HttpSession::HttpSession(const std::shared_ptr<ThreadPool> &pTh, const Socket::Ptr &pSock) :
TcpLimitedSession(pTh, pSock) {
static string rootPath = mINI::Instance()[Config::Http::kRootPath];
m_strPath = rootPath;
static onceToken token([]() {
g_mapCmdIndex.emplace("GET",&HttpSession::Handle_Req_GET);
g_mapCmdIndex.emplace("POST",&HttpSession::Handle_Req_POST);
}, nullptr);
}
HttpSession::~HttpSession() {
//DebugL;
}
void HttpSession::onRecv(const Socket::Buffer::Ptr&pBuf) {
static uint32_t reqSize = mINI::Instance()[Config::Http::kMaxReqSize].as<uint32_t>();
m_ticker.resetTime();
if (m_strRcvBuf.size() + pBuf->size() >= reqSize) {
WarnL << "接收缓冲区溢出!";
shutdown();
return;
}
m_strRcvBuf.append(pBuf->data(), pBuf->size());
size_t index;
string onePkt;
while ((index = m_strRcvBuf.find("\r\n\r\n")) != std::string::npos) {
onePkt = m_strRcvBuf.substr(0, index + 4);
m_strRcvBuf.erase(0, index + 4);
switch (parserHttpReq(onePkt)) {
case 0:
//失败
shutdown();
return;
case 1:
//成功
break;
case 2:
//需要更多数据,恢复数据并退出
m_strRcvBuf = onePkt + m_strRcvBuf;
m_parser.Clear();
return;
}
}
m_parser.Clear();
}
inline int HttpSession::parserHttpReq(const string &str) {
m_parser.Parse(str.data());
string cmd = m_parser.Method();
auto it = g_mapCmdIndex.find(cmd);
if (it == g_mapCmdIndex.end()) {
WarnL << cmd;
sendResponse("403 Forbidden", makeHttpHeader(true), "");
return false;
}
auto fun = it->second;
return (this->*fun)();
}
void HttpSession::onError(const SockException& err) {
//WarnL << err.what();
}
void HttpSession::onManager() {
static uint32_t keepAliveSec = mINI::Instance()[Config::Http::kKeepAliveSecond].as<uint32_t>();
if(m_ticker.elapsedTime() > keepAliveSec * 1000){
//1分钟超时
WarnL<<"HttpSession超时断开";
shutdown();
}
}
inline int HttpSession::Handle_Req_GET() {
string strUrl = strCoding::UrlUTF8Decode(m_parser.Url());
string strFile = m_strPath + strUrl;
string strConType = m_parser["Connection"];
static uint32_t reqCnt = mINI::Instance()[Config::Http::kMaxReqCount].as<uint32_t>();
bool bClose = (strcasecmp(strConType.data(),"close") == 0) && ( ++m_iReqCnt < reqCnt);
if (strFile.back() == '/') {
//index the folder
string strMeun;
if (!makeMeun(strFile, strMeun)) {
sendNotFound(bClose);
return !bClose;
}
sendResponse("200 OK", makeHttpHeader(bClose,strMeun.size() ), strMeun);
return !bClose;
}
//download the file
struct stat tFileStat;
if (0 != stat(strFile.data(), &tFileStat)) {
sendNotFound(bClose);
return !bClose;
}
TimeTicker();
FILE *pFile = fopen(strFile.data(), "rb");
if (pFile == NULL) {
sendNotFound(bClose);
return !bClose;
}
auto &strRange = m_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(pFile, iRangeStart, SEEK_SET);
}
auto httpHeader=makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strUrl.data()));
if (strRange.size() != 0) {
httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl);
}
sendResponse(pcHttpResult, httpHeader, "");
if (iRangeEnd - iRangeStart < 0) {
//file is empty!
return !bClose;
}
//send the file
std::shared_ptr<int64_t> piLeft(new int64_t(iRangeEnd - iRangeStart + 1));
std::shared_ptr<FILE> pFilePtr(pFile, [](FILE *pFp) {
fclose(pFp);
});
static uint32_t sendBufSize = mINI::Instance()[Config::Http::kSendBufSize].as<uint32_t>();
std::shared_ptr<char> pacSendBuf(new char[sendBufSize],[](char *ptr){
delete [] ptr;
});
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
auto onFlush = [pFilePtr,bClose,weakSelf,piLeft,pacSendBuf]() {
TimeTicker();
auto strongSelf = weakSelf.lock();
while(*piLeft && strongSelf){
strongSelf->m_ticker.resetTime();
int64_t iReq = MIN(sendBufSize,*piLeft);
int64_t iRead = fread(pacSendBuf.get(), 1, iReq, pFilePtr.get());
*piLeft -= iRead;
//InfoL << "Send file " << iReq << " " << *piLeft;
if (iRead < iReq || !*piLeft) {
//send completed!
//FatalL << "send completed!";
if(iRead>0) {
strongSelf->send(pacSendBuf.get(), iRead);
}
if(bClose) {
strongSelf->shutdown();
}
return false;
}
int iSent=strongSelf->send(pacSendBuf.get(), iRead);
if(iSent == -1) {
//send error
//FatalL << "send error";
return false;
}
if(iSent < iRead) {
//send wait
//FatalL << "send wait";
return true;
}
//send success
}
return false;
};
onFlush();
sock->setOnFlush(onFlush);
return true;
}
inline bool HttpSession::makeMeun(const string &strFullPath, string &strRet) {
string strPathPrefix(strFullPath);
strPathPrefix = strPathPrefix.substr(0, strPathPrefix.length() - 1);
if (!File::is_dir(strPathPrefix.data())) {
return false;
}
strRet = "<html>\r\n"
"<head>\r\n"
"<title>文件索引</title>\r\n"
"</head>\r\n"
"<body>\r\n"
"<h1>文件索引:";
string strPath = strFullPath;
strPath = strPath.substr(m_strPath.length(), strFullPath.length() - m_strPath.length());
strRet += strPath;
strRet += "</h1>\r\n";
if (strPath != "/") {
strRet += "<li><a href=\"";
strRet += "/";
strRet += "\">";
strRet += "根目录";
strRet += "</a></li>\r\n";
strRet += "<li><a href=\"";
strRet += "../";
strRet += "\">";
strRet += "上一级目录";
strRet += "</a></li>\r\n";
}
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);
}
for(auto &strFile :setFile ){
string strAbsolutePath = strPathPrefix + "/" + strFile;
if (File::is_dir(strAbsolutePath.data())) {
strRet += "<li><a href=\"";
strRet += strFile;
strRet += "/\">";
strRet += strFile;
strRet += "/</a></li>\r\n";
} else { //是文件
strRet += "<li><a href=\"";
strRet += strFile;
strRet += "\">";
strRet += strFile;
struct stat fileData;
if (0 == stat(strAbsolutePath.data(), &fileData)) {
auto &fileSize = fileData.st_size;
if (fileSize < 1024) {
strRet += StrPrinter << " (" << fileData.st_size << "B)" << endl;
} else if (fileSize < 1024 * 1024) {
strRet += StrPrinter << " (" << fileData.st_size / 1024 << "KB)" << endl;
} else if (fileSize < 1024 * 1024 * 1024) {
strRet += StrPrinter << " (" << fileData.st_size / 1024 / 1024 << "MB)" << endl;
} else {
strRet += StrPrinter << " (" << fileData.st_size / 1024 / 1024 / 1024 << "GB)" << endl;
}
}
strRet += "</a></li>\r\n";
}
}
closedir(pDir);
strRet += "<ul>\r\n";
strRet += "</ul>\r\n</body></html>";
return true;
}
inline void HttpSession::sendResponse(const char* pcStatus, const KeyValue& header, const string& strContent) {
_StrPrinter printer;
printer << "HTTP/1.1 " << pcStatus << " \r\n";
for (auto &pr : header) {
printer << pr.first << ": " << pr.second << "\r\n";
}
printer << "\r\n" << strContent;
auto strSend = printer << endl;
//DebugL << strSend;
send(strSend);
m_ticker.resetTime();
}
inline HttpSession::KeyValue HttpSession::makeHttpHeader(bool bClose, int64_t iContentSize,const char* pcContentType) {
KeyValue headerOut;
static string serverName = mINI::Instance()[Config::Http::kServerName];
static string charSet = mINI::Instance()[Config::Http::kCharSet];
static uint32_t keepAliveSec = mINI::Instance()[Config::Http::kKeepAliveSecond].as<uint32_t>();
static uint32_t reqCnt = mINI::Instance()[Config::Http::kMaxReqCount].as<uint32_t>();
headerOut.emplace("Server", serverName);
headerOut.emplace("Connection", bClose ? "close" :
StrPrinter << "keep-alive: timeout=" << keepAliveSec
<< ", max=" << reqCnt << endl);
headerOut.emplace("Date", dateStr());
if(iContentSize >=0 && pcContentType !=nullptr){
auto strContentType = StrPrinter << pcContentType << "; charset=" << charSet << endl;
headerOut.emplace("Content-Type",strContentType.data());
headerOut.emplace("Content-Length", StrPrinter<<iContentSize<<endl);
}
return headerOut;
}
inline int HttpSession::Handle_Req_POST() {
int iContentLen = atoi(m_parser["Content-Length"].data());
if (!iContentLen) {
return false;
}
if ((int) m_strRcvBuf.size() < iContentLen) {
return 2; //需要更多数据
}
auto strContent = m_strRcvBuf.substr(0, iContentLen);
m_strRcvBuf.erase(0, iContentLen);
string strUrl = strCoding::UrlUTF8Decode(m_parser.Url());
string strConType = m_parser["Connection"];
static string charSet = mINI::Instance()[Config::Http::kCharSet];
static uint32_t reqCnt = mINI::Instance()[Config::Http::kMaxReqCount].as<uint32_t>();
bool bClose = (strcasecmp(strConType.data(),"close") == 0) && ( ++m_iReqCnt < reqCnt);
m_parser.setUrl(strUrl);
m_parser.setContent(strContent);
auto headerOut=makeHttpHeader(bClose);
static string notFound = mINI::Instance()[Config::Http::kNotFound];
string strContentOut = notFound;
string strCodeOut = "404 Not Found";
NoticeCenter::Instance().emitEvent(
Config::Broadcast::kBroadcastHttpRequest,
m_parser,strCodeOut,headerOut,strContentOut);
if(strContentOut.size()){
headerOut.emplace("Content-Type",StrPrinter<<"text/json; charset=" << charSet <<endl);
headerOut.emplace("Content-Length",StrPrinter<<strContentOut.size()<<endl);
}
sendResponse(strCodeOut.data(), headerOut, strContentOut);
return !bClose;
}
inline void HttpSession::sendNotFound(bool bClose) {
static string notFound = mINI::Instance()[Config::Http::kNotFound];
sendResponse("404 Not Found", makeHttpHeader(bClose, notFound.size()), notFound.data());
}
} /* namespace Http */
} /* namespace ZL */