966 lines
28 KiB
C++
966 lines
28 KiB
C++
#include "WHttpServer.h"
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
//#include "stdafx.h"
|
|
|
|
|
|
|
|
WHttpServer::WHttpServer()
|
|
{
|
|
mg_mgr_init(&_mgr);
|
|
}
|
|
|
|
WHttpServer::~WHttpServer()
|
|
{
|
|
stop();
|
|
delete _threadPool;
|
|
_threadPool = nullptr;
|
|
}
|
|
|
|
|
|
//实现usleep
|
|
void WHttpServer::usleep(unsigned long usec)
|
|
{
|
|
HANDLE timer;
|
|
LARGE_INTEGER interval;
|
|
interval.QuadPart = (10 * usec);
|
|
|
|
timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
|
SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0);
|
|
WaitForSingleObject(timer, INFINITE);
|
|
CloseHandle(timer);
|
|
}
|
|
|
|
|
|
bool WHttpServer::init(int maxEventThreadNum)
|
|
{
|
|
std::unique_lock<mutex> locker(_httpLocker);
|
|
_threadPool = new WThreadPool();
|
|
_threadPool->setMaxThreadNum(maxEventThreadNum);
|
|
return true;
|
|
}
|
|
|
|
bool WHttpServer::startHttp(string strIp, int port)
|
|
{
|
|
std::unique_lock<mutex> locker(_httpLocker);
|
|
if (!_threadPool)
|
|
{
|
|
Logw("WHttpServer::StartHttp do not init");
|
|
return false;
|
|
}
|
|
|
|
if (_httpPort != -1)
|
|
{
|
|
Logw("WHttpServer::StartHttp http server is already start port:%d", _httpPort);
|
|
return false;
|
|
}
|
|
std::stringstream sstream;
|
|
sstream << strIp << ":" << port;
|
|
_httpCbMsg.httpServer = this;
|
|
_httpCbMsg.httpsFlag = false;
|
|
mg_connection *serverConn = mg_http_listen(&_mgr, sstream.str().c_str(), WHttpServer::recvHttpRequestCallback, (void *)&_httpCbMsg);
|
|
if (!serverConn)
|
|
{
|
|
Logw("WHttpServer::StartHttp http server start failed: %s", sstream.str().c_str());
|
|
return false;
|
|
}
|
|
Logi("WHttpServer::StartHttp http server start success: %s", sstream.str().c_str());
|
|
_httpPort = port;
|
|
return true;
|
|
}
|
|
|
|
bool WHttpServer::startHttps(int port, string certPath, string keyPath)
|
|
{
|
|
std::unique_lock<mutex> locker(_httpLocker);
|
|
if (!_threadPool)
|
|
{
|
|
Logw("WHttpServer::StartHttps do not init");
|
|
return false;
|
|
}
|
|
|
|
if (_httpsPort != -1)
|
|
{
|
|
Logw("WHttpServer::StartHttps https server is already start port:%d", _httpsPort);
|
|
return false;
|
|
}
|
|
_certPath = certPath;
|
|
_keyPath = keyPath;
|
|
std::stringstream sstream;
|
|
sstream << "https://0.0.0.0:" << port;
|
|
_httpsCbMsg.httpServer = this;
|
|
_httpsCbMsg.httpsFlag = true;
|
|
mg_connection *serverConn = mg_http_listen(&_mgr, sstream.str().c_str(), WHttpServer::recvHttpRequestCallback, (void *)&_httpsCbMsg);
|
|
if (!serverConn)
|
|
{
|
|
Logw("WHttpServer::StartHttps https server start failed: %s", sstream.str().c_str());
|
|
return false;
|
|
}
|
|
Logi("WHttpServer::StartHttps https server start success: %s", sstream.str().c_str());
|
|
_httpsPort = port;
|
|
return true;
|
|
}
|
|
|
|
bool WHttpServer::stop()
|
|
{
|
|
std::unique_lock<mutex> locker(_httpLocker);
|
|
if (_httpPort == -1 && _httpsPort == -1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
_httpPort = -1;
|
|
_httpsPort = -1;
|
|
usleep(100*1000); // make sure run() can not call mg_mgr_poll
|
|
|
|
mg_mgr_free(&_mgr);
|
|
reset();
|
|
return true;
|
|
}
|
|
|
|
bool WHttpServer::run()
|
|
{
|
|
if (_httpPort == -1 && _httpsPort == -1)
|
|
{
|
|
usleep(1000);
|
|
return false;
|
|
}
|
|
|
|
sendHttpMsgPoll();
|
|
mg_mgr_poll(&_mgr, 1);
|
|
return true;
|
|
}
|
|
|
|
bool WHttpServer::isRunning()
|
|
{
|
|
return (_httpPort != -1 || _httpsPort != -1);
|
|
}
|
|
|
|
void WHttpServer::addHttpApi(const string &uri, HttpCbFun fun, int httpMethods)
|
|
{
|
|
HttpApiData httpApiData;
|
|
httpApiData.httpCbFun = fun;
|
|
httpApiData.httpMethods = httpMethods;
|
|
_httpApiMap[uri] = httpApiData;
|
|
}
|
|
|
|
void WHttpServer::addChunkHttpApi(const string &uri, HttpCbFun fun, int httpMethods)
|
|
{
|
|
HttpApiData httpApiData;
|
|
httpApiData.httpCbFun = fun;
|
|
httpApiData.httpMethods = httpMethods;
|
|
_chunkHttpApiMap[uri] = httpApiData;
|
|
}
|
|
|
|
void WHttpServer::setHttpFilter(HttpFilterFun filter)
|
|
{
|
|
_httpFilterFun = filter;
|
|
}
|
|
|
|
void WHttpServer::forceCloseHttpConnection(shared_ptr<HttpReqMsg> httpMsg)
|
|
{
|
|
mg_connection *conn = httpMsg->httpConnection;
|
|
conn->is_closing = 1;
|
|
}
|
|
|
|
void WHttpServer::closeHttpConnection(struct mg_connection *conn, bool isDirectClose)
|
|
{
|
|
if (conn->label[W_FD_STATUS_BIT] == HTTP_NORMAL_CLOSE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isDirectClose)
|
|
{
|
|
conn->is_draining = 1;
|
|
}
|
|
conn->label[W_FD_STATUS_BIT] = HTTP_NORMAL_CLOSE;
|
|
}
|
|
|
|
std::set<string> WHttpServer::getSupportMethods(int httpMethods)
|
|
{
|
|
std::set<string> methodsSet;
|
|
if (httpMethods & W_HTTP_GET)
|
|
{
|
|
methodsSet.insert("GET");
|
|
}
|
|
|
|
if (httpMethods & W_HTTP_GET)
|
|
{
|
|
methodsSet.insert("GET");
|
|
}
|
|
|
|
if (httpMethods & W_HTTP_POST)
|
|
{
|
|
methodsSet.insert("POST");
|
|
}
|
|
|
|
if (httpMethods & W_HTTP_PUT)
|
|
{
|
|
methodsSet.insert("PUT");
|
|
}
|
|
|
|
if (httpMethods & W_HTTP_DELETE)
|
|
{
|
|
methodsSet.insert("DELETE");
|
|
}
|
|
|
|
if (httpMethods & W_HTTP_HEAD)
|
|
{
|
|
methodsSet.insert("HEAD");
|
|
}
|
|
|
|
return methodsSet;
|
|
}
|
|
|
|
bool WHttpServer::handleStaticWebDir(shared_ptr<HttpReqMsg> httpMsg, HttpStaticWebDir &webDir)
|
|
{
|
|
string filePath = webDir.dirPath + httpMsg->uri;
|
|
|
|
FILE *file = fopen(filePath.c_str(), "r");
|
|
if (!file)
|
|
{
|
|
// Logw("WHttpServer::handleStaticWebDir can not open file:%s", filePath.c_str());
|
|
// httpReplyJson(httpMsg, 500, "", formJsonBody(101, "can not find this file"));
|
|
return false;
|
|
}
|
|
|
|
struct stat statbuf;
|
|
stat(filePath.c_str(), &statbuf);
|
|
int64_t fileSize = statbuf.st_size;
|
|
stringstream sstream;
|
|
|
|
if (httpMsg->method == "HEAD")
|
|
{
|
|
formStaticWebDirResHeader(sstream, httpMsg, webDir, filePath, 200);
|
|
sstream << "Content-Length: " << fileSize << "\r\n";
|
|
sstream << "\r\n"; // 空行表示http头部完成
|
|
addSendMsgToQueue(httpMsg, sstream.str().c_str(), sstream.str().size());
|
|
}
|
|
else
|
|
{
|
|
if (httpMsg->headers.find("range") != httpMsg->headers.end())
|
|
{
|
|
string rangeStr = httpMsg->headers["range"];
|
|
int64_t startByte = 0, endByte = 0;
|
|
parseRangeStr(rangeStr, startByte, endByte, fileSize);
|
|
startByte = startByte < 0 ? 0 : startByte;
|
|
endByte = (endByte > fileSize - 1) ? (fileSize - 1) : endByte;
|
|
int64_t contentLength = endByte - startByte + 1;
|
|
contentLength = contentLength < 0 ? 0 : contentLength;
|
|
if (contentLength < fileSize)
|
|
{
|
|
formStaticWebDirResHeader(sstream, httpMsg, webDir, filePath, 206);
|
|
}
|
|
else
|
|
{
|
|
formStaticWebDirResHeader(sstream, httpMsg, webDir, filePath, 200);
|
|
}
|
|
sstream << "Content-Range: bytes " << startByte << "-" << endByte << "/" << fileSize << "\r\n";
|
|
sstream << "Accept-Ranges: bytes\r\n";
|
|
sstream << "Content-Length: " << contentLength << "\r\n";
|
|
sstream << "\r\n";
|
|
addSendMsgToQueue(httpMsg, sstream.str().c_str(), sstream.str().size());
|
|
readStaticWebFile(httpMsg, file, contentLength, startByte);
|
|
}
|
|
else
|
|
{
|
|
formStaticWebDirResHeader(sstream, httpMsg, webDir, filePath, 200);
|
|
sstream << "Content-Length: " << fileSize << "\r\n";
|
|
sstream << "\r\n";
|
|
addSendMsgToQueue(httpMsg, sstream.str().c_str(), sstream.str().size());
|
|
readStaticWebFile(httpMsg, file, fileSize, 0);
|
|
}
|
|
}
|
|
|
|
fclose(file);
|
|
if (httpMsg->isKeepingAlive)
|
|
{
|
|
httpMsg->lastKeepAliveTime = getSysTickCountInMilliseconds();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WHttpServer::formStaticWebDirResHeader(stringstream &sstream, shared_ptr<HttpReqMsg> &httpMsg, HttpStaticWebDir &webDir,
|
|
string &filePath, int code)
|
|
{
|
|
sstream << "HTTP/1.1 "<< code << " " << mg_http_status_code_str(code) << "\r\n";
|
|
sstream << "Content-Type: " << guess_content_type(filePath.c_str()) << "\r\n";
|
|
map<string, string> &reqHeaders = httpMsg->headers;
|
|
if (httpMsg->isKeepingAlive)
|
|
{
|
|
sstream << "Connection: " << "keep-alive" << "\r\n";
|
|
}
|
|
else if ((reqHeaders.find("connection") != reqHeaders.end()) && (reqHeaders["connection"] == "keep-alive"))
|
|
{
|
|
if (_currentKeepAliveNum < MAX_KEEP_ALIVE_NUM)
|
|
{
|
|
sstream << "Connection: " << "keep-alive" << "\r\n";
|
|
_currentKeepAliveNum++;
|
|
httpMsg->isKeepingAlive = true;
|
|
}
|
|
else
|
|
{
|
|
sstream << "Connection: " << "close" << "\r\n";
|
|
}
|
|
}
|
|
if (!webDir.header.empty())
|
|
{
|
|
sstream << webDir.header;
|
|
}
|
|
// sstream << "Content-Disposition: attachment;filename=" << fileName << "\r\n";
|
|
}
|
|
|
|
void WHttpServer::readStaticWebFile(shared_ptr<HttpReqMsg> httpMsg, FILE *file, int64_t contentSize, int64_t startByte)
|
|
{
|
|
int64_t currentReadSize = 0;
|
|
int64_t maxPerReadSize = 1024*1024;
|
|
int64_t perReadSize = contentSize > maxPerReadSize ? maxPerReadSize : contentSize;
|
|
int64_t remainSize;
|
|
|
|
uint64_t currentMs = 0;
|
|
uint64_t lastWriteMs = getSysTickCountInMilliseconds();
|
|
fseek(file, startByte, SEEK_SET);
|
|
while((remainSize = contentSize - currentReadSize) > 0 && isRunning())
|
|
{
|
|
if (isClientDisconnect(httpMsg))
|
|
{
|
|
Logw("WHttpServer::readStaticWebFile http client close the connection actively");
|
|
break;
|
|
}
|
|
|
|
// 为了防止发送队列里的数据太大,占用大量内存,当发送队列里面的数据达到一定量,先等待
|
|
if (httpMsg->sendQueue->size() >= HTTP_SEND_QUEUE_SIZE)
|
|
{
|
|
currentMs = getSysTickCountInMilliseconds();
|
|
if (currentMs - lastWriteMs > MAX_DOWNLOAD_PAUSE_TIME * 1000)
|
|
{
|
|
Logi("WHttpServer::readStaticWebFile download file timeout %s", httpMsg->uri.c_str());
|
|
forceCloseHttpConnection(httpMsg);
|
|
return;
|
|
}
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
|
|
string *fileStr = new string();
|
|
fileStr->resize(perReadSize);
|
|
|
|
int64_t currentWantReadSize = remainSize > perReadSize ? perReadSize : remainSize;
|
|
int64_t readSize = fread((char *)fileStr->c_str(), 1, currentWantReadSize, file);
|
|
currentReadSize += readSize;
|
|
if (readSize == 0)
|
|
{
|
|
Logw("WHttpServer::readStaticWebFile read size is 0");
|
|
delete fileStr;
|
|
break;
|
|
}
|
|
|
|
if (readSize != perReadSize)
|
|
{
|
|
fileStr->resize(readSize);
|
|
}
|
|
|
|
addSendMsgToQueue(httpMsg, fileStr);
|
|
lastWriteMs = getSysTickCountInMilliseconds();
|
|
}
|
|
}
|
|
|
|
void WHttpServer::parseRangeStr(string rangeStr, int64_t &startByte, int64_t &endByte, int64_t fileSize)
|
|
{
|
|
startByte = 0;
|
|
endByte = 0;
|
|
size_t equalMarkIndex = rangeStr.find('=');
|
|
size_t lineMarkIndex = rangeStr.find('-');
|
|
if (equalMarkIndex == string::npos || lineMarkIndex == string::npos)
|
|
{
|
|
return;
|
|
}
|
|
startByte = stoll(rangeStr.substr(equalMarkIndex + 1, lineMarkIndex - equalMarkIndex - 1));
|
|
if (lineMarkIndex == rangeStr.size() - 1)
|
|
{
|
|
endByte = fileSize - 1;
|
|
}
|
|
else
|
|
{
|
|
endByte = stoll(rangeStr.substr(lineMarkIndex + 1));
|
|
}
|
|
}
|
|
|
|
void WHttpServer::reset()
|
|
{
|
|
_currentKeepAliveNum = 0;
|
|
}
|
|
|
|
void WHttpServer::logHttpRequestMsg(mg_connection *conn, mg_http_message *httpCbData)
|
|
{
|
|
if (httpCbData->message.len < 1024)
|
|
{
|
|
Logi("WHttpServer::logHttpRequestMsg %s request id:%ld, message: %s", conn->is_tls ? "https" : "http", conn->id, httpCbData->message.ptr);
|
|
}
|
|
else
|
|
{
|
|
char msg[1024] = {0};
|
|
memcpy(msg, httpCbData->message.ptr, 1024);
|
|
Logi("WHttpServer::logHttpRequestMsg %s request id:%ld, message: %s", conn->is_tls ? "https" : "http", conn->id, msg);
|
|
}
|
|
}
|
|
|
|
void WHttpServer::httpReplyJson(shared_ptr<HttpReqMsg> httpMsg, int httpCode, string head, string body)
|
|
{
|
|
stringstream sstream;
|
|
sstream << "HTTP/1.1 " << httpCode << " " << mg_http_status_code_str(httpCode) << "\r\n";
|
|
sstream << "Content-Type: application/json\r\n";
|
|
if (!head.empty())
|
|
{
|
|
sstream << head;
|
|
}
|
|
sstream << "Content-Length: " << body.size() << "\r\n\r\n";
|
|
sstream << body;
|
|
|
|
string data = sstream.str();
|
|
// xy_sync_mg_send(conn, data.c_str(), data.size());
|
|
addSendMsgToQueue(httpMsg, data.c_str(), data.size());
|
|
}
|
|
|
|
void WHttpServer::addSendMsgToQueue(shared_ptr<HttpReqMsg> httpMsg, const char *data, int len)
|
|
{
|
|
string *sendMsg = new string();
|
|
sendMsg->resize(len);
|
|
memcpy((char *)sendMsg->c_str(), data, len);
|
|
bool res = httpMsg->sendQueue->enQueue(sendMsg);
|
|
assert(res);
|
|
}
|
|
|
|
void WHttpServer::addSendMsgToQueue(shared_ptr<HttpReqMsg> httpMsg, string *sendMsg)
|
|
{
|
|
bool res = httpMsg->sendQueue->enQueue(sendMsg);
|
|
assert(res);
|
|
}
|
|
|
|
string WHttpServer::formJsonBody(int code, string message, string body)
|
|
{
|
|
stringstream sstream;
|
|
sstream << "{";
|
|
sstream << R"("code":)" << code << ",";
|
|
sstream << R"("message":")" << message << R"(")" << ",";
|
|
sstream << R"("url":")" << body << R"(")" ;
|
|
sstream << "}";
|
|
return sstream.str();
|
|
}
|
|
|
|
bool WHttpServer::isClientDisconnect(shared_ptr<HttpReqMsg> httpMsg)
|
|
{
|
|
return (httpMsg->httpConnection->label[W_CLIENT_CLOSE_BIT] == 1);
|
|
}
|
|
|
|
shared_ptr<string> WHttpServer::deQueueHttpChunk(shared_ptr<HttpReqMsg> httpMsg)
|
|
{
|
|
string *res = nullptr;
|
|
httpMsg->chunkQueue->deQueue(res);
|
|
return shared_ptr<string>(res);
|
|
}
|
|
|
|
bool WHttpServer::addStaticWebDir(const string &dir, const string &header)
|
|
{
|
|
char tempDir[PATH_MAX];
|
|
if (!realpath(dir.c_str(), tempDir))
|
|
{
|
|
Loge("WHttpServer::addStaticWebDir the dir path is wrong: %s", dir.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!mg_is_dir(tempDir))
|
|
{
|
|
Loge("WHttpServer::addStaticWebDir is not dir: %s", dir.c_str());
|
|
return false;
|
|
}
|
|
|
|
HttpStaticWebDir staticDir;
|
|
staticDir.dirPath = tempDir;
|
|
staticDir.header = header;
|
|
_staticDirVect.push_back(staticDir);
|
|
return true;
|
|
}
|
|
|
|
void WHttpServer::recvHttpRequest(mg_connection *conn, int msgType, void *msgData, void *cbData)
|
|
{
|
|
if (_httpPort == -1 && _httpsPort == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HttpCbMsg *cbMsg = (HttpCbMsg *)cbData;
|
|
int64_t fd = (int64_t)conn->fd;
|
|
if (msgType == MG_EV_ACCEPT && cbMsg->httpsFlag)
|
|
{
|
|
struct mg_tls_opts opts;
|
|
opts.ca = nullptr;
|
|
opts.cert = _certPath.c_str();
|
|
opts.certkey = _keyPath.c_str();
|
|
opts.ciphers = nullptr;
|
|
opts.srvname.ptr = nullptr;
|
|
opts.srvname.len = 0;
|
|
Logi("WHttpServer::recvHttpRequest https connect come id:%ld", conn->id);
|
|
mg_tls_init(conn, &opts);
|
|
}
|
|
else if (msgType == MG_EV_HTTP_MSG)
|
|
{
|
|
struct mg_http_message *httpCbData = (struct mg_http_message *) msgData;
|
|
if (isValidHttpChunk(httpCbData))
|
|
{
|
|
return;
|
|
}
|
|
|
|
logHttpRequestMsg(conn, httpCbData);
|
|
if (httpCbData->head.len > HTTP_MAX_HEAD_SIZE)
|
|
{
|
|
mg_http_reply(conn, 500, "", formJsonBody(HTTP_BEYOND_HEAD_SIZE, "head size beyond 2M").c_str());
|
|
closeHttpConnection(conn, true);
|
|
return;
|
|
}
|
|
|
|
HttpApiData cbApiData;
|
|
if (!findHttpCbFun(httpCbData, cbApiData))
|
|
{
|
|
if ((mg_vcasecmp(&(httpCbData->method), "GET") != 0) && (mg_vcasecmp(&(httpCbData->method), "HEAD") != 0))
|
|
{
|
|
mg_http_reply(conn, 400, "", formJsonBody(HTTP_UNKNOWN_REQUEST, "unknown request").c_str());
|
|
closeHttpConnection(conn, true);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
cbApiData.findStaticFileFlag = true;
|
|
}
|
|
}
|
|
shared_ptr<HttpReqMsg> httpMsg = nullptr;
|
|
// if keep-alive fd, erase last http msg
|
|
if (_workingMsgMap.find(fd) != _workingMsgMap.end())
|
|
{
|
|
releaseHttpReqMsg(_workingMsgMap[fd]);
|
|
_workingMsgMap.erase(fd);
|
|
httpMsg = parseHttpMsg(conn, httpCbData);
|
|
httpMsg->isKeepingAlive = true;
|
|
_workingMsgMap[fd] = httpMsg;
|
|
}
|
|
else
|
|
{
|
|
httpMsg = parseHttpMsg(conn, httpCbData);
|
|
}
|
|
_workingMsgMap[fd] = httpMsg;
|
|
_threadPool->concurrentRun(&WHttpServer::handleHttpMsg, this, std::ref(_workingMsgMap[fd]), cbApiData);
|
|
}
|
|
else if (msgType == MG_EV_HTTP_CHUNK)
|
|
{
|
|
struct mg_http_message *httpCbData = (struct mg_http_message *) msgData;
|
|
HttpApiData chunkCbApiData;
|
|
if (!findChunkHttpCbFun(httpCbData, chunkCbApiData))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (httpCbData->head.len > HTTP_MAX_HEAD_SIZE)
|
|
{
|
|
mg_http_reply(conn, 500, "", formJsonBody(HTTP_BEYOND_HEAD_SIZE, "head size beyond 2M").c_str());
|
|
closeHttpConnection(conn, true);
|
|
return;
|
|
}
|
|
|
|
if (_workingMsgMap.find(fd) == _workingMsgMap.end())
|
|
{
|
|
logHttpRequestMsg(conn, httpCbData);
|
|
shared_ptr<HttpReqMsg> httpMsg = parseHttpMsg(conn, httpCbData, true);
|
|
_workingMsgMap[fd] = httpMsg;
|
|
_threadPool->concurrentRun(&WHttpServer::handleChunkHttpMsg, this, std::ref(_workingMsgMap[fd]), chunkCbApiData);
|
|
}
|
|
else
|
|
{
|
|
shared_ptr<HttpReqMsg> httpMsg = _workingMsgMap[fd];
|
|
enQueueHttpChunk(httpMsg, httpCbData);
|
|
}
|
|
}
|
|
else if (msgType == MG_EV_CLOSE)
|
|
{
|
|
Logi("WHttpServer::RecvHttpRequest http disconnect id:%ld", conn->id);
|
|
if (conn->label[W_VALID_CONNECT_BIT] != 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (conn->label[W_FD_STATUS_BIT] == HTTP_NORMAL_CLOSE)
|
|
{
|
|
releaseHttpReqMsg(_workingMsgMap[fd]);
|
|
_workingMsgMap.erase(fd);
|
|
return;
|
|
}
|
|
|
|
conn->label[W_CLIENT_CLOSE_BIT] = 1;
|
|
while(conn->label[W_FD_STATUS_BIT] == HTTP_IN_USE)
|
|
{
|
|
usleep(1);
|
|
}
|
|
releaseHttpReqMsg(_workingMsgMap[fd]);
|
|
_workingMsgMap.erase(fd);
|
|
}
|
|
}
|
|
|
|
void WHttpServer::handleHttpMsg(shared_ptr<HttpReqMsg> &httpMsg, HttpApiData httpCbData)
|
|
{
|
|
if (httpCbData.findStaticFileFlag)
|
|
{
|
|
bool findFlag = false;
|
|
for(int i = 0; i < (int)_staticDirVect.size(); i++)
|
|
{
|
|
if (handleStaticWebDir(httpMsg, _staticDirVect[i]))
|
|
{
|
|
findFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!findFlag)
|
|
{
|
|
httpReplyJson(httpMsg, 400, "", formJsonBody(HTTP_UNKNOWN_REQUEST, "unknown request").c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_httpFilterFun && !_httpFilterFun(httpMsg))
|
|
{
|
|
closeHttpConnection(httpMsg->httpConnection);
|
|
return;
|
|
}
|
|
set<string> methods = getSupportMethods(httpCbData.httpMethods);
|
|
if (methods.find(httpMsg->method) == methods.end())
|
|
{
|
|
httpReplyJson(httpMsg, 400, "", formJsonBody(HTTP_UNKNOWN_REQUEST, "do not support this method"));
|
|
closeHttpConnection(httpMsg->httpConnection);
|
|
return;
|
|
}
|
|
|
|
httpCbData.httpCbFun(httpMsg);
|
|
}
|
|
|
|
if (httpMsg->isKeepingAlive)
|
|
{
|
|
httpMsg->httpConnection->label[W_FD_STATUS_BIT] = HTTP_NOT_USE;
|
|
}
|
|
else
|
|
{
|
|
closeHttpConnection(httpMsg->httpConnection);
|
|
}
|
|
}
|
|
|
|
void WHttpServer::handleChunkHttpMsg(shared_ptr<HttpReqMsg> &httpMsg, HttpApiData chunkHttpCbData)
|
|
{
|
|
if (_httpFilterFun && !_httpFilterFun(httpMsg))
|
|
{
|
|
closeHttpConnection(httpMsg->httpConnection);
|
|
return;
|
|
}
|
|
set<string> methods = getSupportMethods(chunkHttpCbData.httpMethods);
|
|
if (methods.find(httpMsg->method) == methods.end())
|
|
{
|
|
httpReplyJson(httpMsg, 400, "", formJsonBody(HTTP_UNKNOWN_REQUEST, "do not support this method"));
|
|
closeHttpConnection(httpMsg->httpConnection);
|
|
return;
|
|
}
|
|
chunkHttpCbData.httpCbFun(httpMsg);
|
|
closeHttpConnection(httpMsg->httpConnection);
|
|
}
|
|
|
|
void WHttpServer::sendHttpMsgPoll()
|
|
{
|
|
static int64_t pollCount = 0;
|
|
pollCount++;
|
|
int64_t currentTime = -1;
|
|
if (pollCount % 1000 == 0)
|
|
{
|
|
currentTime = getSysTickCountInMilliseconds();
|
|
}
|
|
std::map<int64_t, shared_ptr<HttpReqMsg>>::iterator it;
|
|
for (it = _workingMsgMap.begin(); it != _workingMsgMap.end(); it++)
|
|
{
|
|
shared_ptr<HttpReqMsg> httpMsg = it->second;
|
|
mg_connection *conn = httpMsg->httpConnection;
|
|
|
|
// identify if keep-alive timeout
|
|
if (httpMsg->isKeepingAlive && (currentTime != -1) && (conn->label[W_FD_STATUS_BIT] == HTTP_NOT_USE))
|
|
{
|
|
if ((currentTime - httpMsg->lastKeepAliveTime > KEEP_ALIVE_TIME * 1000))
|
|
{
|
|
conn->label[W_FD_STATUS_BIT] = HTTP_NORMAL_CLOSE;
|
|
_currentKeepAliveNum--;
|
|
}
|
|
}
|
|
|
|
if ((conn->label[W_FD_STATUS_BIT] == HTTP_NORMAL_CLOSE) && (httpMsg->sendQueue->size() == 0))
|
|
{
|
|
conn->is_draining = 1;
|
|
continue;
|
|
}
|
|
|
|
if (conn->send.len > SEND_BUF_SIZE_BOUNDARY)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (httpMsg->sendQueue->size() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
shared_ptr<string> sendMsg = deQueueHttpSendMsg(httpMsg);
|
|
assert(sendMsg.get());
|
|
mg_send(conn, (const void *)sendMsg->c_str(), sendMsg->size());
|
|
}
|
|
}
|
|
|
|
shared_ptr<string> WHttpServer::deQueueHttpSendMsg(shared_ptr<HttpReqMsg> httpMsg)
|
|
{
|
|
string *sendMsg = nullptr;
|
|
httpMsg->sendQueue->deQueue(sendMsg);
|
|
return shared_ptr<string>(sendMsg);
|
|
}
|
|
|
|
bool WHttpServer::findChunkHttpCbFun(mg_http_message *httpCbData, HttpApiData &cbApiData)
|
|
{
|
|
bool res = false;
|
|
for (auto it = _chunkHttpApiMap.begin(); it != _chunkHttpApiMap.end(); it++)
|
|
{
|
|
if (httpCbData->uri.len < it->first.size())
|
|
{
|
|
continue;
|
|
}
|
|
size_t cmpSize = it->first.size();
|
|
if (strncmp(it->first.c_str(), httpCbData->uri.ptr, cmpSize) == 0)
|
|
{
|
|
if (((it->first)[cmpSize - 1] == '/') || (httpCbData->uri.len == cmpSize) ||
|
|
(httpCbData->uri.len > cmpSize && httpCbData->uri.ptr[cmpSize] == '/'))
|
|
{
|
|
cbApiData = it->second;
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool WHttpServer::isValidHttpChunk(mg_http_message *httpCbData)
|
|
{
|
|
bool res = false;
|
|
for (auto it = _chunkHttpApiMap.begin(); it != _chunkHttpApiMap.end(); it++)
|
|
{
|
|
if (httpCbData->uri.len < it->first.size())
|
|
{
|
|
continue;
|
|
}
|
|
size_t cmpSize = it->first.size();
|
|
if (strncmp(it->first.c_str(), httpCbData->uri.ptr, cmpSize) == 0)
|
|
{
|
|
if (((it->first)[cmpSize - 1] == '/') || (httpCbData->uri.len == cmpSize) ||
|
|
(httpCbData->uri.len > cmpSize && httpCbData->uri.ptr[cmpSize] == '/'))
|
|
{
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool WHttpServer::findHttpCbFun(mg_http_message *httpCbData, HttpApiData &cbApiData)
|
|
{
|
|
bool res = false;
|
|
for (auto it = _httpApiMap.begin(); it != _httpApiMap.end(); it++)
|
|
{
|
|
if (httpCbData->uri.len < it->first.size())
|
|
{
|
|
continue;
|
|
}
|
|
size_t cmpSize = it->first.size();
|
|
if (strncmp(it->first.c_str(), httpCbData->uri.ptr, cmpSize) == 0)
|
|
{
|
|
if (((it->first)[cmpSize - 1] == '/') || (httpCbData->uri.len == cmpSize) ||
|
|
(httpCbData->uri.len > cmpSize && httpCbData->uri.ptr[cmpSize] == '/'))
|
|
{
|
|
cbApiData = it->second;
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
shared_ptr<HttpReqMsg> WHttpServer::parseHttpMsg(mg_connection *conn, mg_http_message *httpCbData, bool chunkFlag)
|
|
{
|
|
shared_ptr<HttpReqMsg> res = shared_ptr<HttpReqMsg>(new HttpReqMsg());
|
|
res->httpConnection = conn;
|
|
conn->label[W_VALID_CONNECT_BIT] = 1;
|
|
conn->label[W_FD_STATUS_BIT] = HTTP_IN_USE;
|
|
res->sendQueue = shared_ptr<HttpSendQueue>(new HttpSendQueue());
|
|
|
|
res->method.resize(httpCbData->method.len);
|
|
memcpy((char*)res->method.c_str(), httpCbData->method.ptr, httpCbData->method.len);
|
|
toUpperString(res->method);
|
|
|
|
res->uri.resize(httpCbData->uri.len);
|
|
memcpy((char*)res->uri.c_str(), httpCbData->uri.ptr, httpCbData->uri.len);
|
|
|
|
string queryKey = "";
|
|
string queryValue = "";
|
|
bool valueFlag = false;
|
|
for (int i = 0; i < (int)httpCbData->query.len; i++)
|
|
{
|
|
if (httpCbData->query.ptr[i] == '=')
|
|
{
|
|
valueFlag = true;
|
|
continue;
|
|
}
|
|
else if(httpCbData->query.ptr[i] == '&')
|
|
{
|
|
valueFlag = false;
|
|
res->querys[queryKey] = queryValue;
|
|
queryKey.clear();
|
|
queryValue.clear();
|
|
continue;
|
|
}
|
|
|
|
if (!valueFlag)
|
|
{
|
|
queryKey.append(1, httpCbData->query.ptr[i]);
|
|
}
|
|
else
|
|
{
|
|
queryValue.append(1, httpCbData->query.ptr[i]);
|
|
}
|
|
}
|
|
res->querys[queryKey] = queryValue;
|
|
|
|
res->proto.resize(httpCbData->proto.len);
|
|
memcpy((char*)res->proto.c_str(), httpCbData->proto.ptr, httpCbData->proto.len);
|
|
|
|
for(int i = 0; i < MG_MAX_HTTP_HEADERS; i++)
|
|
{
|
|
if (httpCbData->headers[i].name.len == 0 || !httpCbData->headers[i].name.ptr)
|
|
{
|
|
break;
|
|
}
|
|
string name;
|
|
string value;
|
|
name.resize(httpCbData->headers[i].name.len);
|
|
value.resize(httpCbData->headers[i].value.len);
|
|
memcpy((char*)name.c_str(), httpCbData->headers[i].name.ptr, httpCbData->headers[i].name.len);
|
|
memcpy((char*)value.c_str(), httpCbData->headers[i].value.ptr, httpCbData->headers[i].value.len);
|
|
toLowerString(name);
|
|
res->headers[name] = value;
|
|
// std::cout << "show headers, " << name << ": " << value << endl;
|
|
}
|
|
|
|
if (res->headers.find("content-length") != res->headers.end())
|
|
{
|
|
res->totalBodySize = (int64_t)stoll(res->headers["content-length"]);
|
|
}
|
|
else
|
|
{
|
|
Logi("WHttpServer::ParseHttpMsg request id:%ld have no content-length", conn->id);
|
|
res->totalBodySize = httpCbData->body.len;
|
|
}
|
|
|
|
if (chunkFlag)
|
|
{
|
|
res->chunkQueue = shared_ptr<HttpChunkQueue>(new HttpChunkQueue());
|
|
string *chunk = new string();
|
|
chunk->resize(httpCbData->chunk.len);
|
|
memcpy((char*)chunk->c_str(), httpCbData->chunk.ptr, httpCbData->chunk.len);
|
|
conn->recv.len -= httpCbData->chunk.len;
|
|
res->chunkQueue->enQueue(chunk);
|
|
res->recvChunkSize += httpCbData->chunk.len;
|
|
res->finishRecvChunk = (res->recvChunkSize >= res->totalBodySize);
|
|
}
|
|
else
|
|
{
|
|
res->body.resize(httpCbData->body.len);
|
|
memcpy((char*)res->body.c_str(), httpCbData->body.ptr, httpCbData->body.len);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void WHttpServer::enQueueHttpChunk(shared_ptr<HttpReqMsg> httpMsg, mg_http_message *httpCbData)
|
|
{
|
|
string *chunk = new string();
|
|
chunk->resize(httpCbData->chunk.len);
|
|
memcpy((char*)chunk->c_str(), httpCbData->chunk.ptr, httpCbData->chunk.len);
|
|
httpMsg->httpConnection->recv.len -= httpCbData->chunk.len;
|
|
bool res = httpMsg->chunkQueue->enQueue(chunk);
|
|
assert(res);
|
|
if (httpMsg->chunkQueue->size() > CHUNK_QUEUE_SIZE_BOUNDARY)
|
|
{
|
|
usleep(500);
|
|
}
|
|
/*
|
|
while(!httpMsg->chunkQueue->enQueue(chunk))
|
|
{
|
|
usleep(500);
|
|
}
|
|
*/
|
|
httpMsg->recvChunkSize += httpCbData->chunk.len;
|
|
httpMsg->finishRecvChunk = (httpMsg->recvChunkSize >= httpMsg->totalBodySize);
|
|
}
|
|
|
|
void WHttpServer::releaseHttpReqMsg(shared_ptr<HttpReqMsg> httpMsg)
|
|
{
|
|
while (httpMsg->chunkQueue.get() && httpMsg->chunkQueue->size() > 0)
|
|
{
|
|
string *res = nullptr;
|
|
httpMsg->chunkQueue->deQueue(res);
|
|
delete res;
|
|
}
|
|
|
|
while (httpMsg->sendQueue.get() && httpMsg->sendQueue->size() > 0)
|
|
{
|
|
string *res = nullptr;
|
|
httpMsg->sendQueue->deQueue(res);
|
|
delete res;
|
|
}
|
|
}
|
|
|
|
void WHttpServer::toLowerString(string &str)
|
|
{
|
|
for(int i = 0; i < (int)str.size(); i++)
|
|
{
|
|
str[i] = tolower(str[i]);
|
|
}
|
|
}
|
|
|
|
void WHttpServer::toUpperString(string &str)
|
|
{
|
|
for(int i = 0; i < (int)str.size(); i++)
|
|
{
|
|
str[i] = toupper(str[i]);
|
|
}
|
|
}
|
|
|
|
void WHttpServer::recvHttpRequestCallback(mg_connection *conn, int msgType, void *msgData, void *cbData)
|
|
{
|
|
HttpCbMsg *cbMsg = (HttpCbMsg *)cbData;
|
|
cbMsg->httpServer->recvHttpRequest(conn, msgType, msgData, cbData);
|
|
}
|
|
|
|
uint64_t WHttpServer::getSysTickCountInMilliseconds()
|
|
{
|
|
/* timespec time;
|
|
int ret = clock_gettime(CLOCK_MONOTONIC, &time);
|
|
|
|
if (ret != 0)
|
|
{
|
|
printf("get clock error!\n");
|
|
}
|
|
uint64_t result = ((uint64_t)time.tv_sec) * 1000 + ((uint64_t)time.tv_nsec) / 1000000;
|
|
return result;*/
|
|
return 0;
|
|
}
|