ZLMediaKit/src/Http/HttpSession.cpp

411 lines
12 KiB
C++
Raw Normal View History

2017-04-01 16:35:56 +08:00
/*
* HttpSession.cpp
*
* Created on: 2016922
* 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 */