Started migrating to trantor. Took lots of core utility classes from drogon aswell. At the moment the project doesn't compiles!

This commit is contained in:
Relintai 2021-02-03 02:18:37 +01:00
parent 5cc22461fd
commit 647694b576
44 changed files with 9399 additions and 19 deletions

View File

@ -138,6 +138,7 @@ env_base.Prepend(CPPPATH=["#libs/trantor/trantor/net/inner"])
env_base.Prepend(CPPPATH=["#libs/trantor/trantor/utils"])
env_base.Prepend(LINKFLAGS=["-lpthread"])
env_base.Append(CXX=["-std=c++17"])
env_base.Append(CXX=["-o3"])
#env_base.Append(CXX=["-g"])
#env_base.Append(CXX=["-g2"])

161
core/HttpFileImpl.cc Normal file
View File

@ -0,0 +1,161 @@
/**
*
* HttpFileImpl.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpFileImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/MultiPart.h>
#include <fstream>
#include <iostream>
using namespace drogon;
int HttpFileImpl::save(const std::string &path) const
{
assert(!path.empty());
if (fileName_ == "")
return -1;
std::string filename;
auto tmpPath = path;
if (path[0] == '/' ||
(path.length() >= 2 && path[0] == '.' && path[1] == '/') ||
(path.length() >= 3 && path[0] == '.' && path[1] == '.' &&
path[2] == '/') ||
path == "." || path == "..")
{
// Absolute or relative path
}
else
{
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
if (uploadPath[uploadPath.length() - 1] == '/')
tmpPath = uploadPath + path;
else
tmpPath = uploadPath + "/" + path;
}
if (utils::createPath(tmpPath) < 0)
return -1;
if (tmpPath[tmpPath.length() - 1] != '/')
{
filename = tmpPath + "/";
filename.append(fileName_.data(), fileName_.length());
}
else
filename = tmpPath.append(fileName_.data(), fileName_.length());
return saveTo(filename);
}
int HttpFileImpl::save() const
{
return save(HttpAppFrameworkImpl::instance().getUploadPath());
}
int HttpFileImpl::saveAs(const std::string &filename) const
{
assert(!filename.empty());
auto pathAndFileName = filename;
if (filename[0] == '/' ||
(filename.length() >= 2 && filename[0] == '.' && filename[1] == '/') ||
(filename.length() >= 3 && filename[0] == '.' && filename[1] == '.' &&
filename[2] == '/'))
{
// Absolute or relative path
}
else
{
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
if (uploadPath[uploadPath.length() - 1] == '/')
pathAndFileName = uploadPath + filename;
else
pathAndFileName = uploadPath + "/" + filename;
}
auto pathPos = pathAndFileName.rfind('/');
if (pathPos != std::string::npos)
{
std::string path = pathAndFileName.substr(0, pathPos);
if (utils::createPath(path) < 0)
return -1;
}
return saveTo(pathAndFileName);
}
int HttpFileImpl::saveTo(const std::string &pathAndFilename) const
{
LOG_TRACE << "save uploaded file:" << pathAndFilename;
std::ofstream file(pathAndFilename, std::ios::binary);
if (file.is_open())
{
file.write(fileContent_.data(), fileContent_.size());
file.close();
return 0;
}
else
{
LOG_ERROR << "save failed!";
return -1;
}
}
std::string HttpFileImpl::getMd5() const
{
return utils::getMd5(fileContent_.data(), fileContent_.size());
}
const std::string &HttpFile::getFileName() const
{
return implPtr_->getFileName();
}
void HttpFile::setFileName(const std::string &filename)
{
implPtr_->setFileName(filename);
}
void HttpFile::setFile(const char *data, size_t length)
{
implPtr_->setFile(data, length);
}
int HttpFile::save() const
{
return implPtr_->save();
}
int HttpFile::save(const std::string &path) const
{
return implPtr_->save(path);
}
int HttpFile::saveAs(const std::string &filename) const
{
return implPtr_->saveAs(filename);
}
size_t HttpFile::fileLength() const noexcept
{
return implPtr_->fileLength();
}
const char *HttpFile::fileData() const noexcept
{
return implPtr_->fileData();
}
std::string HttpFile::getMd5() const
{
return implPtr_->getMd5();
}
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr)
: implPtr_(std::move(implPtr))
{
}

100
core/HttpFileImpl.h Normal file
View File

@ -0,0 +1,100 @@
/**
*
* HttpFileImpl.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/string_view.h>
#include <drogon/HttpRequest.h>
#include <map>
#include <string>
#include <vector>
#include <memory>
namespace drogon
{
class HttpFileImpl
{
public:
/// Return the file name;
const std::string &getFileName() const
{
return fileName_;
};
/// Set the file name, usually called by the MultiPartParser parser.
void setFileName(const std::string &filename)
{
fileName_ = filename;
};
/// Set the contents of the file, usually called by the MultiPartParser
/// parser.
void setFile(const char *data, size_t length)
{
fileContent_ = string_view{data, length};
};
/// Save the file to the file system.
/**
* The folder saving the file is app().getUploadPath().
* The full path is app().getUploadPath()+"/"+this->getFileName()
*/
int save() const;
/// Save the file to @param path
/**
* @param path if the parameter is prefixed with "/", "./" or "../", or is
* "." or "..", the full path is path+"/"+this->getFileName(),
* otherwise the file is saved as
* app().getUploadPath()+"/"+path+"/"+this->getFileName()
*/
int save(const std::string &path) const;
/// Save the file to file system with a new name
/**
* @param filename if the parameter isn't prefixed with "/", "./" or "../",
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
* is saved as the filename
*/
int saveAs(const std::string &filename) const;
/// Return the file length.
size_t fileLength() const noexcept
{
return fileContent_.length();
};
const char *fileData() const noexcept
{
return fileContent_.data();
}
const string_view &fileContent() const noexcept
{
return fileContent_;
}
/// Return the md5 string of the file
std::string getMd5() const;
int saveTo(const std::string &pathAndFilename) const;
void setRequest(const HttpRequestPtr &req)
{
requestPtr_ = req;
}
private:
std::string fileName_;
string_view fileContent_;
HttpRequestPtr requestPtr_;
};
} // namespace drogon

292
core/HttpResponseParser.cc Normal file
View File

@ -0,0 +1,292 @@
/**
*
* HttpResponseParser.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpResponseParser.h"
#include "HttpResponseImpl.h"
#include <iostream>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
using namespace trantor;
using namespace drogon;
void HttpResponseParser::reset()
{
status_ = HttpResponseParseStatus::kExpectResponseLine;
responsePtr_.reset(new HttpResponseImpl);
parseResponseForHeadMethod_ = false;
leftBodyLength_ = 0;
currentChunkLength_ = 0;
}
HttpResponseParser::HttpResponseParser()
: status_(HttpResponseParseStatus::kExpectResponseLine),
responsePtr_(new HttpResponseImpl)
{
}
bool HttpResponseParser::processResponseLine(const char *begin, const char *end)
{
const char *start = begin;
const char *space = std::find(start, end, ' ');
if (space != end)
{
LOG_TRACE << *(space - 1);
if (*(space - 1) == '1')
{
responsePtr_->setVersion(Version::kHttp11);
}
else if (*(space - 1) == '0')
{
responsePtr_->setVersion(Version::kHttp10);
}
else
{
return false;
}
}
start = space + 1;
space = std::find(start, end, ' ');
if (space != end)
{
std::string status_code(start, space - start);
std::string status_message(space + 1, end - space - 1);
LOG_TRACE << status_code << " " << status_message;
auto code = atoi(status_code.c_str());
responsePtr_->setStatusCode(HttpStatusCode(code));
return true;
}
return false;
}
// return false if any error
bool HttpResponseParser::parseResponse(MsgBuffer *buf)
{
bool ok = true;
bool hasMore = true;
while (hasMore)
{
if (status_ == HttpResponseParseStatus::kExpectResponseLine)
{
const char *crlf = buf->findCRLF();
if (crlf)
{
ok = processResponseLine(buf->peek(), crlf);
if (ok)
{
// responsePtr_->setReceiveTime(receiveTime);
buf->retrieveUntil(crlf + 2);
status_ = HttpResponseParseStatus::kExpectHeaders;
}
else
{
hasMore = false;
}
}
else
{
hasMore = false;
}
}
else if (status_ == HttpResponseParseStatus::kExpectHeaders)
{
const char *crlf = buf->findCRLF();
if (crlf)
{
const char *colon = std::find(buf->peek(), crlf, ':');
if (colon != crlf)
{
responsePtr_->addHeader(buf->peek(), colon, crlf);
}
else
{
const std::string &len =
responsePtr_->getHeaderBy("content-length");
// LOG_INFO << "content len=" << len;
if (!len.empty())
{
leftBodyLength_ = atoi(len.c_str());
status_ = HttpResponseParseStatus::kExpectBody;
}
else
{
const std::string &encode =
responsePtr_->getHeaderBy("transfer-encoding");
if (encode == "chunked")
{
status_ = HttpResponseParseStatus::kExpectChunkLen;
hasMore = true;
}
else
{
if (responsePtr_->statusCode() == k204NoContent ||
(responsePtr_->statusCode() ==
k101SwitchingProtocols &&
responsePtr_->getHeaderBy("upgrade") ==
"websocket"))
{
// The Websocket response may not have a
// content-length header.
status_ = HttpResponseParseStatus::kGotAll;
hasMore = false;
}
else
{
status_ = HttpResponseParseStatus::kExpectClose;
hasMore = true;
}
}
}
if (parseResponseForHeadMethod_)
{
leftBodyLength_ = 0;
status_ = HttpResponseParseStatus::kGotAll;
hasMore = false;
}
}
buf->retrieveUntil(crlf + 2);
}
else
{
hasMore = false;
}
}
else if (status_ == HttpResponseParseStatus::kExpectBody)
{
// LOG_INFO << "expectBody:len=" << request_->contentLen;
// LOG_INFO << "expectBody:buf=" << buf;
if (buf->readableBytes() == 0)
{
if (leftBodyLength_ == 0)
{
status_ = HttpResponseParseStatus::kGotAll;
}
break;
}
if (!responsePtr_->bodyPtr_)
{
responsePtr_->bodyPtr_ =
std::make_shared<HttpMessageStringBody>();
}
if (leftBodyLength_ >= buf->readableBytes())
{
leftBodyLength_ -= buf->readableBytes();
responsePtr_->bodyPtr_->append(buf->peek(),
buf->readableBytes());
buf->retrieveAll();
}
else
{
responsePtr_->bodyPtr_->append(buf->peek(), leftBodyLength_);
buf->retrieve(leftBodyLength_);
leftBodyLength_ = 0;
}
if (leftBodyLength_ == 0)
{
status_ = HttpResponseParseStatus::kGotAll;
LOG_TRACE << "post got all:len=" << leftBodyLength_;
// LOG_INFO<<"content:"<<request_->content_;
LOG_TRACE << "content(END)";
hasMore = false;
}
}
else if (status_ == HttpResponseParseStatus::kExpectClose)
{
if (!responsePtr_->bodyPtr_)
{
responsePtr_->bodyPtr_ =
std::make_shared<HttpMessageStringBody>();
}
responsePtr_->bodyPtr_->append(buf->peek(), buf->readableBytes());
buf->retrieveAll();
break;
}
else if (status_ == HttpResponseParseStatus::kExpectChunkLen)
{
const char *crlf = buf->findCRLF();
if (crlf)
{
// chunk length line
std::string len(buf->peek(), crlf - buf->peek());
char *end;
currentChunkLength_ = strtol(len.c_str(), &end, 16);
// LOG_TRACE << "chun length : " <<
// currentChunkLength_;
if (currentChunkLength_ != 0)
{
status_ = HttpResponseParseStatus::kExpectChunkBody;
}
else
{
status_ = HttpResponseParseStatus::kExpectLastEmptyChunk;
}
buf->retrieveUntil(crlf + 2);
}
else
{
hasMore = false;
}
}
else if (status_ == HttpResponseParseStatus::kExpectChunkBody)
{
// LOG_TRACE<<"expect chunk
// len="<<currentChunkLength_;
if (buf->readableBytes() >= (currentChunkLength_ + 2))
{
if (*(buf->peek() + currentChunkLength_) == '\r' &&
*(buf->peek() + currentChunkLength_ + 1) == '\n')
{
if (!responsePtr_->bodyPtr_)
{
responsePtr_->bodyPtr_ =
std::make_shared<HttpMessageStringBody>();
}
responsePtr_->bodyPtr_->append(buf->peek(),
currentChunkLength_);
buf->retrieve(currentChunkLength_ + 2);
currentChunkLength_ = 0;
status_ = HttpResponseParseStatus::kExpectChunkLen;
}
else
{
// error!
buf->retrieveAll();
return false;
}
}
else
{
hasMore = false;
}
}
else if (status_ == HttpResponseParseStatus::kExpectLastEmptyChunk)
{
// last empty chunk
const char *crlf = buf->findCRLF();
if (crlf)
{
buf->retrieveUntil(crlf + 2);
status_ = HttpResponseParseStatus::kGotAll;
break;
}
else
{
hasMore = false;
}
}
}
return ok;
}

75
core/HttpResponseParser.h Normal file
View File

@ -0,0 +1,75 @@
/**
*
* HttpResponseParser.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "impl_forwards.h"
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/TcpConnection.h>
#include <trantor/utils/MsgBuffer.h>
#include <list>
#include <mutex>
namespace drogon
{
class HttpResponseParser : public trantor::NonCopyable
{
public:
enum class HttpResponseParseStatus
{
kExpectResponseLine,
kExpectHeaders,
kExpectBody,
kExpectChunkLen,
kExpectChunkBody,
kExpectLastEmptyChunk,
kExpectClose,
kGotAll,
};
HttpResponseParser();
// default copy-ctor, dtor and assignment are fine
// return false if any error
bool parseResponse(trantor::MsgBuffer *buf);
bool gotAll() const
{
return status_ == HttpResponseParseStatus::kGotAll;
}
void setForHeadMethod()
{
parseResponseForHeadMethod_ = true;
}
void reset();
const HttpResponseImplPtr &responseImpl() const
{
return responsePtr_;
}
private:
bool processResponseLine(const char *begin, const char *end);
HttpResponseParseStatus status_;
HttpResponseImplPtr responsePtr_;
bool parseResponseForHeadMethod_{false};
size_t leftBodyLength_{0};
size_t currentChunkLength_{0};
};
} // namespace drogon

81
core/SessionManager.cc Normal file
View File

@ -0,0 +1,81 @@
/**
*
* SessionManager.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "SessionManager.h"
#include <drogon/utils/Utilities.h>
using namespace drogon;
SessionManager::SessionManager(trantor::EventLoop *loop, size_t timeout)
: loop_(loop), timeout_(timeout)
{
assert(timeout_ >= 0);
if (timeout_ > 0)
{
size_t wheelNum = 1;
size_t bucketNum = 0;
if (timeout_ < 500)
{
bucketNum = timeout_ + 1;
}
else
{
auto tmpTimeout = timeout_;
bucketNum = 100;
while (tmpTimeout > 100)
{
++wheelNum;
tmpTimeout = tmpTimeout / 100;
}
}
sessionMapPtr_ = std::unique_ptr<CacheMap<std::string, SessionPtr>>(
new CacheMap<std::string, SessionPtr>(
loop_, 1.0, wheelNum, bucketNum));
}
else if (timeout_ == 0)
{
sessionMapPtr_ = std::unique_ptr<CacheMap<std::string, SessionPtr>>(
new CacheMap<std::string, SessionPtr>(loop_, 0, 0, 0));
}
}
SessionPtr SessionManager::getSession(const std::string &sessionID,
bool needToSet)
{
assert(!sessionID.empty());
SessionPtr sessionPtr;
std::lock_guard<std::mutex> lock(mapMutex_);
if (sessionMapPtr_->findAndFetch(sessionID, sessionPtr) == false)
{
sessionPtr =
std::shared_ptr<Session>(new Session(sessionID, needToSet));
sessionMapPtr_->insert(sessionID, sessionPtr, timeout_);
return sessionPtr;
}
return sessionPtr;
}
void SessionManager::changeSessionId(const SessionPtr &sessionPtr)
{
auto oldId = sessionPtr->sessionId();
auto newId = utils::getUuid();
sessionPtr->setSessionId(newId);
sessionMapPtr_->insert(newId, sessionPtr, timeout_);
// For requests sent before setting the new session ID to the client, we
// reserve the old session slot for a period of time.
sessionMapPtr_->runAfter(10, [this, oldId = std::move(oldId)]() {
LOG_TRACE << "remove the old slot of the session";
sessionMapPtr_->erase(oldId);
});
}

44
core/SessionManager.h Normal file
View File

@ -0,0 +1,44 @@
/**
*
* SessionManager.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/Session.h>
#include <drogon/CacheMap.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <memory>
#include <string>
#include <mutex>
namespace drogon
{
class SessionManager : public trantor::NonCopyable
{
public:
SessionManager(trantor::EventLoop *loop, size_t timeout);
~SessionManager()
{
sessionMapPtr_.reset();
}
SessionPtr getSession(const std::string &sessionID, bool needToSet);
void changeSessionId(const SessionPtr &sessionPtr);
private:
std::unique_ptr<CacheMap<std::string, SessionPtr>> sessionMapPtr_;
std::mutex mapMutex_;
trantor::EventLoop *loop_;
size_t timeout_;
};
} // namespace drogon

31
core/any.h Normal file
View File

@ -0,0 +1,31 @@
/**
*
* any.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900)
#include <any>
#else
#include <boost/any.hpp>
#endif
namespace drogon
{
#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900)
using std::any;
using std::any_cast;
#else
using boost::any;
using boost::any_cast;
#endif
} // namespace drogon

View File

@ -12,6 +12,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <trantor/utils/Logger.h>
#include "core/listener_manager.h"
using namespace std::placeholders;
void Application::load_settings() {
}
@ -119,8 +125,308 @@ void Application::send_file(const std::string &path, Request *request) {
void Application::migrate() {
}
//drogon -> void HttpAppFrameworkImpl::run()
void Application::run() {
if (!get_loop()->isInLoopThread()) {
get_loop()->moveToCurrentThread();
}
LOG_TRACE << "Start to run...";
/*
trantor::AsyncFileLogger asyncFileLogger;
// Create dirs for cache files
for (int i = 0; i < 256; ++i) {
char dirName[4];
snprintf(dirName, sizeof(dirName), "%02x", i);
std::transform(dirName, dirName + 2, dirName, toupper);
utils::createPath(getUploadPath() + "/tmp/" + dirName);
}
*/
/*
if (runAsDaemon_) {
// go daemon!
godaemon();
#ifdef __linux__
get_loop()->resetTimerQueue();
#endif
get_loop()->resetAfterFork();
}
*/
/*
// set relaunching
if (relaunchOnError_) {
#ifndef _WIN32
while (true) {
int child_status = 0;
auto child_pid = fork();
if (child_pid < 0) {
LOG_ERROR << "fork error";
abort();
} else if (child_pid == 0) {
// child
break;
}
waitpid(child_pid, &child_status, 0);
sleep(1);
LOG_INFO << "start new process";
}
get_loop()->resetAfterFork();
#endif
}*/
//signal(SIGTERM, TERMFunction);
/*
// set logger
if (!logPath_.empty()) {
#ifdef _WIN32
if (_access(logPath_.c_str(), 06) != 0)
#else
if (access(logPath_.c_str(), R_OK | W_OK) != 0)
#endif
{
LOG_ERROR << "log file path not exist";
abort();
} else {
std::string baseName = logfileBaseName_;
if (baseName == "") {
baseName = "drogon";
}
asyncFileLogger.setFileName(baseName, ".log", logPath_);
asyncFileLogger.startLogging();
trantor::Logger::setOutputFunction(
[&](const char *msg, const uint64_t len) {
asyncFileLogger.output(msg, len);
},
[&]() { asyncFileLogger.flush(); });
asyncFileLogger.setFileSizeLimit(logfileSize_);
}
}
*/
/*
if (relaunchOnError_) {
LOG_INFO << "Start child process";
}
*/
// now start runing!!
_running = true;
/*
#ifndef _WIN32
if (!libFilePaths_.empty()) {
sharedLibManagerPtr_ = std::unique_ptr<SharedLibManager>(
new SharedLibManager(libFilePaths_, libFileOutputPath_));
}
#endif
*/
// Create all listeners.
/*
auto ioLoops = listenerManagerPtr_->createListeners(
std::bind(&Application::onAsyncRequest, this, _1, _2),
std::bind(&Application::onNewWebsockRequest, this, _1, _2, _3),
std::bind(&Application::onConnection, this, _1),
idleConnectionTimeout_,
sslCertPath_,
sslKeyPath_,
threadNum_,
syncAdvices_);
assert(ioLoops.size() == threadNum_);
for (size_t i = 0; i < threadNum_; ++i) {
ioLoops[i]->setIndex(i);
}
get_loop()->setIndex(threadNum_);
*/
// A fast database client instance should be created in the main event
// loop, so put the main loop into ioLoops.
ioLoops.push_back(get_loop());
/*
dbClientManagerPtr_->createDbClients(ioLoops);
httpCtrlsRouterPtr_->init(ioLoops);
httpSimpleCtrlsRouterPtr_->init(ioLoops);
staticFileRouterPtr_->init(ioLoops);
websockCtrlsRouterPtr_->init();
*/
/*
get_loop()->queueInLoop([this]() {
// Let listener event loops run when everything is ready.
listenerManagerPtr_->startListening();
for (auto &adv : beginningAdvices_) {
adv();
}
beginningAdvices_.clear();
});
*/
get_loop()->loop();
}
trantor::EventLoop *Application::get_loop() const {
static trantor::EventLoop loop;
return &loop;
}
void Application::onAsyncRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback) {
/*
LOG_TRACE << "new request:" << req->peerAddr().toIpPort() << "->"
<< req->localAddr().toIpPort();
LOG_TRACE << "Headers " << req->methodString() << " " << req->path();
LOG_TRACE << "http path=" << req->path();
if (req->method() == Options && (req->path() == "*" || req->path() == "/*")) {
auto resp = HttpResponse::newHttpResponse();
resp->setContentTypeCode(ContentType::CT_TEXT_PLAIN);
resp->addHeader("ALLOW", "GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH");
resp->setExpiredTime(0);
callback(resp);
return;
}
findSessionForRequest(req);
// Route to controller
if (!preRoutingObservers_.empty()) {
for (auto &observer : preRoutingObservers_) {
observer(req);
}
}
if (preRoutingAdvices_.empty()) {
httpSimpleCtrlsRouterPtr_->route(req, std::move(callback));
} else {
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)> >(
std::move(callback));
doAdvicesChain(
preRoutingAdvices_,
0,
req,
std::make_shared<std::function<void(const HttpResponsePtr &)> >(
[req, callbackPtr, this](const HttpResponsePtr &resp) {
callCallback(req, resp, *callbackPtr);
}),
[this, callbackPtr, req]() {
httpSimpleCtrlsRouterPtr_->route(req, std::move(*callbackPtr));
});
}*/
}
void Application::onNewWebsockRequest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const WebSocketConnectionPtr &wsConnPtr) {
/*
findSessionForRequest(req);
// Route to controller
if (!preRoutingObservers_.empty()) {
for (auto &observer : preRoutingObservers_) {
observer(req);
}
}
if (preRoutingAdvices_.empty()) {
websockCtrlsRouterPtr_->route(req, std::move(callback), wsConnPtr);
} else {
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)> >(
std::move(callback));
doAdvicesChain(
preRoutingAdvices_,
0,
req,
std::make_shared<std::function<void(const HttpResponsePtr &)> >(
[req, callbackPtr, this](const HttpResponsePtr &resp) {
callCallback(req, resp, *callbackPtr);
}),
[this, callbackPtr, req, wsConnPtr]() {
websockCtrlsRouterPtr_->route(req,
std::move(*callbackPtr),
wsConnPtr);
});
}*/
}
void Application::onConnection(const trantor::TcpConnectionPtr &conn)
{
/*
static std::mutex mtx;
LOG_TRACE << "connect!!!" << maxConnectionNum_
<< " num=" << connectionNum_.load();
if (conn->connected())
{
if (connectionNum_.fetch_add(1, std::memory_order_relaxed) >=
maxConnectionNum_)
{
LOG_ERROR << "too much connections!force close!";
conn->forceClose();
return;
}
else if (maxConnectionNumPerIP_ > 0)
{
{
std::lock_guard<std::mutex> lock(mtx);
auto iter = connectionsNumMap_.find(conn->peerAddr().toIp());
if (iter == connectionsNumMap_.end())
{
connectionsNumMap_[conn->peerAddr().toIp()] = 1;
}
else if (iter->second++ > maxConnectionNumPerIP_)
{
conn->getLoop()->queueInLoop(
[conn]() { conn->forceClose(); });
return;
}
}
}
for (auto &advice : newConnectionAdvices_)
{
if (!advice(conn->peerAddr(), conn->localAddr()))
{
conn->forceClose();
return;
}
}
}
else
{
if (!conn->hasContext())
{
// If the connection is connected to the SSL port and then
// disconnected before the SSL handshake.
return;
}
connectionNum_.fetch_sub(1, std::memory_order_relaxed);
if (maxConnectionNumPerIP_ > 0)
{
std::lock_guard<std::mutex> lock(mtx);
auto iter = connectionsNumMap_.find(conn->peerAddr().toIp());
if (iter != connectionsNumMap_.end())
{
--iter->second;
if (iter->second <= 0)
{
connectionsNumMap_.erase(iter);
}
}
}
}
*/
}
Application::Application() {
_instance = this;
threadNum_ = 1;
idleConnectionTimeout_ = 60;
}
Application::~Application() {

View File

@ -7,9 +7,16 @@
#include <string>
#include <vector>
#include <trantor/net/TcpServer.h>
#include <trantor/net/callbacks.h>
#include <trantor/utils/NonCopyable.h>
#include "handler_instance.h"
#include "core/http_server_callbacks.h"
class Request;
class ListenerManager;
class Application {
public:
@ -31,11 +38,22 @@ public:
virtual void migrate();
virtual void run();
trantor::EventLoop *get_loop() const;
void onAsyncRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback);
void onNewWebsockRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const WebSocketConnectionPtr &wsConnPtr);
void onConnection(const trantor::TcpConnectionPtr &conn);
Application();
virtual ~Application();
static Application *get_instance();
std::unique_ptr<ListenerManager> listenerManagerPtr_;
public:
static HandlerInstance index_func;
static std::map<std::string, HandlerInstance> main_route_map;
@ -44,8 +62,15 @@ public:
static std::map<int, std::function<void(int, Request *)> > error_handler_map;
static std::function<void(int, Request *)> default_error_handler_func;
size_t idleConnectionTimeout_;
size_t threadNum_;
std::string sslCertPath_;
std::string sslKeyPath_;
std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)> > syncAdvices_;
private:
static Application *_instance;
bool _running;
};
#endif

130
core/attribute.h Normal file
View File

@ -0,0 +1,130 @@
/**
*
* Attribute.h
* armstrong@sweelia.com
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/any.h>
#include <trantor/utils/Logger.h>
#include <map>
#include <memory>
namespace drogon
{
/**
* @brief This class represents the attributes stored in the HTTP request.
* One can add/get any type of data to/from an Attributes object.
*/
class Attributes
{
public:
/**
* @brief Get the data identified by the key parameter.
* @note if the data is not found, a default value is returned.
* For example:
* @code
auto &userName = attributesPtr->get<std::string>("user name");
@endcode
*/
template <typename T>
const T &get(const std::string &key) const
{
const static T nullVal = T();
auto it = attributesMap_.find(key);
if (it != attributesMap_.end())
{
if (typeid(T) == it->second.type())
{
return *(any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
return nullVal;
}
/**
* @brief Get the 'any' object identified by the given key
*/
any &operator[](const std::string &key)
{
return attributesMap_[key];
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
attributesPtr->insert("user name", userNameString);
@endcode
*/
void insert(const std::string &key, const any &obj)
{
attributesMap_[key] = obj;
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
attributesPtr->insert("user name", userNameString);
@endcode
*/
void insert(const std::string &key, any &&obj)
{
attributesMap_[key] = std::move(obj);
}
/**
* @brief Erase the data identified by the given key.
*/
void erase(const std::string &key)
{
attributesMap_.erase(key);
}
/**
* @brief Retrun true if the data identified by the key exists.
*/
bool find(const std::string &key)
{
if (attributesMap_.find(key) == attributesMap_.end())
{
return false;
}
return true;
}
/**
* @brief Clear all attributes.
*/
void clear()
{
attributesMap_.clear();
}
/**
* @brief Constructor, usually called by the framework
*/
Attributes() = default;
private:
using AttributesMap = std::map<std::string, any>;
AttributesMap attributesMap_;
};
using AttributesPtr = std::shared_ptr<Attributes>;
} // namespace drogon

95
core/cache_file.cpp Normal file
View File

@ -0,0 +1,95 @@
/**
*
* CacheFile.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "cache_file.h"
#include <trantor/utils/Logger.h>
#ifdef _WIN32
#include <mman.h>
#else
#include <unistd.h>
#include <sys/mman.h>
#endif
using namespace drogon;
CacheFile::CacheFile(const std::string &path, bool autoDelete)
: autoDelete_(autoDelete), path_(path)
{
#ifndef _MSC_VER
file_ = fopen(path_.data(), "wb+");
#else
if (fopen_s(&file_, path_.data(), "wb+") != 0)
{
file_ = nullptr;
}
#endif
}
CacheFile::~CacheFile()
{
if (data_)
{
munmap(data_, dataLength_);
}
if (autoDelete_ && file_)
{
fclose(file_);
#ifdef _WIN32
_unlink(path_.data());
#else
unlink(path_.data());
#endif
}
else if (file_)
{
fclose(file_);
}
}
void CacheFile::append(const char *data, size_t length)
{
if (file_)
fwrite(data, length, 1, file_);
}
size_t CacheFile::length()
{
if (file_)
return ftell(file_);
return 0;
}
char *CacheFile::data()
{
if (!file_)
return nullptr;
if (!data_)
{
fflush(file_);
#ifdef _WIN32
auto fd = _fileno(file_);
#else
auto fd = fileno(file_);
#endif
dataLength_ = length();
data_ = static_cast<char *>(
mmap(nullptr, dataLength_, PROT_READ, MAP_SHARED, fd, 0));
if (data_ == MAP_FAILED)
{
data_ = nullptr;
LOG_SYSERR << "mmap:";
}
}
return data_;
}

50
core/cache_file.h Normal file
View File

@ -0,0 +1,50 @@
/**
*
* CacheFile.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/string_view.h>
#include <trantor/utils/NonCopyable.h>
#include <string>
#include <stdio.h>
namespace drogon
{
class CacheFile : public trantor::NonCopyable
{
public:
explicit CacheFile(const std::string &path, bool autoDelete = true);
~CacheFile();
void append(const std::string &data)
{
append(data.data(), data.length());
}
void append(const char *data, size_t length);
string_view getStringView()
{
if (data())
return string_view(data_, dataLength_);
return string_view();
}
private:
char *data();
size_t length();
FILE *file_{nullptr};
bool autoDelete_{true};
const std::string path_;
char *data_{nullptr};
size_t dataLength_{0};
};
} // namespace drogon

50
core/cookie.cpp Normal file
View File

@ -0,0 +1,50 @@
/**
*
* Cookie.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <core/cookie.h>
#include <core/utilities.h>
using namespace drogon;
std::string Cookie::cookieString() const
{
std::string ret = "Set-Cookie: ";
ret.append(key_).append("= ").append(value_).append("; ");
if (expiresDate_.microSecondsSinceEpoch() !=
(std::numeric_limits<int64_t>::max)() &&
expiresDate_.microSecondsSinceEpoch() >= 0)
{
ret.append("Expires= ")
.append(utils::getHttpFullDate(expiresDate_))
.append("; ");
}
if (!domain_.empty())
{
ret.append("Domain= ").append(domain_).append("; ");
}
if (!path_.empty())
{
ret.append("Path= ").append(path_).append("; ");
}
if (secure_)
{
ret.append("Secure; ");
}
if (httpOnly_)
{
ret.append("HttpOnly; ");
}
ret.resize(ret.length() - 2); // delete last semicolon
ret.append("\r\n");
return ret;
}

252
core/cookie.h Normal file
View File

@ -0,0 +1,252 @@
/**
*
* Cookis.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Date.h>
#include <string>
#include <limits>
namespace drogon
{
/**
* @brief this class represents a cookie entity.
*/
class Cookie
{
public:
/// Constructor
/**
* @param key key of the cookie
* @param value value of the cookie
*/
Cookie(const std::string &key, const std::string &value)
: key_(key), value_(value)
{
}
Cookie(std::string &&key, std::string &&value)
: key_(std::move(key)), value_(std::move(value))
{
}
Cookie() = default;
/**
* @brief Set the Expires Date
*
* @param date The expiration date
*/
void setExpiresDate(const trantor::Date &date)
{
expiresDate_ = date;
}
/**
* @brief Set if the cookie is HTTP only.
*/
void setHttpOnly(bool only)
{
httpOnly_ = only;
}
/**
* @brief Set if the cookie is secure.
*/
void setSecure(bool secure)
{
secure_ = secure;
}
/**
* @brief Set the domain of the cookie.
*/
void setDomain(const std::string &domain)
{
domain_ = domain;
}
void setDomain(std::string &&domain)
{
domain_ = std::move(domain);
}
/**
* @brief Set the path of the cookie.
*/
void setPath(const std::string &path)
{
path_ = path;
}
void setPath(std::string &&path)
{
path_ = std::move(path);
}
/**
* @brief Set the key of the cookie.
*/
void setKey(const std::string &key)
{
key_ = key;
}
void setKey(std::string &&key)
{
key_ = std::move(key);
}
/**
* @brief Set the value of the cookie.
*/
void setValue(const std::string &value)
{
value_ = value;
}
void setValue(std::string &&value)
{
value_ = std::move(value);
}
/**
* @brief Get the string value of the cookie
*/
std::string cookieString() const;
/**
* @brief Get the string value of the cookie
*/
std::string getCookieString() const
{
return cookieString();
}
/**
* @brief Get the expiration date of the cookie
*/
const trantor::Date &expiresDate() const
{
return expiresDate_;
}
/**
* @brief Get the expiration date of the cookie
*/
const trantor::Date &getExpiresDate() const
{
return expiresDate_;
}
/**
* @brief Get the domain of the cookie
*/
const std::string &domain() const
{
return domain_;
}
/**
* @brief Get the domain of the cookie
*/
const std::string &getDomain() const
{
return domain_;
}
/**
* @brief Get the path of the cookie
*/
const std::string &path() const
{
return path_;
}
/**
* @brief Get the path of the cookie
*/
const std::string &getPath() const
{
return path_;
}
/**
* @brief Get the keyword of the cookie
*/
const std::string &key() const
{
return key_;
}
/**
* @brief Get the keyword of the cookie
*/
const std::string &getKey() const
{
return key_;
}
/**
* @brief Get the value of the cookie
*/
const std::string &value() const
{
return value_;
}
/**
* @brief Get the value of the cookie
*/
const std::string &getValue() const
{
return value_;
}
/**
* @brief Check if the cookie is empty
*
* @return true means the cookie is not empty
* @return false means the cookie is empty
*/
operator bool() const
{
return (!key_.empty()) && (!value_.empty());
}
/**
* @brief Check if the cookie is HTTP only
*
* @return true means the cookie is HTTP only
* @return false means the cookie is not HTTP only
*/
bool isHttpOnly() const
{
return httpOnly_;
}
/**
* @brief Check if the cookie is secure.
*
* @return true means the cookie is secure.
* @return false means the cookie is not secure.
*/
bool isSecure() const
{
return secure_;
}
private:
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
bool httpOnly_{true};
bool secure_{false};
std::string domain_;
std::string path_;
std::string key_;
std::string value_;
};
} // namespace drogon

View File

@ -0,0 +1,32 @@
/**
*
* HttpFileUploadRequest.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "http_file_upload_request.h"
#include <core/upload_file.h>
#include <core/utilities.h>
using namespace drogon;
HttpFileUploadRequest::HttpFileUploadRequest(
const std::vector<UploadFile> &files)
: HttpRequest(nullptr),
boundary_(utils::genRandomString(32)),
files_(files)
{
setMethod(drogon::Post);
setVersion(drogon::Version::kHttp11);
setContentType("Content-Type: multipart/form-data; boundary=" + boundary_ +
"\r\n");
contentType_ = CT_MULTIPART_FORM_DATA;
}

View File

@ -0,0 +1,40 @@
/**
*
* HttpFileUploadRequest.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "core/http_request.h"
#include <string>
#include <vector>
namespace drogon
{
class HttpFileUploadRequest : public HttpRequest
{
public:
const std::string &boundary() const
{
return boundary_;
}
const std::vector<UploadFile> &files() const
{
return files_;
}
explicit HttpFileUploadRequest(const std::vector<UploadFile> &files);
private:
std::string boundary_;
std::vector<UploadFile> files_;
};
} // namespace drogon

159
core/http_message_body.h Normal file
View File

@ -0,0 +1,159 @@
/**
*
* HttpMessageBody.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "core/string_view.h"
#include <string>
namespace drogon
{
class HttpMessageBody
{
public:
enum class BodyType
{
kNone = 0,
kString,
kStringView
};
BodyType bodyType()
{
return type_;
}
virtual const char *data() const
{
return nullptr;
}
virtual char *data()
{
return nullptr;
}
virtual size_t length() const
{
return 0;
}
virtual const std::string &getString() const = 0;
virtual std::string &getString() = 0;
virtual void append(const char *buf, size_t len)
{
}
virtual ~HttpMessageBody()
{
}
protected:
BodyType type_{BodyType::kNone};
};
class HttpMessageStringBody : public HttpMessageBody
{
public:
HttpMessageStringBody()
{
type_ = BodyType::kString;
}
HttpMessageStringBody(const std::string &body) : body_(body)
{
type_ = BodyType::kString;
}
HttpMessageStringBody(std::string &&body) : body_(std::move(body))
{
type_ = BodyType::kString;
}
virtual const char *data() const override
{
return body_.data();
}
virtual char *data() override
{
return const_cast<char *>(body_.data());
}
virtual size_t length() const override
{
return body_.length();
}
virtual const std::string &getString() const override
{
return body_;
}
virtual std::string &getString() override
{
return body_;
}
virtual void append(const char *buf, size_t len) override
{
body_.append(buf, len);
}
private:
std::string body_;
};
class HttpMessageStringViewBody : public HttpMessageBody
{
public:
HttpMessageStringViewBody(const char *buf, size_t len) : body_(buf, len)
{
type_ = BodyType::kStringView;
}
virtual const char *data() const override
{
return body_.data();
}
virtual char *data() override
{
return const_cast<char *>(body_.data());
}
virtual size_t length() const override
{
return body_.length();
}
virtual const std::string &getString() const override
{
if (!bodyString_)
{
if (!body_.empty())
{
bodyString_ =
std::make_unique<std::string>(body_.data(), body_.length());
}
else
{
bodyString_ = std::make_unique<std::string>();
}
}
return *bodyString_;
}
virtual std::string &getString() override
{
if (!bodyString_)
{
if (!body_.empty())
{
bodyString_ =
std::make_unique<std::string>(body_.data(), body_.length());
}
else
{
bodyString_ = std::make_unique<std::string>();
}
}
return *bodyString_;
}
private:
string_view body_;
mutable std::unique_ptr<std::string> bodyString_;
};
} // namespace drogon

699
core/http_request.cpp Normal file
View File

@ -0,0 +1,699 @@
/**
*
* @file HttpRequest.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "http_request.h"
#include "http_file_upload_request.h"
//#include "HttpAppFrameworkImpl.h"
#include <core/utilities.h>
#include "core/application.h"
#include <fstream>
#include <iostream>
#ifndef _WIN32
#include <unistd.h>
#endif
using namespace drogon;
void HttpRequest::parseJson() const
{
auto input = contentView();
if (input.empty())
return;
if (contentType_ == CT_APPLICATION_JSON ||
getHeaderBy("content-type").find("application/json") !=
std::string::npos)
{
static std::once_flag once;
static Json::CharReaderBuilder builder;
std::call_once(once, []() { builder["collectComments"] = false; });
jsonPtr_ = std::make_shared<Json::Value>();
JSONCPP_STRING errs;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (!reader->parse(input.data(),
input.data() + input.size(),
jsonPtr_.get(),
&errs))
{
LOG_ERROR << errs;
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_unique<std::string>(std::move(errs));
}
else
{
jsonParsingErrorPtr_.reset();
}
}
else
{
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_unique<std::string>("content type error");
}
}
void HttpRequest::parseParameters() const
{
auto input = queryView();
if (!input.empty())
{
string_view::size_type pos = 0;
while ((input[pos] == '?' || isspace(input[pos])) &&
pos < input.length())
{
++pos;
}
auto value = input.substr(pos);
while ((pos = value.find('&')) != string_view::npos)
{
auto coo = value.substr(0, pos);
auto epos = coo.find('=');
if (epos != string_view::npos)
{
auto key = coo.substr(0, epos);
string_view::size_type cpos = 0;
while (cpos < key.length() && isspace(key[cpos]))
++cpos;
key = key.substr(cpos);
auto pvalue = coo.substr(epos + 1);
std::string pdecode = utils::urlDecode(pvalue);
std::string keydecode = utils::urlDecode(key);
parameters_[keydecode] = pdecode;
}
value = value.substr(pos + 1);
}
if (value.length() > 0)
{
auto &coo = value;
auto epos = coo.find('=');
if (epos != string_view::npos)
{
auto key = coo.substr(0, epos);
string_view::size_type cpos = 0;
while (cpos < key.length() && isspace(key[cpos]))
++cpos;
key = key.substr(cpos);
auto pvalue = coo.substr(epos + 1);
std::string pdecode = utils::urlDecode(pvalue);
std::string keydecode = utils::urlDecode(key);
parameters_[keydecode] = pdecode;
}
}
}
input = contentView();
if (input.empty())
return;
std::string type = getHeaderBy("content-type");
std::transform(type.begin(), type.end(), type.begin(), tolower);
if (type.empty() ||
type.find("application/x-www-form-urlencoded") != std::string::npos)
{
string_view::size_type pos = 0;
while ((input[pos] == '?' || isspace(input[pos])) &&
pos < input.length())
{
++pos;
}
auto value = input.substr(pos);
while ((pos = value.find('&')) != string_view::npos)
{
auto coo = value.substr(0, pos);
auto epos = coo.find('=');
if (epos != string_view::npos)
{
auto key = coo.substr(0, epos);
string_view::size_type cpos = 0;
while (cpos < key.length() && isspace(key[cpos]))
++cpos;
key = key.substr(cpos);
auto pvalue = coo.substr(epos + 1);
std::string pdecode = utils::urlDecode(pvalue);
std::string keydecode = utils::urlDecode(key);
parameters_[keydecode] = pdecode;
}
value = value.substr(pos + 1);
}
if (value.length() > 0)
{
auto &coo = value;
auto epos = coo.find('=');
if (epos != string_view::npos)
{
auto key = coo.substr(0, epos);
string_view::size_type cpos = 0;
while (cpos < key.length() && isspace(key[cpos]))
++cpos;
key = key.substr(cpos);
auto pvalue = coo.substr(epos + 1);
std::string pdecode = utils::urlDecode(pvalue);
std::string keydecode = utils::urlDecode(key);
parameters_[keydecode] = pdecode;
}
}
}
}
void HttpRequest::appendToBuffer(trantor::MsgBuffer *output) const
{
switch (method_)
{
case Get:
output->append("GET ");
break;
case Post:
output->append("POST ");
break;
case Head:
output->append("HEAD ");
break;
case Put:
output->append("PUT ");
break;
case Delete:
output->append("DELETE ");
break;
case Options:
output->append("OPTIONS ");
break;
case Patch:
output->append("PATCH ");
break;
default:
return;
}
if (!path_.empty())
{
output->append(utils::urlEncode(path_));
}
else
{
output->append("/");
}
std::string content;
if (!passThrough_ &&
(!parameters_.empty() && contentType_ != CT_MULTIPART_FORM_DATA))
{
for (auto const &p : parameters_)
{
content.append(utils::urlEncodeComponent(p.first));
content.append("=");
content.append(utils::urlEncodeComponent(p.second));
content.append("&");
}
content.resize(content.length() - 1);
if (method_ == Get || method_ == Delete || method_ == Head)
{
auto ret = std::find(output->peek(),
(const char *)output->beginWrite(),
'?');
if (ret != output->beginWrite())
{
if (ret != output->beginWrite() - 1)
{
output->append("&");
}
}
else
{
output->append("?");
}
output->append(content);
content.clear();
}
else if (contentType_ == CT_APPLICATION_JSON)
{
/// Can't set parameters in content in this case
LOG_ERROR
<< "You can't set parameters in the query string when the "
"request content type is JSON and http method "
"is POST or PUT";
LOG_ERROR << "Please put these parameters into the path or "
"into the json "
"string";
content.clear();
}
}
output->append(" ");
if (version_ == Version::kHttp11)
{
output->append("HTTP/1.1");
}
else if (version_ == Version::kHttp10)
{
output->append("HTTP/1.0");
}
else
{
return;
}
output->append("\r\n");
if (!passThrough_ && contentType_ == CT_MULTIPART_FORM_DATA)
{
auto mReq = dynamic_cast<const HttpFileUploadRequest *>(this);
if (mReq)
{
for (auto &param : mReq->getParameters())
{
content.append("--");
content.append(mReq->boundary());
content.append("\r\n");
content.append("Content-Disposition: form-data; name=\"");
content.append(param.first);
content.append("\"\r\n\r\n");
content.append(param.second);
content.append("\r\n");
}
for (auto &file : mReq->files())
{
content.append("--");
content.append(mReq->boundary());
content.append("\r\n");
content.append("Content-Disposition: form-data; name=\"");
content.append(file.itemName());
content.append("\"; filename=\"");
content.append(file.fileName());
content.append("\"\r\n\r\n");
std::ifstream infile(file.path(), std::ifstream::binary);
if (!infile)
{
LOG_ERROR << file.path() << " not found";
}
else
{
std::streambuf *pbuf = infile.rdbuf();
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
pbuf->pubseekoff(0, infile.beg); // rewind
std::string str;
str.resize(filesize);
pbuf->sgetn(&str[0], filesize);
content.append(std::move(str));
}
content.append("\r\n");
}
content.append("--");
content.append(mReq->boundary());
content.append("--");
}
}
assert(!(!content.empty() && !content_.empty()));
if (!passThrough_ && (!content.empty() || !content_.empty()))
{
char buf[64];
auto len = snprintf(buf,
sizeof(buf),
contentLengthFormatString<decltype(content.length())>(),
content.length() + content_.length());
output->append(buf, len);
if (contentTypeString_.empty())
{
auto &type = webContentTypeToString(contentType_);
output->append(type.data(), type.length());
}
}
if (!passThrough_ && !contentTypeString_.empty())
{
output->append(contentTypeString_);
}
for (auto it = headers_.begin(); it != headers_.end(); ++it)
{
output->append(it->first);
output->append(": ");
output->append(it->second);
output->append("\r\n");
}
if (cookies_.size() > 0)
{
output->append("Cookie: ");
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
output->append(it->first);
output->append("= ");
output->append(it->second);
output->append(";");
}
output->unwrite(1); // delete last ';'
output->append("\r\n");
}
output->append("\r\n");
if (!content.empty())
output->append(content);
if (!content_.empty())
output->append(content_);
}
void HttpRequest::addHeader(const char *start,
const char *colon,
const char *end)
{
std::string field(start, colon);
// Field name is case-insensitive.so we transform it to lower;(rfc2616-4.2)
std::transform(field.begin(), field.end(), field.begin(), ::tolower);
++colon;
while (colon < end && isspace(*colon))
{
++colon;
}
std::string value(colon, end);
while (!value.empty() && isspace(value[value.size() - 1]))
{
value.resize(value.size() - 1);
}
if (field.length() == 6 && field == "cookie")
{
LOG_TRACE << "cookies!!!:" << value;
std::string::size_type pos;
while ((pos = value.find(';')) != std::string::npos)
{
std::string coo = value.substr(0, pos);
auto epos = coo.find('=');
if (epos != std::string::npos)
{
std::string cookie_name = coo.substr(0, epos);
std::string::size_type cpos = 0;
while (cpos < cookie_name.length() &&
isspace(cookie_name[cpos]))
++cpos;
cookie_name = cookie_name.substr(cpos);
std::string cookie_value = coo.substr(epos + 1);
cookies_[std::move(cookie_name)] = std::move(cookie_value);
}
value = value.substr(pos + 1);
}
if (value.length() > 0)
{
std::string &coo = value;
auto epos = coo.find('=');
if (epos != std::string::npos)
{
std::string cookie_name = coo.substr(0, epos);
std::string::size_type cpos = 0;
while (cpos < cookie_name.length() &&
isspace(cookie_name[cpos]))
++cpos;
cookie_name = cookie_name.substr(cpos);
std::string cookie_value = coo.substr(epos + 1);
cookies_[std::move(cookie_name)] = std::move(cookie_value);
}
}
}
else
{
switch (field.length())
{
case 6:
if (field == "expect")
{
expectPtr_ = std::make_unique<std::string>(value);
}
break;
case 10:
{
if (field == "connection")
{
if (version_ == Version::kHttp11)
{
if (value.length() == 5 && value == "close")
keepAlive_ = false;
}
else if (value.length() == 10 &&
(value == "Keep-Alive" || value == "keep-alive"))
{
keepAlive_ = true;
}
}
}
break;
default:
break;
}
headers_.emplace(std::move(field), std::move(value));
}
}
HttpRequestPtr HttpRequest::newHttpRequest()
{
auto req = std::make_shared<HttpRequest>(nullptr);
req->setMethod(drogon::Get);
req->setVersion(drogon::Version::kHttp11);
return req;
}
HttpRequestPtr HttpRequest::newHttpFormPostRequest()
{
auto req = std::make_shared<HttpRequest>(nullptr);
req->setMethod(drogon::Post);
req->setVersion(drogon::Version::kHttp11);
req->contentType_ = CT_APPLICATION_X_FORM;
req->flagForParsingContentType_ = true;
return req;
}
HttpRequestPtr HttpRequest::newHttpJsonRequest(const Json::Value &data)
{
static std::once_flag once;
static Json::StreamWriterBuilder builder;
std::call_once(once, []() {
builder["commentStyle"] = "None";
builder["indentation"] = "";
/*
if (!Application::get_instance()->isUnicodeEscapingUsedInJson())
{
builder["emitUTF8"] = true;
}*/
});
auto req = std::make_shared<HttpRequest>(nullptr);
req->setMethod(drogon::Get);
req->setVersion(drogon::Version::kHttp11);
req->contentType_ = CT_APPLICATION_JSON;
req->setContent(writeString(builder, data));
req->flagForParsingContentType_ = true;
return req;
}
HttpRequestPtr HttpRequest::newFileUploadRequest(
const std::vector<UploadFile> &files)
{
return std::make_shared<HttpFileUploadRequest>(files);
}
void HttpRequest::swap(HttpRequest &that) noexcept
{
using std::swap;
swap(method_, that.method_);
swap(version_, that.version_);
swap(flagForParsingJson_, that.flagForParsingJson_);
swap(flagForParsingParameters_, that.flagForParsingParameters_);
swap(matchedPathPattern_, that.matchedPathPattern_);
swap(path_, that.path_);
swap(query_, that.query_);
swap(headers_, that.headers_);
swap(cookies_, that.cookies_);
swap(parameters_, that.parameters_);
swap(jsonPtr_, that.jsonPtr_);
swap(sessionPtr_, that.sessionPtr_);
swap(attributesPtr_, that.attributesPtr_);
swap(cacheFilePtr_, that.cacheFilePtr_);
swap(peer_, that.peer_);
swap(local_, that.local_);
swap(creationDate_, that.creationDate_);
swap(content_, that.content_);
swap(expectPtr_, that.expectPtr_);
swap(contentType_, that.contentType_);
swap(contentTypeString_, that.contentTypeString_);
swap(keepAlive_, that.keepAlive_);
swap(loop_, that.loop_);
swap(flagForParsingContentType_, that.flagForParsingContentType_);
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
}
const char *HttpRequest::methodString() const
{
const char *result = "UNKNOWN";
switch (method_)
{
case Get:
result = "GET";
break;
case Post:
result = "POST";
break;
case Head:
result = "HEAD";
break;
case Put:
result = "PUT";
break;
case Delete:
result = "DELETE";
break;
case Options:
result = "OPTIONS";
break;
case Patch:
result = "PATCH";
break;
default:
break;
}
return result;
}
bool HttpRequest::setMethod(const char *start, const char *end)
{
assert(method_ == Invalid);
string_view m(start, end - start);
switch (m.length())
{
case 3:
if (m == "GET")
{
method_ = Get;
}
else if (m == "PUT")
{
method_ = Put;
}
else
{
method_ = Invalid;
}
break;
case 4:
if (m == "POST")
{
method_ = Post;
}
else if (m == "HEAD")
{
method_ = Head;
}
else
{
method_ = Invalid;
}
break;
case 5:
if (m == "PATCH")
{
method_ = Patch;
}
else
{
method_ = Invalid;
}
break;
case 6:
if (m == "DELETE")
{
method_ = Delete;
}
else
{
method_ = Invalid;
}
break;
case 7:
if (m == "OPTIONS")
{
method_ = Options;
}
else
{
method_ = Invalid;
}
break;
default:
method_ = Invalid;
break;
}
// if (method_ != Invalid)
// {
// content_ = "";
// query_ = "";
// cookies_.clear();
// parameters_.clear();
// headers_.clear();
// }
return method_ != Invalid;
}
HttpRequest::~HttpRequest()
{
}
void HttpRequest::reserveBodySize(size_t length)
{
if (length <= HttpAppFrameworkImpl::instance().getClientMaxMemoryBodySize())
{
content_.reserve(length);
}
else
{
// Store data of body to a temperary file
createTmpFile();
}
}
void HttpRequest::appendToBody(const char *data, size_t length)
{
if (cacheFilePtr_)
{
cacheFilePtr_->append(data, length);
}
else
{
if (content_.length() + length <=
HttpAppFrameworkImpl::instance().getClientMaxMemoryBodySize())
{
content_.append(data, length);
}
else
{
createTmpFile();
cacheFilePtr_->append(content_);
cacheFilePtr_->append(data, length);
content_.clear();
}
}
}
void HttpRequest::createTmpFile()
{
/*
auto tmpfile = HttpAppFrameworkImpl::instance().getUploadPath();
auto fileName = utils::getUuid();
tmpfile.append("/tmp/")
.append(1, fileName[0])
.append(1, fileName[1])
.append("/")
.append(fileName);
cacheFilePtr_ = std::make_unique<CacheFile>(tmpfile);
*/
}

723
core/http_request.h Normal file
View File

@ -0,0 +1,723 @@
/**
*
* @file HttpRequest.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/attribute.h>
#include <core/http_types.h>
#include <core/session.h>
#include <core/string_view.h>
#include <core/upload_file.h>
#include <json/json.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Date.h>
#include <memory>
#include <string>
#include <unordered_map>
#include "cache_file.h"
#include "core/http_utils.h"
#include <core/http_request.h>
#include <core/utilities.h>
#include <assert.h>
#include <stdio.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <trantor/utils/NonCopyable.h>
#include <algorithm>
#include <string>
#include <thread>
#include <unordered_map>
namespace drogon {
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
/**
* @brief This template is used to convert a request object to a custom
* type object. Users must specialize the template for a particular type.
*/
template <typename T>
T fromRequest(const HttpRequest &req) {
//LOG_ERROR << "You must specialize the fromRequest template for the type of "
// << DrClassMap::demangle(typeid(T).name());
exit(1);
}
/**
* @brief This template is used to create a request object from a custom
* type object by calling the newCustomHttpRequest(). Users must specialize
* the template for a particular type.
*/
template <typename T>
HttpRequestPtr toRequest(T &&) {
//LOG_ERROR << "You must specialize the toRequest template for the type of "
// << DrClassMap::demangle(typeid(T).name());
exit(1);
}
template <>
HttpRequestPtr toRequest<const Json::Value &>(const Json::Value &pJson);
template <>
HttpRequestPtr toRequest(Json::Value &&pJson);
template <>
inline HttpRequestPtr toRequest<Json::Value &>(Json::Value &pJson) {
return toRequest((const Json::Value &)pJson);
}
template <>
std::shared_ptr<Json::Value> fromRequest(const HttpRequest &req);
/// Abstract class for webapp developer to get or set the Http request;
class HttpRequest {
public:
friend class HttpRequestParser;
explicit HttpRequest(trantor::EventLoop *loop) :
creationDate_(trantor::Date::now()), loop_(loop) {
}
void reset() {
method_ = Invalid;
version_ = Version::kUnknown;
flagForParsingJson_ = false;
headers_.clear();
cookies_.clear();
flagForParsingParameters_ = false;
path_.clear();
matchedPathPattern_ = "";
query_.clear();
parameters_.clear();
jsonPtr_.reset();
sessionPtr_.reset();
attributesPtr_.reset();
cacheFilePtr_.reset();
expectPtr_.reset();
content_.clear();
contentType_ = CT_TEXT_PLAIN;
flagForParsingContentType_ = false;
contentTypeString_.clear();
keepAlive_ = true;
jsonParsingErrorPtr_.reset();
}
trantor::EventLoop *getLoop() {
return loop_;
}
void setVersion(Version v) {
version_ = v;
if (version_ == Version::kHttp10) {
keepAlive_ = false;
}
}
void setPath(const char *start, const char *end) {
if (utils::needUrlDecoding(start, end)) {
path_ = utils::urlDecode(start, end);
} else {
path_.append(start, end);
}
}
virtual void setPath(const std::string &path) {
path_ = path;
}
/**
* @brief This template enables implicit type conversion. For using this
* template, user must specialize the fromRequest template. For example a
* shared_ptr<Json::Value> specialization version is available above, so
* we can use the following code to get a json object:
* @code
std::shared_ptr<Json::Value> jsonPtr = *requestPtr;
@endcode
* With this template, user can use their favorite JSON library instead of
* the default jsoncpp library or convert the request to an object of any
* custom type.
*/
template <typename T>
operator T() const {
return fromRequest<T>(*this);
}
/**
* @brief This template enables explicit type conversion, see the above
* template.
*/
template <typename T>
T as() const {
return fromRequest<T>(*this);
}
/// Return the method string of the request, such as GET, POST, etc.
virtual const char *methodString() const;
const char *getMethodString() const {
return methodString();
}
/// Return the enum type method of the request.
virtual HttpMethod method() const {
return method_;
}
HttpMethod getMethod() const {
return method();
}
/// Get the header string identified by the field parameter
virtual const std::string &getHeader(const std::string &field) const {
auto lowField = field;
std::transform(lowField.begin(),
lowField.end(),
lowField.begin(),
tolower);
return getHeaderBy(lowField);
}
/// Get the header string identified by the field parameter
virtual const std::string &getHeader(std::string &&field) const {
std::transform(field.begin(), field.end(), field.begin(), tolower);
return getHeaderBy(field);
}
/// Set the header string identified by the field parameter
virtual void addHeader(const std::string &key,
const std::string &value) {
headers_[key] = value;
}
/// Get the cookie string identified by the field parameter
virtual const std::string &getCookie(const std::string &field) const {
const static std::string defaultVal;
auto it = cookies_.find(field);
if (it != cookies_.end()) {
return it->second;
}
return defaultVal;
}
/// Get all headers of the request
virtual const std::unordered_map<std::string, std::string> &headers() const {
return headers_;
}
/// Get all headers of the request
const std::unordered_map<std::string, std::string> &getHeaders() const {
return headers();
}
/// Get all cookies of the request
virtual const std::unordered_map<std::string, std::string> &cookies() const {
return cookies_;
}
/// Get all cookies of the request
const std::unordered_map<std::string, std::string> &getCookies() const {
return cookies();
}
/// Get the query string of the request.
/**
* The query string is the substring after the '?' in the URL string.
*/
virtual const std::string &query() const {
return query_;
}
/// Get the query string of the request.
const std::string &getQuery() const {
return query();
}
/// Get the content string of the request, which is the body part of the
/// request.
string_view body() const {
return string_view(bodyData(), bodyLength());
}
/// Get the content string of the request, which is the body part of the
/// request.
string_view getBody() const {
return body();
}
void setQuery(const char *start, const char *end) {
query_.assign(start, end);
}
void setQuery(const std::string &query) {
query_ = query;
}
string_view bodyView() const {
if (cacheFilePtr_) {
return cacheFilePtr_->getStringView();
}
return content_;
}
virtual const char *bodyData() const {
if (cacheFilePtr_) {
return cacheFilePtr_->getStringView().data();
}
return content_.data();
}
virtual size_t bodyLength() const {
if (cacheFilePtr_) {
return cacheFilePtr_->getStringView().length();
}
return content_.length();
}
void appendToBody(const char *data, size_t length);
void reserveBodySize(size_t length);
string_view queryView() const {
return query_;
}
string_view contentView() const {
if (cacheFilePtr_)
return cacheFilePtr_->getStringView();
return content_;
}
/// Set the content string of the request.
virtual void setBody(const std::string &body) {
content_ = body;
}
/// Set the content string of the request.
virtual void setBody(std::string &&body) {
content_ = std::move(body);
}
/// Get the path of the request.
virtual const std::string &path() const {
return path_;
}
/// Get the path of the request.
const std::string &getPath() const {
return path();
}
/// Get the matched path pattern after routing
string_view getMatchedPathPattern() const {
return matchedPathPattern();
}
/// Get the matched path pattern after routing
string_view matchedPathPattern() const {
return string_view(matchedPathPatternData(),
matchedPathPatternLength());
}
virtual const char *matchedPathPatternData() const {
return matchedPathPattern_.data();
}
virtual size_t matchedPathPatternLength() const {
return matchedPathPattern_.length();
}
void setMatchedPathPattern(const std::string &pathPattern) {
matchedPathPattern_ = pathPattern;
}
const std::string &expect() const {
const static std::string none{ "" };
if (expectPtr_)
return *expectPtr_;
return none;
}
bool keepAlive() const {
return keepAlive_;
}
/// Return the enum type version of the request.
/**
* kHttp10 means Http version is 1.0
* kHttp11 means Http verison is 1.1
*/
virtual Version version() const {
return version_;
}
/// Return the enum type version of the request.
Version getVersion() const {
return version();
}
/// Get the session to which the request belongs.
virtual SessionPtr session() const {
return sessionPtr_;
}
void setSession(const SessionPtr &session) {
sessionPtr_ = session;
}
/// Get the session to which the request belongs.
SessionPtr getSession() const {
return session();
}
/// Get the attributes store, users can add/get any type of data to/from
/// this store
virtual const AttributesPtr &attributes() const {
if (!attributesPtr_) {
attributesPtr_ = std::make_shared<Attributes>();
}
return attributesPtr_;
}
/// Get the attributes store, users can add/get any type of data to/from
/// this store
const AttributesPtr &getAttributes() const {
return attributes();
}
/// Get parameters of the request.
virtual const std::unordered_map<std::string, std::string> &parameters()
const {
parseParametersOnce();
return parameters_;
}
/// Get parameters of the request.
const std::unordered_map<std::string, std::string> &getParameters() const {
return parameters();
}
/// Get a parameter identified by the @param key
virtual const std::string &getParameter(
const std::string &key) const {
const static std::string defaultVal;
parseParametersOnce();
auto iter = parameters_.find(key);
if (iter != parameters_.end())
return iter->second;
return defaultVal;
}
/// Return the remote IP address and port
virtual const trantor::InetAddress &peerAddr() const {
return peer_;
}
const trantor::InetAddress &getPeerAddr() const {
return peerAddr();
}
/// Return the local IP address and port
virtual const trantor::InetAddress &localAddr() const {
return local_;
}
const trantor::InetAddress &getLocalAddr() const {
return localAddr();
}
/// Return the creation timestamp set by the framework.
virtual const trantor::Date &creationDate() const {
return creationDate_;
}
const trantor::Date &getCreationDate() const {
return creationDate();
}
void setCreationDate(const trantor::Date &date) {
creationDate_ = date;
}
void setPeerAddr(const trantor::InetAddress &peer) {
peer_ = peer;
}
void setLocalAddr(const trantor::InetAddress &local) {
local_ = local;
}
void addHeader(const char *start, const char *colon, const char *end);
const std::string &getHeaderBy(const std::string &lowerField) const {
const static std::string defaultVal;
auto it = headers_.find(lowerField);
if (it != headers_.end()) {
return it->second;
}
return defaultVal;
}
/// Get the Json object of the request
/**
* The content type of the request must be 'application/json', and the query
* string (the part after the question mark in the URI) must be empty,
* otherwise the method returns an empty shared_ptr object.
*/
virtual const std::shared_ptr<Json::Value> &jsonObject() const {
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingJson_) {
flagForParsingJson_ = true;
parseJson();
}
return jsonPtr_;
}
/// Get the Json object of the request
const std::shared_ptr<Json::Value> &getJsonObject() const {
return jsonObject();
}
/**
* @brief Get the error message of parsing the JSON body received from peer.
* This method usually is called after getting a empty shared_ptr object
* by the getJsonObject() method.
*
* @return const std::string& The error message. An empty string is returned
* when no error occurs.
*/
virtual const std::string &getJsonError() const {
const static std::string none{ "" };
if (jsonParsingErrorPtr_)
return *jsonParsingErrorPtr_;
return none;
}
/// Get the content type
virtual ContentType contentType() const {
if (!flagForParsingContentType_) {
flagForParsingContentType_ = true;
auto &contentTypeString = getHeaderBy("content-type");
if (contentTypeString == "") {
contentType_ = CT_NONE;
} else {
auto pos = contentTypeString.find(';');
if (pos != std::string::npos) {
contentType_ = parseContentType(
string_view(contentTypeString.data(), pos));
} else {
contentType_ =
parseContentType(string_view(contentTypeString));
}
}
}
return contentType_;
}
ContentType getContentType() const {
return contentType();
}
/// Set the Http method
virtual void setMethod(const HttpMethod method) {
method_ = method;
return;
}
/// Set the path of the request
void setSecure(bool secure) {
isOnSecureConnection_ = secure;
}
/// Set the parameter of the request
virtual void setParameter(const std::string &key,
const std::string &value) {
flagForParsingParameters_ = true;
parameters_[key] = value;
}
const std::string &getContent() const {
return content_;
}
void swap(HttpRequest &that) noexcept;
void setContent(const std::string &content) {
content_ = content;
}
/// Set or get the content type
virtual void setContentTypeCode(const ContentType type) {
contentType_ = type;
flagForParsingContentType_ = true;
auto &typeStr = webContentTypeToString(type);
setContentType(std::string(typeStr.data(), typeStr.length()));
}
/// Set the request content-type string, The string
/// must contain the header name and CRLF.
/// For example, "content-type: text/plain\r\n"
virtual void setCustomContentTypeString(const std::string &type) {
contentType_ = CT_NONE;
flagForParsingContentType_ = true;
contentTypeString_ = type;
}
// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const
// std::string &charSet = "utf-8")
// {
// contentType_ = type;
// setContentType(webContentTypeAndCharsetToString(type, charSet));
// }
/// Add a cookie
virtual void addCookie(const std::string &key,
const std::string &value) {
cookies_[key] = value;
}
/**
* @brief Set the request object to the pass-through mode or not. It's not
* by default when a new request object is created.
* In pass-through mode, no addtional headers (including server, date,
* content-type and content-length, etc.) are added to the request. This
* mode is useful for some applications such as a proxy.
*
* @param flag
*/
virtual void setPassThrough(bool flag) {
passThrough_ = flag;
}
bool passThrough() const {
return passThrough_;
}
void appendToBuffer(trantor::MsgBuffer *output) const;
virtual ~HttpRequest();
/// The following methods are a series of factory methods that help users
/// create request objects.
/// Create a normal request with http method Get and version Http1.1.
static HttpRequestPtr newHttpRequest();
/// Create a http request with:
/// Method: Get
/// Version: Http1.1
/// Content type: application/json, the @param data is serialized into the
/// content of the request.
static HttpRequestPtr newHttpJsonRequest(const Json::Value &data);
/// Create a http request with:
/// Method: Post
/// Version: Http1.1
/// Content type: application/x-www-form-urlencoded
static HttpRequestPtr newHttpFormPostRequest();
/// Create a http file upload request with:
/// Method: Post
/// Version: Http1.1
/// Content type: multipart/form-data
/// The @param files represents pload files which are transferred to the
/// server via the multipart/form-data format
static HttpRequestPtr newFileUploadRequest(
const std::vector<UploadFile> &files);
/**
* @brief Create a custom HTTP request object. For using this template,
* users must specialize the toRequest template.
*/
template <typename T>
static HttpRequestPtr newCustomHttpRequest(T &&obj) {
return toRequest(std::forward<T>(obj));
}
virtual bool isOnSecureConnection() const noexcept {
return isOnSecureConnection_;
}
protected:
void setContentType(const std::string &contentType) {
contentTypeString_ = contentType;
}
void setContentType(std::string &&contentType) {
contentTypeString_ = std::move(contentType);
}
private:
void parseParameters() const;
void parseParametersOnce() const {
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingParameters_) {
flagForParsingParameters_ = true;
parseParameters();
}
}
void createTmpFile();
void parseJson() const;
mutable bool flagForParsingParameters_{ false };
mutable bool flagForParsingJson_{ false };
HttpMethod method_{ Invalid };
Version version_{ Version::kUnknown };
std::string path_;
string_view matchedPathPattern_{ "" };
std::string query_;
std::unordered_map<std::string, std::string> headers_;
std::unordered_map<std::string, std::string> cookies_;
mutable std::unordered_map<std::string, std::string> parameters_;
mutable std::shared_ptr<Json::Value> jsonPtr_;
SessionPtr sessionPtr_;
mutable AttributesPtr attributesPtr_;
trantor::InetAddress peer_;
trantor::InetAddress local_;
trantor::Date creationDate_;
std::unique_ptr<CacheFile> cacheFilePtr_;
mutable std::unique_ptr<std::string> jsonParsingErrorPtr_;
std::unique_ptr<std::string> expectPtr_;
bool keepAlive_{ true };
bool isOnSecureConnection_{ false };
bool passThrough_{ false };
protected:
std::string content_;
trantor::EventLoop *loop_;
mutable ContentType contentType_{ CT_TEXT_PLAIN };
mutable bool flagForParsingContentType_{ false };
std::string contentTypeString_;
};
template <>
inline HttpRequestPtr toRequest<const Json::Value &>(const Json::Value &pJson) {
return HttpRequest::newHttpJsonRequest(pJson);
}
template <>
inline HttpRequestPtr toRequest(Json::Value &&pJson) {
return HttpRequest::newHttpJsonRequest(std::move(pJson));
}
template <>
inline std::shared_ptr<Json::Value> fromRequest(const HttpRequest &req) {
return req.getJsonObject();
}
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
inline void swap(HttpRequest &one, HttpRequest &two) noexcept {
one.swap(two);
}
} // namespace drogon

View File

@ -0,0 +1,510 @@
/**
*
* HttpRequestParser.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "core/http_request_parser.h"
#include "core/http_response.h"
#include "http_request.h"
#include "core/http_utils.h"
#include <core/http_types.h>
#include <iostream>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
using namespace trantor;
using namespace drogon;
HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr)
: status_(HttpRequestParseStatus::kExpectMethod),
loop_(connPtr->getLoop()),
conn_(connPtr)
{
}
void HttpRequestParser::shutdownConnection(HttpStatusCode code)
{
auto connPtr = conn_.lock();
if (connPtr)
{
connPtr->send(utils::formattedString(
"HTTP/1.1 %d %s\r\nConnection: close\r\n\r\n",
code,
statusCodeToString(code).data()));
connPtr->shutdown();
}
}
bool HttpRequestParser::processRequestLine(const char *begin, const char *end)
{
bool succeed = false;
const char *start = begin;
const char *space = std::find(start, end, ' ');
if (space != end)
{
const char *question = std::find(start, space, '?');
if (question != space)
{
request_->setPath(start, question);
request_->setQuery(question + 1, space);
}
else
{
request_->setPath(start, space);
}
start = space + 1;
succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1.");
if (succeed)
{
if (*(end - 1) == '1')
{
request_->setVersion(Version::kHttp11);
}
else if (*(end - 1) == '0')
{
request_->setVersion(Version::kHttp10);
}
else
{
succeed = false;
}
}
}
return succeed;
}
HttpRequestPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr)
{
std::weak_ptr<HttpRequestParser> weakPtr = shared_from_this();
return std::shared_ptr<HttpRequestImpl>(ptr, [weakPtr](HttpRequestImpl *p) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
if (thisPtr->loop_->isInLoopThread())
{
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
}
else
{
thisPtr->loop_->queueInLoop([thisPtr, p]() {
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
});
}
}
else
{
delete p;
}
});
}
void HttpRequestParser::reset()
{
assert(loop_->isInLoopThread());
currentContentLength_ = 0;
status_ = HttpRequestParseStatus::kExpectMethod;
if (requestsPool_.empty())
{
request_ = makeRequestForPool(new HttpRequestImpl(loop_));
}
else
{
auto req = std::move(requestsPool_.back());
requestsPool_.pop_back();
request_ = std::move(req);
request_->setCreationDate(trantor::Date::now());
}
}
// Return false if any error
bool HttpRequestParser::parseRequest(MsgBuffer *buf)
{
bool ok = true;
bool hasMore = true;
// std::cout<<std::string(buf->peek(),buf->readableBytes())<<std::endl;
while (hasMore)
{
if (status_ == HttpRequestParseStatus::kExpectMethod)
{
auto *space =
std::find(buf->peek(), (const char *)buf->beginWrite(), ' ');
if (space != buf->beginWrite())
{
if (request_->setMethod(buf->peek(), space))
{
status_ = HttpRequestParseStatus::kExpectRequestLine;
buf->retrieveUntil(space + 1);
continue;
}
else
{
buf->retrieveAll();
shutdownConnection(k405MethodNotAllowed);
return false;
}
}
else
{
if (buf->readableBytes() >= 7)
{
buf->retrieveAll();
shutdownConnection(k400BadRequest);
return false;
}
hasMore = false;
}
}
else if (status_ == HttpRequestParseStatus::kExpectRequestLine)
{
const char *crlf = buf->findCRLF();
if (crlf)
{
ok = processRequestLine(buf->peek(), crlf);
if (ok)
{
buf->retrieveUntil(crlf + 2);
status_ = HttpRequestParseStatus::kExpectHeaders;
}
else
{
buf->retrieveAll();
shutdownConnection(k400BadRequest);
return false;
}
}
else
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for request line is 64K bytes. respone
/// k414RequestURITooLarge
/// TODO: Make this configurable?
buf->retrieveAll();
shutdownConnection(k414RequestURITooLarge);
return false;
}
hasMore = false;
}
}
else if (status_ == HttpRequestParseStatus::kExpectHeaders)
{
const char *crlf = buf->findCRLF();
if (crlf)
{
const char *colon = std::find(buf->peek(), crlf, ':');
if (colon != crlf)
{
request_->addHeader(buf->peek(), colon, crlf);
}
else
{
// empty line, end of header
const std::string &len =
request_->getHeaderBy("content-length");
if (!len.empty())
{
currentContentLength_ = std::stoull(len.c_str());
if (currentContentLength_ == 0)
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
hasMore = false;
}
else
{
status_ = HttpRequestParseStatus::kExpectBody;
}
}
else
{
const std::string &encode =
request_->getHeaderBy("transfer-encoding");
if (encode.empty())
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
hasMore = false;
}
else if (encode == "chunked")
{
status_ = HttpRequestParseStatus::kExpectChunkLen;
}
else
{
buf->retrieveAll();
shutdownConnection(k501NotImplemented);
return false;
}
}
auto &expect = request_->expect();
if (expect == "100-continue" &&
request_->getVersion() >= Version::kHttp11)
{
if (currentContentLength_ == 0)
{
buf->retrieveAll();
shutdownConnection(k400BadRequest);
return false;
}
// rfc2616-8.2.3
auto connPtr = conn_.lock();
if (connPtr)
{
auto resp = HttpResponse::newHttpResponse();
if (currentContentLength_ >
HttpAppFrameworkImpl::instance()
.getClientMaxBodySize())
{
resp->setStatusCode(k413RequestEntityTooLarge);
auto httpString =
static_cast<HttpResponseImpl *>(resp.get())
->renderToBuffer();
reset();
connPtr->send(std::move(*httpString));
}
else
{
resp->setStatusCode(k100Continue);
auto httpString =
static_cast<HttpResponseImpl *>(resp.get())
->renderToBuffer();
connPtr->send(std::move(*httpString));
}
}
}
else if (!expect.empty())
{
LOG_WARN << "417ExpectationFailed for \"" << expect
<< "\"";
auto connPtr = conn_.lock();
if (connPtr)
{
buf->retrieveAll();
shutdownConnection(k417ExpectationFailed);
return false;
}
}
else if (currentContentLength_ >
HttpAppFrameworkImpl::instance()
.getClientMaxBodySize())
{
buf->retrieveAll();
shutdownConnection(k413RequestEntityTooLarge);
return false;
}
request_->reserveBodySize(currentContentLength_);
}
buf->retrieveUntil(crlf + 2);
}
else
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for every request header is 64K bytes;
/// TODO: Make this configurable?
buf->retrieveAll();
shutdownConnection(k400BadRequest);
return false;
}
hasMore = false;
}
}
else if (status_ == HttpRequestParseStatus::kExpectBody)
{
if (buf->readableBytes() == 0)
{
if (currentContentLength_ == 0)
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
}
break;
}
if (currentContentLength_ >= buf->readableBytes())
{
currentContentLength_ -= buf->readableBytes();
request_->appendToBody(buf->peek(), buf->readableBytes());
buf->retrieveAll();
}
else
{
request_->appendToBody(buf->peek(), currentContentLength_);
buf->retrieve(currentContentLength_);
currentContentLength_ = 0;
}
if (currentContentLength_ == 0)
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
hasMore = false;
}
}
else if (status_ == HttpRequestParseStatus::kExpectChunkLen)
{
const char *crlf = buf->findCRLF();
if (crlf)
{
// chunk length line
std::string len(buf->peek(), crlf - buf->peek());
char *end;
currentChunkLength_ = strtol(len.c_str(), &end, 16);
// LOG_TRACE << "chun length : " <<
// responsePtr_->currentChunkLength_;
if (currentChunkLength_ != 0)
{
if (currentChunkLength_ + currentContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
buf->retrieveAll();
shutdownConnection(k413RequestEntityTooLarge);
return false;
}
status_ = HttpRequestParseStatus::kExpectChunkBody;
}
else
{
status_ = HttpRequestParseStatus::kExpectLastEmptyChunk;
}
buf->retrieveUntil(crlf + 2);
}
else
{
hasMore = false;
}
}
else if (status_ == HttpRequestParseStatus::kExpectChunkBody)
{
// LOG_TRACE<<"expect chunk
// len="<<responsePtr_->currentChunkLength_;
if (buf->readableBytes() >= (currentChunkLength_ + 2))
{
if (*(buf->peek() + currentChunkLength_) == '\r' &&
*(buf->peek() + currentChunkLength_ + 1) == '\n')
{
request_->appendToBody(buf->peek(), currentChunkLength_);
buf->retrieve(currentChunkLength_ + 2);
currentContentLength_ += currentChunkLength_;
currentChunkLength_ = 0;
status_ = HttpRequestParseStatus::kExpectChunkLen;
}
else
{
// error!
buf->retrieveAll();
return false;
}
}
else
{
hasMore = false;
}
}
else if (status_ == HttpRequestParseStatus::kExpectLastEmptyChunk)
{
// last empty chunk
const char *crlf = buf->findCRLF();
if (crlf)
{
buf->retrieveUntil(crlf + 2);
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
break;
}
else
{
hasMore = false;
}
}
}
return ok;
}
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req)
{
#ifndef NDEBUG
auto conn = conn_.lock();
if (conn)
{
conn->getLoop()->assertInLoopThread();
}
#endif
requestPipelining_.push_back({req, {nullptr, false}});
}
HttpRequestPtr HttpRequestParser::getFirstRequest() const
{
#ifndef NDEBUG
auto conn = conn_.lock();
if (conn)
{
conn->getLoop()->assertInLoopThread();
}
#endif
if (!requestPipelining_.empty())
{
return requestPipelining_.front().first;
}
return nullptr;
}
std::pair<HttpResponsePtr, bool> HttpRequestParser::getFirstResponse() const
{
#ifndef NDEBUG
auto conn = conn_.lock();
if (conn)
{
conn->getLoop()->assertInLoopThread();
}
#endif
if (!requestPipelining_.empty())
{
return requestPipelining_.front().second;
}
return {nullptr, false};
}
void HttpRequestParser::popFirstRequest()
{
#ifndef NDEBUG
auto conn = conn_.lock();
if (conn)
{
conn->getLoop()->assertInLoopThread();
}
#endif
requestPipelining_.pop_front();
}
void HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req,
const HttpResponsePtr &resp,
bool isHeadMethod)
{
#ifndef NDEBUG
auto conn = conn_.lock();
if (conn)
{
conn->getLoop()->assertInLoopThread();
}
#endif
for (auto &iter : requestPipelining_)
{
if (iter.first == req)
{
iter.second = {resp, isHeadMethod};
return;
}
}
}

156
core/http_request_parser.h Normal file
View File

@ -0,0 +1,156 @@
/**
*
* HttpRequestParser.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "core/http_server_callbacks.h"
#include <core/http_types.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/TcpConnection.h>
#include <trantor/utils/MsgBuffer.h>
#include <mutex>
#include <deque>
namespace drogon
{
class HttpRequestParser : public trantor::NonCopyable,
public std::enable_shared_from_this<HttpRequestParser>
{
public:
enum class HttpRequestParseStatus
{
kExpectMethod,
kExpectRequestLine,
kExpectHeaders,
kExpectBody,
kExpectChunkLen,
kExpectChunkBody,
kExpectLastEmptyChunk,
kGotAll,
};
explicit HttpRequestParser(const trantor::TcpConnectionPtr &connPtr);
// return false if any error
bool parseRequest(trantor::MsgBuffer *buf);
bool gotAll() const
{
return status_ == HttpRequestParseStatus::kGotAll;
}
void reset();
const HttpRequestPtr &requestImpl() const
{
return request_;
}
bool firstReq()
{
if (firstRequest_)
{
firstRequest_ = false;
return true;
}
return false;
}
const WebSocketConnectionPtr &webSocketConn() const
{
return websockConnPtr_;
}
void setWebsockConnection(const WebSocketConnectionPtr &conn)
{
websockConnPtr_ = conn;
}
// to support request pipelining(rfc2616-8.1.2.2)
void pushRequestToPipelining(const HttpRequestPtr &req);
HttpRequestPtr getFirstRequest() const;
std::pair<HttpResponsePtr, bool> getFirstResponse() const;
void popFirstRequest();
void pushResponseToPipelining(const HttpRequestPtr &req,
const HttpResponsePtr &resp,
bool isHeadMethod);
size_t numberOfRequestsInPipelining() const
{
return requestPipelining_.size();
}
bool emptyPipelining()
{
return requestPipelining_.empty();
}
bool isStop() const
{
return stopWorking_;
}
void stop()
{
stopWorking_ = true;
}
size_t numberOfRequestsParsed() const
{
return requestsCounter_;
}
trantor::MsgBuffer &getBuffer()
{
return sendBuffer_;
}
std::vector<std::pair<HttpResponsePtr, bool>> &getResponseBuffer()
{
assert(loop_->isInLoopThread());
if (!responseBuffer_)
{
responseBuffer_ =
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>(
new std::vector<std::pair<HttpResponsePtr, bool>>);
}
return *responseBuffer_;
}
std::vector<HttpRequestPtr> &getRequestBuffer()
{
assert(loop_->isInLoopThread());
if (!requestBuffer_)
{
requestBuffer_ = std::unique_ptr<std::vector<HttpRequestPtr>>(
new std::vector<HttpRequestPtr>);
}
return *requestBuffer_;
}
private:
HttpRequestPtr makeRequestForPool(HttpRequest *p);
void shutdownConnection(HttpStatusCode code);
bool processRequestLine(const char *begin, const char *end);
HttpRequestParseStatus status_;
trantor::EventLoop *loop_;
HttpRequestPtr request_;
bool firstRequest_{true};
WebSocketConnectionPtr websockConnPtr_;
std::deque<std::pair<HttpRequestPtr, std::pair<HttpResponsePtr, bool>>>
requestPipelining_;
size_t requestsCounter_{0};
std::weak_ptr<trantor::TcpConnection> conn_;
bool stopWorking_{false};
trantor::MsgBuffer sendBuffer_;
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>
responseBuffer_;
std::unique_ptr<std::vector<HttpRequestPtr>> requestBuffer_;
std::vector<HttpRequestPtr> requestsPool_;
size_t currentChunkLength_;
size_t currentContentLength_{0};
};
} // namespace drogon

677
core/http_response.cpp Normal file
View File

@ -0,0 +1,677 @@
/**
*
* @file HttpResponse.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "core/http_response.h"
//#include "HttpAppFrameworkImpl.h"
#include "core/http_utils.h"
#include <core/http_view_data.h>
#include <core/io_thread_storage.h>
#include "core/application.h"
#include <fstream>
#include <memory>
#include <stdio.h>
#include <sys/stat.h>
#include <trantor/utils/Logger.h>
#ifdef _WIN32
#define stat _stati64
#endif
using namespace trantor;
using namespace drogon;
namespace drogon
{
// "Fri, 23 Aug 2019 12:58:03 GMT" length = 29
static const size_t httpFullDateStringLength = 29;
static inline void doResponseCreateAdvices(
const HttpResponsePtr &responsePtr)
{
/*
auto &advices =
HttpAppFrameworkImpl::instance().getResponseCreationAdvices();
if (!advices.empty())
{
for (auto &advice : advices)
{
advice(responsePtr);
}
}*/
}
static inline HttpResponsePtr genHttpResponse(std::string viewName,
const HttpViewData &data)
{
auto templ = DrTemplateBase::newTemplate(viewName);
if (templ)
{
auto res = HttpResponse::newHttpResponse();
res->setBody(templ->genText(data));
return res;
}
return drogon::HttpResponse::newNotFoundResponse();
}
} // namespace drogon
HttpResponsePtr HttpResponse::newHttpResponse()
{
auto res = std::make_shared<HttpResponse>(k200OK, CT_TEXT_HTML);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
{
auto res = std::make_shared<HttpResponse>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(data);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data)
{
auto res = std::make_shared<HttpResponse>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(std::move(data));
doResponseCreateAdvices(res);
return res;
}
void HttpResponse::generateBodyFromJson() const
{
if (!jsonPtr_ || flagForSerializingJson_)
{
return;
}
flagForSerializingJson_ = true;
static std::once_flag once;
static Json::StreamWriterBuilder builder;
std::call_once(once, []() {
builder["commentStyle"] = "None";
builder["indentation"] = "";
/*
if (!app().isUnicodeEscapingUsedInJson())
{
builder["emitUTF8"] = true;
}*/
});
bodyPtr_ = std::make_shared<HttpMessageStringBody>(
writeString(builder, *jsonPtr_));
}
HttpResponsePtr HttpResponse::newNotFoundResponse()
{
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
auto &resp = HttpAppFrameworkImpl::instance().getCustom404Page();
if (resp)
{
if (loop && loop->index() < Application::get_instance()->threadNum_)
{
return resp;
}
else
{
return HttpResponsePtr{new HttpResponse(
*static_cast<HttpResponse *>(resp.get()))};
}
}
else
{
if (loop && loop->index() < Application::get_instance()->threadNum_)
{
// If the current thread is an IO thread
static std::once_flag threadOnce;
static IOThreadStorage<HttpResponsePtr> thread404Pages;
std::call_once(threadOnce, [] {
thread404Pages.init(
[](drogon::HttpResponsePtr &resp, size_t index) {
if (HttpAppFrameworkImpl::instance()
.isUsingCustomErrorHandler())
{
//resp = app().getCustomErrorHandler()(k404NotFound);
//resp->setExpiredTime(0);
}
else
{
HttpViewData data;
data.insert("version", drogon::getVersion());
resp = HttpResponse::newHttpViewResponse(
"drogon::NotFound", data);
resp->setStatusCode(k404NotFound);
resp->setExpiredTime(0);
}
});
});
LOG_TRACE << "Use cached 404 response";
return thread404Pages.getThreadData();
}
else
{
//if (HttpAppFrameworkImpl::instance().isUsingCustomErrorHandler())
//{
//auto resp = app().getCustomErrorHandler()(k404NotFound);
// return resp;
// }
HttpViewData data;
data.insert("version", drogon::getVersion());
auto notFoundResp =
HttpResponse::newHttpViewResponse("drogon::NotFound", data);
notFoundResp->setStatusCode(k404NotFound);
return notFoundResp;
}
}
}
HttpResponsePtr HttpResponse::newRedirectionResponse(
const std::string &location,
HttpStatusCode status)
{
auto res = std::make_shared<HttpResponse>();
res->setStatusCode(status);
res->redirect(location);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName,
const HttpViewData &data)
{
return genHttpResponse(viewName, data);
}
HttpResponsePtr HttpResponse::newFileResponse(
const std::string &fullPath,
const std::string &attachmentFileName,
ContentType type)
{
std::ifstream infile(fullPath, std::ifstream::binary);
LOG_TRACE << "send http file:" << fullPath;
if (!infile)
{
auto resp = HttpResponse::newNotFoundResponse();
return resp;
}
auto resp = std::make_shared<HttpResponse>();
std::streambuf *pbuf = infile.rdbuf();
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
pbuf->pubseekoff(0, infile.beg); // rewind
if (HttpAppFrameworkImpl::instance().useSendfile() && filesize > 1024 * 200)
// TODO : Is 200k an appropriate value? Or set it to be configurable
{
// The advantages of sendfile() can only be reflected in sending large
// files.
resp->setSendfile(fullPath);
}
else
{
std::string str;
str.resize(filesize);
pbuf->sgetn(&str[0], filesize);
resp->setBody(std::move(str));
}
resp->setStatusCode(k200OK);
if (type == CT_NONE)
{
if (!attachmentFileName.empty())
{
resp->setContentTypeCode(
drogon::getContentType(attachmentFileName));
}
else
{
resp->setContentTypeCode(drogon::getContentType(fullPath));
}
}
else
{
resp->setContentTypeCode(type);
}
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
doResponseCreateAdvices(resp);
return resp;
}
void HttpResponse::makeHeaderString(trantor::MsgBuffer &buffer)
{
buffer.ensureWritableBytes(128);
int len{0};
if (version_ == Version::kHttp11)
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.1 %d ",
statusCode_);
}
else
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.0 %d ",
statusCode_);
}
buffer.hasWritten(len);
if (!statusMessage_.empty())
buffer.append(statusMessage_.data(), statusMessage_.length());
buffer.append("\r\n");
generateBodyFromJson();
if (!passThrough_)
{
buffer.ensureWritableBytes(64);
if (sendfileName_.empty())
{
auto bodyLength = bodyPtr_ ? bodyPtr_->length() : 0;
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(bodyLength)>(),
bodyLength);
}
else
{
struct stat filestat;
if (stat(sendfileName_.data(), &filestat) < 0)
{
LOG_SYSERR << sendfileName_ << " stat error";
return;
}
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(filestat.st_size)>(),
filestat.st_size);
}
buffer.hasWritten(len);
if (headers_.find("Connection") == headers_.end())
{
if (closeConnection_)
{
buffer.append("Connection: close\r\n");
}
else if (version_ == Version::kHttp10)
{
buffer.append("Connection: Keep-Alive\r\n");
}
}
buffer.append(contentTypeString_.data(), contentTypeString_.length());
if (HttpAppFrameworkImpl::instance().sendServerHeader())
{
buffer.append(
HttpAppFrameworkImpl::instance().getServerHeaderString());
}
}
for (auto it = headers_.begin(); it != headers_.end(); ++it)
{
buffer.append(it->first);
buffer.append(": ");
buffer.append(it->second);
buffer.append("\r\n");
}
}
void HttpResponse::renderToBuffer(trantor::MsgBuffer &buffer)
{
if (expriedTime_ >= 0)
{
auto strPtr = renderToBuffer();
buffer.append(strPtr->peek(), strPtr->readableBytes());
return;
}
if (!fullHeaderString_)
{
makeHeaderString(buffer);
}
else
{
buffer.append(*fullHeaderString_);
}
// output cookies
if (cookies_.size() > 0)
{
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
buffer.append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
buffer.append("Date: ");
buffer.append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
buffer.append("\r\n\r\n");
}
else
{
buffer.append("\r\n");
}
if (bodyPtr_)
buffer.append(bodyPtr_->data(), bodyPtr_->length());
}
std::shared_ptr<trantor::MsgBuffer> HttpResponse::renderToBuffer()
{
if (expriedTime_ >= 0)
{
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
if (datePos_ != static_cast<size_t>(-1))
{
auto now = trantor::Date::now();
bool isDateChanged =
((now.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC) !=
httpStringDate_);
assert(httpString_);
if (isDateChanged)
{
httpStringDate_ =
now.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
auto newDate = utils::getHttpFullDate(now);
httpString_ =
std::make_shared<trantor::MsgBuffer>(*httpString_);
memcpy((void *)&(*httpString_)[datePos_],
newDate,
httpFullDateStringLength);
return httpString_;
}
return httpString_;
}
}
else
{
if (httpString_)
return httpString_;
}
}
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_)
{
makeHeaderString(*httpString);
}
else
{
httpString->append(*fullHeaderString_);
}
// output cookies
if (cookies_.size() > 0)
{
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
httpString->append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
httpString->append("Date: ");
auto datePos = httpString->readableBytes();
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
httpString->append("\r\n\r\n");
datePos_ = datePos;
}
else
{
httpString->append("\r\n");
}
LOG_TRACE << "reponse(no body):"
<< string_view{httpString->peek(), httpString->readableBytes()};
if (bodyPtr_)
httpString->append(bodyPtr_->data(), bodyPtr_->length());
if (expriedTime_ >= 0)
{
httpString_ = httpString;
}
return httpString;
}
std::shared_ptr<trantor::MsgBuffer> HttpResponse::
renderHeaderForHeadMethod()
{
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_)
{
makeHeaderString(*httpString);
}
else
{
httpString->append(*fullHeaderString_);
}
// output cookies
if (cookies_.size() > 0)
{
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
httpString->append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
httpString->append("Date: ");
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
httpString->append("\r\n\r\n");
}
else
{
httpString->append("\r\n");
}
return httpString;
}
void HttpResponse::addHeader(const char *start,
const char *colon,
const char *end)
{
fullHeaderString_.reset();
std::string field(start, colon);
transform(field.begin(), field.end(), field.begin(), ::tolower);
++colon;
while (colon < end && isspace(*colon))
{
++colon;
}
std::string value(colon, end);
while (!value.empty() && isspace(value[value.size() - 1]))
{
value.resize(value.size() - 1);
}
if (field == "set-cookie")
{
// LOG_INFO<<"cookies!!!:"<<value;
auto values = utils::splitString(value, ";");
Cookie cookie;
cookie.setHttpOnly(false);
for (size_t i = 0; i < values.size(); ++i)
{
std::string &coo = values[i];
std::string cookie_name;
std::string cookie_value;
auto epos = coo.find('=');
if (epos != std::string::npos)
{
cookie_name = coo.substr(0, epos);
std::string::size_type cpos = 0;
while (cpos < cookie_name.length() &&
isspace(cookie_name[cpos]))
++cpos;
cookie_name = cookie_name.substr(cpos);
++epos;
while (epos < coo.length() && isspace(coo[epos]))
++epos;
cookie_value = coo.substr(epos);
}
else
{
std::string::size_type cpos = 0;
while (cpos < coo.length() && isspace(coo[cpos]))
++cpos;
cookie_name = coo.substr(cpos);
}
if (i == 0)
{
cookie.setKey(cookie_name);
cookie.setValue(cookie_value);
}
else
{
std::transform(cookie_name.begin(),
cookie_name.end(),
cookie_name.begin(),
tolower);
if (cookie_name == "path")
{
cookie.setPath(cookie_value);
}
else if (cookie_name == "domain")
{
cookie.setDomain(cookie_value);
}
else if (cookie_name == "expires")
{
cookie.setExpiresDate(utils::getHttpDate(cookie_value));
}
else if (cookie_name == "secure")
{
cookie.setSecure(true);
}
else if (cookie_name == "httponly")
{
cookie.setHttpOnly(true);
}
}
}
if (!cookie.key().empty())
{
cookies_[cookie.key()] = cookie;
}
}
else
{
headers_[std::move(field)] = std::move(value);
}
}
void HttpResponse::swap(HttpResponse &that) noexcept
{
using std::swap;
headers_.swap(that.headers_);
cookies_.swap(that.cookies_);
swap(statusCode_, that.statusCode_);
swap(version_, that.version_);
swap(statusMessage_, that.statusMessage_);
swap(closeConnection_, that.closeConnection_);
bodyPtr_.swap(that.bodyPtr_);
swap(contentType_, that.contentType_);
swap(flagForParsingContentType_, that.flagForParsingContentType_);
swap(flagForParsingJson_, that.flagForParsingJson_);
swap(sendfileName_, that.sendfileName_);
jsonPtr_.swap(that.jsonPtr_);
fullHeaderString_.swap(that.fullHeaderString_);
httpString_.swap(that.httpString_);
swap(datePos_, that.datePos_);
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
}
void HttpResponse::clear()
{
statusCode_ = kUnknown;
version_ = Version::kHttp11;
statusMessage_ = string_view{};
fullHeaderString_.reset();
jsonParsingErrorPtr_.reset();
sendfileName_.clear();
headers_.clear();
cookies_.clear();
bodyPtr_.reset();
jsonPtr_.reset();
expriedTime_ = -1;
datePos_ = std::string::npos;
flagForParsingContentType_ = false;
flagForParsingJson_ = false;
}
void HttpResponse::parseJson() const
{
static std::once_flag once;
static Json::CharReaderBuilder builder;
std::call_once(once, []() { builder["collectComments"] = false; });
JSONCPP_STRING errs;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (bodyPtr_)
{
jsonPtr_ = std::make_shared<Json::Value>();
if (!reader->parse(bodyPtr_->data(),
bodyPtr_->data() + bodyPtr_->length(),
jsonPtr_.get(),
&errs))
{
LOG_ERROR << errs;
LOG_ERROR << "body: " << bodyPtr_->getString();
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_shared<std::string>(std::move(errs));
}
else
{
jsonParsingErrorPtr_.reset();
}
}
else
{
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_shared<std::string>("empty response body");
}
}
HttpResponse::~HttpResponse()
{
}
bool HttpResponse::shouldBeCompressed() const
{
if (!sendfileName_.empty() ||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
getBody().length() < 1024 || !(getHeaderBy("content-encoding").empty()))
{
return false;
}
return true;
}

670
core/http_response.h Normal file
View File

@ -0,0 +1,670 @@
/**
* @file HttpResponse.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/cookie.h>
#include <core/http_types.h>
#include <core/string_view.h>
#include <core/http_view_data.h>
#include <json/json.h>
#include <memory>
#include <string>
#include "core/http_utils.h"
#include "http_message_body.h"
#include <core/http_response.h>
#include <core/utilities.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/MsgBuffer.h>
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
namespace drogon {
/// Abstract class for webapp developer to get or set the Http response;
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
/**
* @brief This template is used to convert a response object to a custom
* type object. Users must specialize the template for a particular type.
*/
template <typename T>
T fromResponse(const HttpResponse &resp) {
// LOG_ERROR
// << "You must specialize the fromResponse template for the type of "
// << DrClassMap::demangle(typeid(T).name());
exit(1);
}
/**
* @brief This template is used to create a response object from a custom
* type object by calling the newCustomHttpResponse(). Users must specialize
* the template for a particular type.
*/
template <typename T>
HttpResponsePtr toResponse(T &&) {
// LOG_ERROR << "You must specialize the toResponse template for the type of "
// << DrClassMap::demangle(typeid(T).name());
exit(1);
}
template <>
HttpResponsePtr toResponse<const Json::Value &>(const Json::Value &pJson);
template <>
HttpResponsePtr toResponse(Json::Value &&pJson);
template <>
inline HttpResponsePtr toResponse<Json::Value &>(Json::Value &pJson) {
return toResponse((const Json::Value &)pJson);
}
class HttpResponse {
friend class HttpResponseParser;
public:
/**
* @brief This template enables automatic type conversion. For using this
* template, user must specialize the fromResponse template. For example a
* shared_ptr<Json::Value> specialization version is available above, so
* we can use the following code to get a json object:
* @code
* std::shared_ptr<Json::Value> jsonPtr = *responsePtr;
* @endcode
* With this template, user can use their favorite JSON library instead of
* the default jsoncpp library or convert the response to an object of any
* custom type.
*/
template <typename T>
operator T() const {
return fromResponse<T>(*this);
}
/**
* @brief This template enables explicit type conversion, see the above
* template.
*/
template <typename T>
T as() const {
}
/// Get the status code such as 200, 404
virtual HttpStatusCode statusCode() const {
return statusCode_;
}
HttpStatusCode getStatusCode() const {
return statusCode();
}
/// Set the status code of the response.
virtual void setStatusCode(HttpStatusCode code) {
statusCode_ = code;
setStatusMessage(statusCodeToString(code));
}
/// Get the creation timestamp of the response.
virtual const trantor::Date &creationDate() const {
return creationDate_;
}
const trantor::Date &getCreationDate() const {
return creationDate();
}
/// Set the http version, http1.0 or http1.1
virtual void setVersion(const Version v) {
version_ = v;
if (version_ == Version::kHttp10) {
closeConnection_ = true;
}
}
/// Set if close the connection after the request is sent.
/**
* @param on if the parameter is false, the connection keeps alive on the
* condition that the client request has a 'keep-alive' head, otherwise it
* is closed immediately after sending the last byte of the response. It's
* false by default when the response is created.
*/
virtual void setCloseConnection(bool on) {
closeConnection_ = on;
}
/// Get the status set by the setCloseConnetion() method.
virtual bool ifCloseConnection() const {
return closeConnection_;
}
/// Set the response content type, such as text/html, text/plaint, image/png
/// and so on. If the content type
/// is a text type, the character set is utf8.
virtual void setContentTypeCode(ContentType type) {
contentType_ = type;
setContentType(webContentTypeToString(type));
flagForParsingContentType_ = true;
}
/// Set the response content type and the content-type string, The string
/// must contain the header name and CRLF.
/// For example, "content-type: text/plain\r\n"
void setContentTypeCodeAndCustomString(ContentType type,
const string_view &typeString) {
setContentTypeCodeAndCustomString(type,
typeString.data(),
typeString.length());
}
template <int N>
void setContentTypeCodeAndCustomString(ContentType type,
const char (&typeString)[N]) {
assert(N > 0);
setContentTypeCodeAndCustomString(type, typeString, N - 1);
}
/// Set the reponse content type and the character set.
/// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const
/// std::string &charSet = "utf-8") = 0;
/// Get the response content type.
virtual ContentType contentType() const {
if (!flagForParsingContentType_) {
flagForParsingContentType_ = true;
auto &contentTypeString = getHeaderBy("content-type");
if (contentTypeString == "") {
contentType_ = CT_NONE;
} else {
auto pos = contentTypeString.find(';');
if (pos != std::string::npos) {
contentType_ = parseContentType(
string_view(contentTypeString.data(), pos));
} else {
contentType_ =
parseContentType(string_view(contentTypeString));
}
}
}
return contentType_;
}
ContentType getContentType() const {
return contentType();
}
const std::string &getHeaderBy(const std::string &lowerKey) const {
const static std::string defaultVal;
auto iter = headers_.find(lowerKey);
if (iter == headers_.end()) {
return defaultVal;
}
return iter->second;
}
/// Get the header string identified by the key parameter.
/**
* @note
* If there is no the header, a empty string is retured.
* The key is case insensitive
*/
virtual const std::string &getHeader(const std::string &key) const {
auto field = key;
transform(field.begin(), field.end(), field.begin(), ::tolower);
return getHeaderBy(field);
}
virtual const std::string &getHeader(std::string &&key) const {
transform(key.begin(), key.end(), key.begin(), ::tolower);
return getHeaderBy(key);
}
/// Remove the header identified by the key parameter.
virtual void removeHeader(const std::string &key) {
auto field = key;
transform(field.begin(), field.end(), field.begin(), ::tolower);
removeHeaderBy(field);
}
/// Remove the header identified by the key parameter.
virtual void removeHeader(std::string &&key) {
transform(key.begin(), key.end(), key.begin(), ::tolower);
removeHeaderBy(key);
}
void removeHeaderBy(const std::string &lowerKey) {
headers_.erase(lowerKey);
}
/// Get all headers of the response
virtual const std::unordered_map<std::string, std::string> &headers()
const {
return headers_;
}
/// Get all headers of the response
const std::unordered_map<std::string, std::string> &getHeaders() const {
return headers();
}
/// Add a header.
virtual void addHeader(const std::string &key,
const std::string &value) {
fullHeaderString_.reset();
auto field = key;
transform(field.begin(), field.end(), field.begin(), ::tolower);
headers_[std::move(field)] = value;
}
/// Add a header.
virtual void addHeader(const std::string &key, std::string &&value) {
fullHeaderString_.reset();
auto field = key;
transform(field.begin(), field.end(), field.begin(), ::tolower);
headers_[std::move(field)] = std::move(value);
}
void addHeader(const char *start, const char *colon, const char *end);
/// Add a cookie
virtual void addCookie(const std::string &key,
const std::string &value) {
cookies_[key] = Cookie(key, value);
}
/// Add a cookie
virtual void addCookie(const Cookie &cookie) {
cookies_[cookie.key()] = cookie;
}
virtual void addCookie(Cookie &&cookie) {
cookies_[cookie.key()] = std::move(cookie);
}
/// Get the cookie identified by the key parameter.
/// If there is no the cookie, the empty cookie is retured.
virtual const Cookie &getCookie(const std::string &key) const {
static const Cookie defaultCookie;
auto it = cookies_.find(key);
if (it != cookies_.end()) {
return it->second;
}
return defaultCookie;
}
/// Get all cookies.
virtual const std::unordered_map<std::string, Cookie> &cookies()
const {
return cookies_;
}
/// Get all cookies.
const std::unordered_map<std::string, Cookie> &getCookies() const {
return cookies();
}
/// Remove the cookie identified by the key parameter.
virtual void removeCookie(const std::string &key) {
cookies_.erase(key);
}
/// Set the response body(content).
/**
* @note The body must match the content type
*/
virtual void setBody(const std::string &body) {
bodyPtr_ = std::make_shared<HttpMessageStringBody>(body);
}
/// Set the response body(content).
virtual void setBody(std::string &&body) {
bodyPtr_ = std::make_shared<HttpMessageStringBody>(std::move(body));
}
/// Set the response body(content).
template <int N>
void setBody(const char (&body)[N]) {
assert(strnlen(body, N) == N - 1);
setBody(body, N - 1);
}
/// Get the response body.
string_view body() const {
return string_view{ getBodyData(), getBodyLength() };
}
/// Get the response body.
string_view getBody() const {
return body();
}
/// Return the enum type version of the response.
/**
* kHttp10 means Http version is 1.0
* kHttp11 means Http verison is 1.1
*/
virtual Version version() const {
return version_;
}
/// Return the enum type version of the response.
Version getVersion() const {
return version();
}
/// Reset the reponse object to its initial state
virtual void clear();
/// Set the expiration time of the response cache in memory.
/// in seconds, 0 means always cache, negative means not cache, default is
/// -1.
virtual void setExpiredTime(ssize_t expiredTime) {
expriedTime_ = expiredTime;
datePos_ = std::string::npos;
if (expriedTime_ < 0 && version_ == Version::kHttp10) {
fullHeaderString_.reset();
}
}
/// Get the expiration time of the response.
virtual ssize_t expiredTime() const {
return expriedTime_;
}
ssize_t getExpiredTime() const {
return expiredTime();
}
/// Get the json object from the server response.
/// If the response is not in json format, then a empty shared_ptr is
/// retured.
virtual const std::shared_ptr<Json::Value> &jsonObject() const {
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingJson_) {
flagForParsingJson_ = true;
parseJson();
}
return jsonPtr_;
}
const std::shared_ptr<Json::Value> &getJsonObject() const {
return jsonObject();
}
void setJsonObject(const Json::Value &pJson) {
flagForParsingJson_ = true;
flagForSerializingJson_ = false;
jsonPtr_ = std::make_shared<Json::Value>(pJson);
}
void setJsonObject(Json::Value &&pJson) {
flagForParsingJson_ = true;
flagForSerializingJson_ = false;
jsonPtr_ = std::make_shared<Json::Value>(std::move(pJson));
}
bool shouldBeCompressed() const;
void generateBodyFromJson() const;
const std::string &sendfileName() const {
return sendfileName_;
}
void setSendfile(const std::string &filename) {
sendfileName_ = filename;
}
void makeHeaderString() {
fullHeaderString_ = std::make_shared<trantor::MsgBuffer>(128);
makeHeaderString(*fullHeaderString_);
}
void gunzip() {
if (bodyPtr_) {
auto gunzipBody =
utils::gzipDecompress(bodyPtr_->data(), bodyPtr_->length());
removeHeader("content-encoding");
bodyPtr_ =
std::make_shared<HttpMessageStringBody>(move(gunzipBody));
}
}
#ifdef USE_BROTLI
void brDecompress() {
if (bodyPtr_) {
auto gunzipBody =
utils::brotliDecompress(bodyPtr_->data(), bodyPtr_->length());
removeHeader("content-encoding");
bodyPtr_ =
std::make_shared<HttpMessageStringBody>(move(gunzipBody));
}
}
#endif
/**
* @brief Get the error message of parsing the JSON body received from peer.
* This method usually is called after getting a empty shared_ptr object
* by the getJsonObject() method.
*
* @return const std::string& The error message. An empty string is returned
* when no error occurs.
*/
virtual const std::string &getJsonError() const {
const static std::string none{ "" };
if (jsonParsingErrorPtr_)
return *jsonParsingErrorPtr_;
return none;
}
/**
* @brief Set the reponse object to the pass-through mode or not. It's not
* by default when a new response object is created.
* In pass-through mode, no addtional headers (including server, date,
* content-type and content-length, etc.) are added to the response. This
* mode is useful for some applications such as a proxy.
*
* @param flag
*/
virtual void setPassThrough(bool flag) {
passThrough_ = flag;
}
void redirect(const std::string &url) {
headers_["location"] = url;
}
std::shared_ptr<trantor::MsgBuffer> renderToBuffer();
void renderToBuffer(trantor::MsgBuffer &buffer);
std::shared_ptr<trantor::MsgBuffer> renderHeaderForHeadMethod();
/* The following methods are a series of factory methods that help users
* create response objects. */
/// Create a normal response with a status code of 200ok and a content type
/// of text/html.
static HttpResponsePtr newHttpResponse();
/// Create a response which returns a 404 page.
static HttpResponsePtr newNotFoundResponse();
/// Create a response which returns a json object. Its content type is set
/// to set/json.
static HttpResponsePtr newHttpJsonResponse(const Json::Value &data);
static HttpResponsePtr newHttpJsonResponse(Json::Value &&data);
/// Create a response that returns a page rendered by a view named
/// viewName.
/**
* @param viewName The name of the view
* @param data is the data displayed on the page.
* @note For more details, see the wiki pages, the "View" section.
*/
static HttpResponsePtr newHttpViewResponse(
const std::string &viewName,
const HttpViewData &data = HttpViewData());
/// Create a response that returns a redirection page, redirecting to
/// another page located in the location parameter.
/**
* @param location The location to redirect
* @param status The HTTP status code, k302Found by default. Users could set
* it to one of the 301, 302, 303, 307, ...
*/
static HttpResponsePtr newRedirectionResponse(
const std::string &location,
HttpStatusCode status = k302Found);
/// Create a response that returns a file to the client.
/**
* @param fullPath is the full path to the file.
* @param attachmentFileName if the parameter is not empty, the browser
* does not open the file, but saves it as an attachment.
* @param type if the parameter is CT_NONE, the content type is set by
* drogon based on the file extension.
*/
static HttpResponsePtr newFileResponse(
const std::string &fullPath,
const std::string &attachmentFileName = "",
ContentType type = CT_NONE);
/**
* @brief Create a custom HTTP response object. For using this template,
* users must specialize the toResponse template.
*/
template <typename T>
static HttpResponsePtr newCustomHttpResponse(T &&obj) {
return toResponse(std::forward<T>(obj));
}
HttpResponse() :
creationDate_(trantor::Date::now()) {
}
HttpResponse(HttpStatusCode code, ContentType type) :
statusCode_(code),
statusMessage_(statusCodeToString(code)),
creationDate_(trantor::Date::now()),
contentType_(type),
flagForParsingContentType_(true),
contentTypeString_(webContentTypeToString(type)) {
}
virtual ~HttpResponse();
// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const
// std::string &charSet = "utf-8")
// {
// contentType_ = type;
// setContentType(webContentTypeAndCharsetToString(type, charSet));
// }
void swap(HttpResponse &that) noexcept;
protected:
void makeHeaderString(trantor::MsgBuffer &headerString);
private:
virtual void setBody(const char *body, size_t len) {
bodyPtr_ = std::make_shared<HttpMessageStringViewBody>(body, len);
}
virtual const char *getBodyData() const {
if (!flagForSerializingJson_ && jsonPtr_) {
generateBodyFromJson();
} else if (!bodyPtr_) {
return nullptr;
}
return bodyPtr_->data();
}
virtual size_t getBodyLength() const {
if (bodyPtr_)
return bodyPtr_->length();
return 0;
}
void parseJson() const;
virtual void setContentTypeCodeAndCustomString(
ContentType type,
const char *typeString,
size_t typeStringLength) {
contentType_ = type;
flagForParsingContentType_ = true;
setContentType(string_view{ typeString, typeStringLength });
}
std::unordered_map<std::string, std::string> headers_;
std::unordered_map<std::string, Cookie> cookies_;
HttpStatusCode statusCode_{ kUnknown };
string_view statusMessage_;
trantor::Date creationDate_;
Version version_{ Version::kHttp11 };
bool closeConnection_{ false };
mutable std::shared_ptr<HttpMessageBody> bodyPtr_;
ssize_t expriedTime_{ -1 };
std::string sendfileName_;
mutable std::shared_ptr<Json::Value> jsonPtr_;
std::shared_ptr<trantor::MsgBuffer> fullHeaderString_;
mutable std::shared_ptr<trantor::MsgBuffer> httpString_;
mutable size_t datePos_{ static_cast<size_t>(-1) };
mutable int64_t httpStringDate_{ -1 };
mutable bool flagForParsingJson_{ false };
mutable bool flagForSerializingJson_{ true };
mutable ContentType contentType_{ CT_TEXT_PLAIN };
mutable bool flagForParsingContentType_{ false };
mutable std::shared_ptr<std::string> jsonParsingErrorPtr_;
string_view contentTypeString_{
"Content-Type: text/html; charset=utf-8\r\n"
};
bool passThrough_{ false };
void setContentType(const string_view &contentType) {
contentTypeString_ = contentType;
}
void setStatusMessage(const string_view &message) {
statusMessage_ = message;
}
};
template <>
inline HttpResponsePtr toResponse<const Json::Value &>(const Json::Value &pJson) {
return HttpResponse::newHttpJsonResponse(pJson);
}
template <>
inline HttpResponsePtr toResponse(Json::Value &&pJson) {
return HttpResponse::newHttpJsonResponse(std::move(pJson));
}
template <>
inline std::shared_ptr<Json::Value> fromResponse(const HttpResponse &resp) {
return resp.getJsonObject();
}
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
inline void swap(HttpResponse &one, HttpResponse &two) noexcept {
one.swap(two);
}
} // namespace drogon

View File

@ -1,19 +1,25 @@
#include "http_server.h"
#include "request.h"
#include <functional>
#include "application.h"
#include "request.h"
#include <trantor/utils/Logger.h>
#define LOG_VERBOSE 0
using namespace std::placeholders;
void HTTPServer::http_callback_handler(Request *request) {
Application::handle_request(request);
Application::handle_request(request);
}
void HTTPServer::httpEnterCallbackDefault(const HTTPParser &httpParser, const HttpSession::Ptr &session) {
Request *request = RequestPool::get_request();
request->http_parser = &httpParser;
request->session = &session;
request->http_parser = &httpParser;
request->session = &session;
request->setup_url_stack();
@ -21,7 +27,7 @@ void HTTPServer::httpEnterCallbackDefault(const HTTPParser &httpParser, const Ht
std::cout << "method:" << http_method_str(static_cast<http_method>(httpParser.method())) << std::endl;
#endif
http_callback_handler(request);
http_callback_handler(request);
}
void HTTPServer::wsEnterCallbackDefault(const HttpSession::Ptr &httpSession, WebSocketFormat::WebSocketFrameType opcode, const std::string &payload) {
@ -87,10 +93,195 @@ void HTTPServer::main_loop_old() {
}
}
void HTTPServer::configure() {
}
void HTTPServer::initialize() {
}
//drogon -> void HttpAppFrameworkImpl::run()
void HTTPServer::main_loop() {
if (!getLoop()->isInLoopThread()) {
getLoop()->moveToCurrentThread();
}
LOG_TRACE << "Start to run...";
/*
trantor::AsyncFileLogger asyncFileLogger;
// Create dirs for cache files
for (int i = 0; i < 256; ++i) {
char dirName[4];
snprintf(dirName, sizeof(dirName), "%02x", i);
std::transform(dirName, dirName + 2, dirName, toupper);
utils::createPath(getUploadPath() + "/tmp/" + dirName);
}
*/
/*
if (runAsDaemon_) {
// go daemon!
godaemon();
#ifdef __linux__
getLoop()->resetTimerQueue();
#endif
getLoop()->resetAfterFork();
}
*/
/*
// set relaunching
if (relaunchOnError_) {
#ifndef _WIN32
while (true) {
int child_status = 0;
auto child_pid = fork();
if (child_pid < 0) {
LOG_ERROR << "fork error";
abort();
} else if (child_pid == 0) {
// child
break;
}
waitpid(child_pid, &child_status, 0);
sleep(1);
LOG_INFO << "start new process";
}
getLoop()->resetAfterFork();
#endif
}*/
//signal(SIGTERM, TERMFunction);
/*
// set logger
if (!logPath_.empty()) {
#ifdef _WIN32
if (_access(logPath_.c_str(), 06) != 0)
#else
if (access(logPath_.c_str(), R_OK | W_OK) != 0)
#endif
{
LOG_ERROR << "log file path not exist";
abort();
} else {
std::string baseName = logfileBaseName_;
if (baseName == "") {
baseName = "drogon";
}
asyncFileLogger.setFileName(baseName, ".log", logPath_);
asyncFileLogger.startLogging();
trantor::Logger::setOutputFunction(
[&](const char *msg, const uint64_t len) {
asyncFileLogger.output(msg, len);
},
[&]() { asyncFileLogger.flush(); });
asyncFileLogger.setFileSizeLimit(logfileSize_);
}
}
*/
/*
if (relaunchOnError_) {
LOG_INFO << "Start child process";
}
*/
// now start runing!!
_running = true;
/*
#ifndef _WIN32
if (!libFilePaths_.empty()) {
sharedLibManagerPtr_ = std::unique_ptr<SharedLibManager>(
new SharedLibManager(libFilePaths_, libFileOutputPath_));
}
#endif
*/
// Create all listeners.
auto ioLoops = listenerManagerPtr_->createListeners(
std::bind(&HttpAppFrameworkImpl::onAsyncRequest, this, _1, _2),
std::bind(&HttpAppFrameworkImpl::onNewWebsockRequest, this, _1, _2, _3),
std::bind(&HttpAppFrameworkImpl::onConnection, this, _1),
idleConnectionTimeout_,
sslCertPath_,
sslKeyPath_,
threads,
syncAdvices_);
assert(ioLoops.size() == threads);
for (size_t i = 0; i < threads; ++i) {
ioLoops[i]->setIndex(i);
}
getLoop()->setIndex(threads);
// A fast database client instance should be created in the main event
// loop, so put the main loop into ioLoops.
ioLoops.push_back(getLoop());
/*
dbClientManagerPtr_->createDbClients(ioLoops);
httpCtrlsRouterPtr_->init(ioLoops);
httpSimpleCtrlsRouterPtr_->init(ioLoops);
staticFileRouterPtr_->init(ioLoops);
websockCtrlsRouterPtr_->init();
*/
getLoop()->queueInLoop([this]() {
// Let listener event loops run when everything is ready.
listenerManagerPtr_->startListening();
for (auto &adv : beginningAdvices_) {
adv();
}
beginningAdvices_.clear();
});
getLoop()->loop();
}
trantor::EventLoop *HttpAppFrameworkImpl::get_loop() const
{
static trantor::EventLoop loop;
return &loop;
}
HTTPServer::HTTPServer() {
port = 80;
threads = 4;
listenBuilder = nullptr;
_running = false;
}
HttpServer::HTTPServer(
EventLoop *loop,
const InetAddress &listenAddr,
const std::string &name,
const std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)>>
&syncAdvices)
#ifdef __linux__
: server_(loop, listenAddr, name.c_str()),
#else
: server_(loop, listenAddr, name.c_str(), true, false),
#endif
httpAsyncCallback_(defaultHttpAsyncCallback),
newWebsocketCallback_(defaultWebSockAsyncCallback),
connectionCallback_(defaultConnectionCallback),
syncAdvices_(syncAdvices)
{
server_.setConnectionCallback(
std::bind(&HttpServer::onConnection, this, _1));
server_.setRecvMessageCallback(
std::bind(&HttpServer::onMessage, this, _1, _2));
}
HTTPServer::~HTTPServer() {

View File

@ -12,6 +12,13 @@
#include <brynet/net/wrapper/HttpServiceBuilder.hpp>
#include <brynet/net/wrapper/ServiceBuilder.hpp>
#include <trantor/net/TcpServer.h>
#include <trantor/net/callbacks.h>
#include <trantor/utils/NonCopyable.h>
#include "core/http_server_callbacks.h"
#include "core/http_request_parser.h"
using namespace brynet;
using namespace brynet::net;
using namespace brynet::net::http;
@ -23,25 +30,78 @@ public:
int port;
int threads;
std::shared_ptr<TcpService> service;
wrapper::HttpListenerBuilder *listenBuilder;
wrapper::HttpListenerBuilder *listenBuilder;
static void http_callback_handler(Request *response);
static void http_callback_handler(Request *response);
static void httpEnterCallbackDefault(const HTTPParser &httpParser, const HttpSession::Ptr &session);
static void wsEnterCallbackDefault(const HttpSession::Ptr &httpSession, WebSocketFormat::WebSocketFrameType opcode, const std::string &payload);
virtual void configure_old();
virtual void configure_old();
virtual void initialize_old();
void main_loop_old();
//virtual void configure();
//virtual void initialize();
virtual void configure();
virtual void initialize();
//void main_loop();
void main_loop();
HTTPServer();
virtual ~HTTPServer();
trantor::EventLoop *getLoop() const {
return server_.getLoop();
}
void setHttpAsyncCallback(const HttpAsyncCallback &cb) {
httpAsyncCallback_ = cb;
}
void setNewWebsocketCallback(const WebSocketNewAsyncCallback &cb) {
newWebsocketCallback_ = cb;
}
void setConnectionCallback(const trantor::ConnectionCallback &cb) {
connectionCallback_ = cb;
}
void setIoLoopThreadPool(
const std::shared_ptr<trantor::EventLoopThreadPool> &pool) {
server_.setIoLoopThreadPool(pool);
}
void setIoLoopNum(int numThreads) {
server_.setIoLoopNum(numThreads);
}
void kickoffIdleConnections(size_t timeout) {
server_.kickoffIdleConnections(timeout);
}
trantor::EventLoop *getLoop() {
return server_.getLoop();
}
std::vector<trantor::EventLoop *> getIoLoops() {
return server_.getIoLoops();
}
void start();
void stop();
void enableSSL(const std::string &certPath, const std::string &keyPath) {
server_.enableSSL(certPath, keyPath);
}
HTTPServer();
HTTPServer(trantor::EventLoop *loop, const trantor::InetAddress &listenAddr, const std::string &name,
const std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)> > &syncAdvices);
virtual ~HTTPServer();
protected:
void onConnection(const trantor::TcpConnectionPtr &conn);
void onMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
void onRequests(const trantor::TcpConnectionPtr &, const std::vector<HttpRequestPtr> &, const std::shared_ptr<drogon::HttpRequestParser> &);
void sendResponse(const trantor::TcpConnectionPtr &, const HttpResponsePtr &, bool isHeadMethod);
void sendResponses(const trantor::TcpConnectionPtr &conn, const std::vector<std::pair<HttpResponsePtr, bool> > &responses, trantor::MsgBuffer &buffer);
trantor::TcpServer server_;
HttpAsyncCallback httpAsyncCallback_;
WebSocketNewAsyncCallback newWebsocketCallback_;
trantor::ConnectionCallback connectionCallback_;
const std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)> > &syncAdvices_;
};
#endif

View File

@ -0,0 +1,19 @@
#ifndef HTTP_SERVER_CALLBACKS_H
#define HTTP_SERVER_CALLBACKS_H
#include <functional>
#include <memory>
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
class WebSocketConnectionImpl;
using WebSocketConnectionPtr = std::shared_ptr<WebSocketConnectionImpl>;
using HttpAsyncCallback = std::function<void(const HttpRequestPtr &,std::function<void(const HttpResponsePtr &)> &&)>;
using WebSocketNewAsyncCallback = std::function<void(const HttpRequestPtr &, std::function<void(const HttpResponsePtr &)> &&,
const WebSocketConnectionPtr &)>;
#endif

144
core/http_types.h Normal file
View File

@ -0,0 +1,144 @@
/**
* HttpTypes.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <atomic>
#include <thread>
namespace drogon
{
enum HttpStatusCode
{
// rfc2616-6.1.1
kUnknown = 0,
k100Continue = 100,
k101SwitchingProtocols = 101,
k200OK = 200,
k201Created = 201,
k202Accepted = 202,
k203NonAuthoritativeInformation = 203,
k204NoContent = 204,
k205ResetContent = 205,
k206PartialContent = 206,
k300MultipleChoices = 300,
k301MovedPermanently = 301,
k302Found = 302,
k303SeeOther = 303,
k304NotModified = 304,
k305UseProxy = 305,
k307TemporaryRedirect = 307,
k308PermanentRedirect = 308,
k400BadRequest = 400,
k401Unauthorized = 401,
k402PaymentRequired = 402,
k403Forbidden = 403,
k404NotFound = 404,
k405MethodNotAllowed = 405,
k406NotAcceptable = 406,
k407ProxyAuthenticationRequired = 407,
k408RequestTimeout = 408,
k409Conflict = 409,
k410Gone = 410,
k411LengthRequired = 411,
k412PreconditionFailed = 412,
k413RequestEntityTooLarge = 413,
k414RequestURITooLarge = 414,
k415UnsupportedMediaType = 415,
k416RequestedRangeNotSatisfiable = 416,
k417ExpectationFailed = 417,
k421MisdirectedRequest = 421,
k425TooEarly = 425,
k426UpgradeRequired = 426,
k428PreconditionRequired = 428,
k429TooManyRequests = 429,
k431RequestHeaderFieldsTooLarge = 431,
k451UnavailableForLegalReasons = 451,
k500InternalServerError = 500,
k501NotImplemented = 501,
k502BadGateway = 502,
k503ServiceUnavailable = 503,
k504GatewayTimeout = 504,
k505HTTPVersionNotSupported = 505,
k510NotExtended = 510,
};
enum class Version
{
kUnknown = 0,
kHttp10,
kHttp11
};
enum ContentType
{
CT_NONE = 0,
CT_APPLICATION_JSON,
CT_TEXT_PLAIN,
CT_TEXT_HTML,
CT_APPLICATION_X_FORM,
CT_APPLICATION_X_JAVASCRIPT,
CT_TEXT_CSS,
CT_TEXT_XML,
CT_APPLICATION_XML,
CT_TEXT_XSL,
CT_APPLICATION_WASM,
CT_APPLICATION_OCTET_STREAM,
CT_APPLICATION_X_FONT_TRUETYPE,
CT_APPLICATION_X_FONT_OPENTYPE,
CT_APPLICATION_FONT_WOFF,
CT_APPLICATION_FONT_WOFF2,
CT_APPLICATION_VND_MS_FONTOBJ,
CT_APPLICATION_PDF,
CT_IMAGE_SVG_XML,
CT_IMAGE_PNG,
CT_IMAGE_JPG,
CT_IMAGE_GIF,
CT_IMAGE_XICON,
CT_IMAGE_ICNS,
CT_IMAGE_BMP,
CT_MULTIPART_FORM_DATA,
CT_CUSTOM
};
enum HttpMethod
{
Get = 0,
Post,
Head,
Put,
Delete,
Options,
Patch,
Invalid
};
enum class ReqResult
{
Ok,
BadResponse,
NetworkFailure,
BadServerAddress,
Timeout
};
enum class WebSocketMessageType
{
Text,
Binary,
Ping,
Pong,
Close,
Unknown
};
} // namespace drogon

580
core/http_utils.cpp Normal file
View File

@ -0,0 +1,580 @@
/**
*
* @file HttpUtils.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "core/http_utils.h"
#include "core/utilities.h"
#include <trantor/utils/Logger.h>
#include <unordered_map>
namespace drogon
{
const string_view &webContentTypeToString(ContentType contenttype)
{
switch (contenttype)
{
case CT_TEXT_HTML:
{
static string_view sv =
"Content-Type: text/html; charset=utf-8\r\n";
return sv;
}
case CT_APPLICATION_X_FORM:
{
static string_view sv =
"Content-Type: application/x-www-form-urlencoded\r\n";
return sv;
}
case CT_APPLICATION_XML:
{
static string_view sv =
"Content-Type: application/xml; charset=utf-8\r\n";
return sv;
}
case CT_APPLICATION_JSON:
{
static string_view sv =
"Content-Type: application/json; charset=utf-8\r\n";
return sv;
}
case CT_APPLICATION_X_JAVASCRIPT:
{
static string_view sv =
"Content-Type: application/x-javascript; charset=utf-8\r\n";
return sv;
}
case CT_TEXT_CSS:
{
static string_view sv = "Content-Type: text/css; charset=utf-8\r\n";
return sv;
}
case CT_TEXT_XML:
{
static string_view sv = "Content-Type: text/xml; charset=utf-8\r\n";
return sv;
}
case CT_TEXT_XSL:
{
static string_view sv = "Content-Type: text/xsl; charset=utf-8\r\n";
return sv;
}
case CT_APPLICATION_OCTET_STREAM:
{
static string_view sv =
"Content-Type: application/octet-stream\r\n";
return sv;
}
case CT_IMAGE_SVG_XML:
{
static string_view sv = "Content-Type: image/svg+xml\r\n";
return sv;
}
case CT_APPLICATION_X_FONT_TRUETYPE:
{
static string_view sv =
"Content-Type: application/x-font-truetype\r\n";
return sv;
}
case CT_APPLICATION_X_FONT_OPENTYPE:
{
static string_view sv =
"Content-Type: application/x-font-opentype\r\n";
return sv;
}
case CT_APPLICATION_FONT_WOFF:
{
static string_view sv = "Content-Type: application/font-woff\r\n";
return sv;
}
case CT_APPLICATION_FONT_WOFF2:
{
static string_view sv = "Content-Type: application/font-woff2\r\n";
return sv;
}
case CT_APPLICATION_VND_MS_FONTOBJ:
{
static string_view sv =
"Content-Type: application/vnd.ms-fontobject\r\n";
return sv;
}
case CT_APPLICATION_PDF:
{
static string_view sv = "Content-Type: application/pdf\r\n";
return sv;
}
case CT_IMAGE_PNG:
{
static string_view sv = "Content-Type: image/png\r\n";
return sv;
}
case CT_IMAGE_JPG:
{
static string_view sv = "Content-Type: image/jpeg\r\n";
return sv;
}
case CT_IMAGE_GIF:
{
static string_view sv = "Content-Type: image/gif\r\n";
return sv;
}
case CT_IMAGE_XICON:
{
static string_view sv = "Content-Type: image/x-icon\r\n";
return sv;
}
case CT_IMAGE_BMP:
{
static string_view sv = "Content-Type: image/bmp\r\n";
return sv;
}
case CT_IMAGE_ICNS:
{
static string_view sv = "Content-Type: image/icns\r\n";
return sv;
}
case CT_APPLICATION_WASM:
{
static string_view sv = "Content-Type: application/wasm\r\n";
return sv;
}
default:
case CT_TEXT_PLAIN:
{
static string_view sv =
"Content-Type: text/plain; charset=utf-8\r\n";
return sv;
}
}
}
const string_view &statusCodeToString(int code)
{
switch (code)
{
case 100:
{
static string_view sv = "Continue";
return sv;
}
case 101:
{
static string_view sv = "Switching Protocols";
return sv;
}
case 200:
{
static string_view sv = "OK";
return sv;
}
case 201:
{
static string_view sv = "Created";
return sv;
}
case 202:
{
static string_view sv = "Accepted";
return sv;
}
case 203:
{
static string_view sv = "Non-Authoritative Information";
return sv;
}
case 204:
{
static string_view sv = "No Content";
return sv;
}
case 205:
{
static string_view sv = "Reset Content";
return sv;
}
case 206:
{
static string_view sv = "Partial Content";
return sv;
}
case 300:
{
static string_view sv = "Multiple Choices";
return sv;
}
case 301:
{
static string_view sv = "Moved Permanently";
return sv;
}
case 302:
{
static string_view sv = "Found";
return sv;
}
case 303:
{
static string_view sv = "See Other";
return sv;
}
case 304:
{
static string_view sv = "Not Modified";
return sv;
}
case 305:
{
static string_view sv = "Use Proxy";
return sv;
}
case 307:
{
static string_view sv = "Temporary Redirect";
return sv;
}
case 308:
{
static string_view sv = "Permanent Redirect";
return sv;
}
case 400:
{
static string_view sv = "Bad Request";
return sv;
}
case 401:
{
static string_view sv = "Unauthorized";
return sv;
}
case 402:
{
static string_view sv = "Payment Required";
return sv;
}
case 403:
{
static string_view sv = "Forbidden";
return sv;
}
case 404:
{
static string_view sv = "Not Found";
return sv;
}
case 405:
{
static string_view sv = "Method Not Allowed";
return sv;
}
case 406:
{
static string_view sv = "Not Acceptable";
return sv;
}
case 407:
{
static string_view sv = "Proxy Authentication Required";
return sv;
}
case 408:
{
static string_view sv = "Request Time-out";
return sv;
}
case 409:
{
static string_view sv = "Conflict";
return sv;
}
case 410:
{
static string_view sv = "Gone";
return sv;
}
case 411:
{
static string_view sv = "Length Required";
return sv;
}
case 412:
{
static string_view sv = "Precondition Failed";
return sv;
}
case 413:
{
static string_view sv = "Request Entity Too Large";
return sv;
}
case 414:
{
static string_view sv = "Request-URI Too Large";
return sv;
}
case 415:
{
static string_view sv = "Unsupported Media Type";
return sv;
}
case 416:
{
static string_view sv = "Requested Range Not Satisfiable";
return sv;
}
case 417:
{
static string_view sv = "Expectation Failed";
return sv;
}
case 421:
{
static string_view sv = "Misdirected Request";
return sv;
}
case 425:
{
static string_view sv = "Too Early";
return sv;
}
case 426:
{
static string_view sv = "Upgrade Required";
return sv;
}
case 428:
{
static string_view sv = "Precondition Required";
return sv;
}
case 429:
{
static string_view sv = "Too Many Requests";
return sv;
}
case 431:
{
static string_view sv = "Request Header Fields Too Large";
return sv;
}
case 451:
{
static string_view sv = "Unavailable For Legal Reasons";
return sv;
}
case 500:
{
static string_view sv = "Internal Server Error";
return sv;
}
case 501:
{
static string_view sv = "Not Implemented";
return sv;
}
case 502:
{
static string_view sv = "Bad Gateway";
return sv;
}
case 503:
{
static string_view sv = "Service Unavailable";
return sv;
}
case 504:
{
static string_view sv = "Gateway Time-out";
return sv;
}
case 505:
{
static string_view sv = "HTTP Version Not Supported";
return sv;
}
case 510:
{
static string_view sv = "Not Extended";
return sv;
}
default:
if (code >= 100 && code < 200)
{
static string_view sv = "Informational";
return sv;
}
else if (code >= 200 && code < 300)
{
static string_view sv = "Successful";
return sv;
}
else if (code >= 300 && code < 400)
{
static string_view sv = "Redirection";
return sv;
}
else if (code >= 400 && code < 500)
{
static string_view sv = "Bad Request";
return sv;
}
else if (code >= 500 && code < 600)
{
static string_view sv = "Server Error";
return sv;
}
else
{
static string_view sv = "Undefined Error";
return sv;
}
}
}
ContentType getContentType(const std::string &fileName)
{
std::string extName;
auto pos = fileName.rfind('.');
if (pos != std::string::npos)
{
extName = fileName.substr(pos + 1);
transform(extName.begin(), extName.end(), extName.begin(), tolower);
}
switch (extName.length())
{
case 0:
return CT_APPLICATION_OCTET_STREAM;
case 2:
{
if (extName == "js")
return CT_APPLICATION_X_JAVASCRIPT;
return CT_APPLICATION_OCTET_STREAM;
}
case 3:
{
switch (extName[0])
{
case 'b':
if (extName == "bmp")
return CT_IMAGE_BMP;
break;
case 'c':
if (extName == "css")
return CT_TEXT_CSS;
break;
case 'e':
if (extName == "eot")
return CT_APPLICATION_VND_MS_FONTOBJ;
break;
case 'g':
if (extName == "gif")
return CT_IMAGE_GIF;
break;
case 'i':
if (extName == "ico")
return CT_IMAGE_XICON;
break;
case 'j':
if (extName == "jpg")
return CT_IMAGE_JPG;
break;
case 'o':
if (extName == "otf")
return CT_APPLICATION_X_FONT_OPENTYPE;
break;
case 'p':
if (extName == "png")
return CT_IMAGE_PNG;
else if (extName == "pdf")
return CT_APPLICATION_PDF;
break;
case 's':
if (extName == "svg")
return CT_IMAGE_SVG_XML;
break;
case 't':
if (extName == "txt")
return CT_TEXT_PLAIN;
else if (extName == "ttf")
return CT_APPLICATION_X_FONT_TRUETYPE;
break;
case 'x':
if (extName == "xml")
return CT_TEXT_XML;
else if (extName == "xsl")
return CT_TEXT_XSL;
break;
default:
break;
}
return CT_APPLICATION_OCTET_STREAM;
}
case 4:
{
if (extName == "html")
return CT_TEXT_HTML;
else if (extName == "jpeg")
return CT_IMAGE_JPG;
else if (extName == "icns")
return CT_IMAGE_ICNS;
else if (extName == "woff")
return CT_APPLICATION_FONT_WOFF;
return CT_APPLICATION_OCTET_STREAM;
}
case 5:
{
if (extName == "woff2")
return CT_APPLICATION_FONT_WOFF2;
return CT_APPLICATION_OCTET_STREAM;
}
default:
return CT_APPLICATION_OCTET_STREAM;
}
}
ContentType parseContentType(const string_view &contentType)
{
static const std::unordered_map<string_view, ContentType> map_{
{"text/html", CT_TEXT_HTML},
{"application/x-www-form-urlencoded", CT_APPLICATION_X_FORM},
{"application/xml", CT_APPLICATION_XML},
{"application/json", CT_APPLICATION_JSON},
{"application/x-javascript", CT_APPLICATION_X_JAVASCRIPT},
{"text/css", CT_TEXT_CSS},
{"text/xml", CT_TEXT_XML},
{"text/xsl", CT_TEXT_XSL},
{"application/octet-stream", CT_APPLICATION_OCTET_STREAM},
{"image/svg+xml", CT_IMAGE_SVG_XML},
{"application/x-font-truetype", CT_APPLICATION_X_FONT_TRUETYPE},
{"application/x-font-opentype", CT_APPLICATION_X_FONT_OPENTYPE},
{"application/font-woff", CT_APPLICATION_FONT_WOFF},
{"application/font-woff2", CT_APPLICATION_FONT_WOFF2},
{"application/vnd.ms-fontobject", CT_APPLICATION_VND_MS_FONTOBJ},
{"application/pdf", CT_APPLICATION_PDF},
{"image/png", CT_IMAGE_PNG},
{"image/jpeg", CT_IMAGE_JPG},
{"image/gif", CT_IMAGE_GIF},
{"image/x-icon", CT_IMAGE_XICON},
{"image/bmp", CT_IMAGE_BMP},
{"image/icns", CT_IMAGE_ICNS},
{"application/wasm", CT_APPLICATION_WASM},
{"text/plain", CT_TEXT_PLAIN},
{"multipart/form-data", CT_MULTIPART_FORM_DATA}};
auto iter = map_.find(contentType);
if (iter == map_.end())
return CT_NONE;
return iter->second;
}
} // namespace drogon

58
core/http_utils.h Normal file
View File

@ -0,0 +1,58 @@
/**
*
* HttpUtils.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/string_view.h>
#include <core/http_types.h>
#include <string>
#include <trantor/utils/MsgBuffer.h>
namespace drogon
{
ContentType parseContentType(const string_view &contentType);
const string_view &webContentTypeToString(ContentType contenttype);
const string_view &statusCodeToString(int code);
ContentType getContentType(const std::string &fileName);
template <typename T>
inline constexpr const char *contentLengthFormatString()
{
return "Content-Length: %d\r\n";
}
template <>
inline constexpr const char *contentLengthFormatString<unsigned int>()
{
return "Content-Length: %u\r\n";
}
template <>
inline constexpr const char *contentLengthFormatString<long>()
{
return "Content-Length: %ld\r\n";
}
template <>
inline constexpr const char *contentLengthFormatString<unsigned long>()
{
return "Content-Length: %lu\r\n";
}
template <>
inline constexpr const char *contentLengthFormatString<long long>()
{
return "Content-Length: %lld\r\n";
}
template <>
inline constexpr const char *contentLengthFormatString<unsigned long long>()
{
return "Content-Length: %llu\r\n";
}
} // namespace drogon

191
core/http_view_data.h Normal file
View File

@ -0,0 +1,191 @@
/**
*
* HttpViewData.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/string_view.h>
#include <core/any.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <sstream>
#include <string>
#include <unordered_map>
#include <stdarg.h>
#include <stdio.h>
namespace drogon
{
/// This class represents the data set displayed in views.
class HttpViewData
{
public:
/// The function template is used to get an item in the data set by the key
/// parameter.
template <typename T>
const T &get(const std::string &key) const
{
const static T nullVal = T();
auto it = viewData_.find(key);
if (it != viewData_.end())
{
if (typeid(T) == it->second.type())
{
return *(any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
return nullVal;
}
/// Insert an item identified by the key parameter into the data set;
void insert(const std::string &key, any &&obj)
{
viewData_[key] = std::move(obj);
}
void insert(const std::string &key, const any &obj)
{
viewData_[key] = obj;
}
/// Insert an item identified by the key parameter into the data set; The
/// item is converted to a string.
template <typename T>
void insertAsString(const std::string &key, T &&val)
{
std::stringstream ss;
ss << val;
viewData_[key] = ss.str();
}
/// Insert a formated string identified by the key parameter.
void insertFormattedString(const std::string &key, const char *format, ...)
{
std::string strBuffer;
strBuffer.resize(128);
va_list ap, backup_ap;
va_start(ap, format);
va_copy(backup_ap, ap);
auto result = vsnprintf((char *)strBuffer.data(),
strBuffer.size(),
format,
backup_ap);
va_end(backup_ap);
if ((result >= 0) &&
((std::string::size_type)result < strBuffer.size()))
{
strBuffer.resize(result);
}
else
{
while (true)
{
if (result < 0)
{
// Older snprintf() behavior. Just try doubling the buffer
// size
strBuffer.resize(strBuffer.size() * 2);
}
else
{
strBuffer.resize(result + 1);
}
va_copy(backup_ap, ap);
auto result = vsnprintf((char *)strBuffer.data(),
strBuffer.size(),
format,
backup_ap);
va_end(backup_ap);
if ((result >= 0) &&
((std::string::size_type)result < strBuffer.size()))
{
strBuffer.resize(result);
break;
}
}
}
va_end(ap);
viewData_[key] = std::move(strBuffer);
}
/// Get the 'any' object by the key parameter.
any &operator[](const std::string &key) const
{
return viewData_[key];
}
/// Translate some special characters to HTML format
/**
* such as:
* @code
" --> &quot;
& --> &amp;
< --> &lt;
> --> &gt;
@endcode
*/
static std::string htmlTranslate(const char *str, size_t length);
static std::string htmlTranslate(const std::string &str)
{
return htmlTranslate(str.data(), str.length());
}
static std::string htmlTranslate(const string_view &str)
{
return htmlTranslate(str.data(), str.length());
}
static bool needTranslation(const std::string &str)
{
for (auto const &c : str)
{
switch (c)
{
case '"':
case '&':
case '<':
case '>':
return true;
default:
continue;
}
}
return false;
}
static bool needTranslation(const string_view &str)
{
for (auto const &c : str)
{
switch (c)
{
case '"':
case '&':
case '<':
case '>':
return true;
default:
continue;
}
}
return false;
}
protected:
using ViewDataMap = std::unordered_map<std::string, any>;
mutable ViewDataMap viewData_;
};
} // namespace drogon

162
core/io_thread_storage.h Normal file
View File

@ -0,0 +1,162 @@
/**
*
* IOThreadStorage.h
* Daniel Mensinger
*
* Copyright 2019, Daniel Mensinger. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
//#include <drogon/HttpAppFramework.h>
#include <trantor/utils/NonCopyable.h>
#include <memory>
#include <vector>
#include <limits>
#include <functional>
namespace drogon
{
/**
* @brief Utility class for thread storage handling
*
* Thread storage allows the efficient handling of reusable data without thread
* synchronisation. For instance, such a thread storage would be useful to store
* database connections.
*
* Example usage:
*
* @code
* struct MyThreadData {
* int threadLocal = 42;
* std::string something = "foo";
* };
*
* class MyController : public HttpController<MyController> {
* public:
* METHOD_LIST_BEGIN
* ADD_METHOD_TO(MyController::endpoint, "/some/path", Get);
* METHOD_LIST_END
*
* void login(const HttpRequestPtr &req,
* std::function<void (const HttpResponsePtr &)> &&callback) {
* assert(storage_->threadLocal == 42);
*
* // handle the request
* }
*
* private:
* IOThreadStorage<MyThreadData> storage_;
* };
* @endcode
*/
template <typename C>
class IOThreadStorage : public trantor::NonCopyable
{
public:
using ValueType = C;
using InitCallback = std::function<void(ValueType &, size_t)>;
template <typename... Args>
IOThreadStorage(Args &&... args)
{
static_assert(std::is_constructible<C, Args &&...>::value,
"Unable to construct storage with given signature");
size_t numThreads = app().getThreadNum();
#ifdef _WIN32
assert(numThreads > 0 && numThreads != size_t(-1));
#else
assert(numThreads > 0 &&
numThreads != std::numeric_limits<size_t>::max());
#endif
// set the size to numThreads+1 to enable access to this in the main
// thread.
storage_.reserve(numThreads + 1);
for (size_t i = 0; i <= numThreads; ++i)
{
storage_.emplace_back(std::forward<Args>(args)...);
}
}
void init(const InitCallback &initCB)
{
for (size_t i = 0; i < storage_.size(); ++i)
{
initCB(storage_[i], i);
}
}
/**
* @brief Get the thread storage asociate with the current thread
*
* This function may only be called in a request handler
*/
inline ValueType &getThreadData()
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return storage_[idx];
}
inline const ValueType &getThreadData() const
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return storage_[idx];
}
/**
* @brief Sets the thread data for the current thread
*
* This function may only be called in a request handler
*/
inline void setThreadData(const ValueType &newData)
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
storage_[idx] = newData;
}
inline void setThreadData(ValueType &&newData)
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
storage_[idx] = std::move(newData);
}
inline ValueType *operator->()
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return &storage_[idx];
}
inline ValueType &operator*()
{
return getThreadData();
}
inline const ValueType *operator->() const
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return &storage_[idx];
}
inline const ValueType &operator*() const
{
return getThreadData();
}
private:
std::vector<ValueType> storage_;
};
} // namespace drogon

241
core/listener_manager.cpp Normal file
View File

@ -0,0 +1,241 @@
/**
*
* ListenerManager.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "listener_manager.h"
#include "http_server.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <trantor/utils/Logger.h>
#ifndef _WIN32
#include <sys/file.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
namespace drogon {
#ifndef _WIN32
class DrogonFileLocker : public trantor::NonCopyable {
public:
DrogonFileLocker() {
fd_ = open("/tmp/drogon.lock", O_TRUNC | O_CREAT, 0755);
flock(fd_, LOCK_EX);
}
~DrogonFileLocker() {
close(fd_);
}
private:
int fd_{ 0 };
};
#endif
} // namespace drogon
using namespace trantor;
using namespace drogon;
void ListenerManager::addListener(const std::string &ip, uint16_t port, bool useSSL, const std::string &certFile, const std::string &keyFile) {
#ifndef OpenSSL_FOUND
if (useSSL) {
LOG_ERROR << "Can't use SSL without OpenSSL found in your system";
}
#endif
listeners_.emplace_back(ip, port, useSSL, certFile, keyFile);
}
std::vector<trantor::EventLoop *> ListenerManager::createListeners(
const HttpAsyncCallback &httpCallback,
const WebSocketNewAsyncCallback &webSocketCallback,
const ConnectionCallback &connectionCallback,
size_t connectionTimeout,
const std::string &globalCertFile,
const std::string &globalKeyFile,
size_t threadNum,
const std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)> > &syncAdvices) {
#ifdef __linux__
for (size_t i = 0; i < threadNum; ++i) {
LOG_TRACE << "thread num=" << threadNum;
auto loopThreadPtr = std::make_shared<EventLoopThread>("DrogonIoLoop");
listeningloopThreads_.push_back(loopThreadPtr);
ioLoops_.push_back(loopThreadPtr->getLoop());
for (auto const &listener : listeners_) {
auto const &ip = listener.ip_;
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
std::shared_ptr<HttpServer> serverPtr;
if (i == 0) {
DrogonFileLocker lock;
// Check whether the port is in use.
TcpServer server(HttpAppFrameworkImpl::instance().getLoop(),
InetAddress(ip, listener.port_, isIpv6),
"drogonPortTest",
true,
false);
serverPtr = std::make_shared<HttpServer>(
loopThreadPtr->getLoop(),
InetAddress(ip, listener.port_, isIpv6),
"drogon",
syncAdvices);
} else {
serverPtr = std::make_shared<HttpServer>(
loopThreadPtr->getLoop(),
InetAddress(ip, listener.port_, isIpv6),
"drogon",
syncAdvices);
}
if (listener.useSSL_) {
#ifdef OpenSSL_FOUND
auto cert = listener.certFile_;
auto key = listener.keyFile_;
if (cert == "")
cert = globalCertFile;
if (key == "")
key = globalKeyFile;
if (cert == "" || key == "") {
std::cerr
<< "You can't use https without cert file or key file"
<< std::endl;
exit(1);
}
serverPtr->enableSSL(cert, key);
#endif
}
serverPtr->setHttpAsyncCallback(httpCallback);
serverPtr->setNewWebsocketCallback(webSocketCallback);
serverPtr->setConnectionCallback(connectionCallback);
serverPtr->kickoffIdleConnections(connectionTimeout);
serverPtr->start();
servers_.push_back(serverPtr);
}
}
#else
auto loopThreadPtr =
std::make_shared<EventLoopThread>("DrogonListeningLoop");
listeningloopThreads_.push_back(loopThreadPtr);
ioLoopThreadPoolPtr_ = std::make_shared<EventLoopThreadPool>(threadNum);
for (auto const &listener : listeners_) {
LOG_TRACE << "thread num=" << threadNum;
auto ip = listener.ip_;
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
auto serverPtr = std::make_shared<HttpServer>(
loopThreadPtr->getLoop(),
InetAddress(ip, listener.port_, isIpv6),
"drogon",
syncAdvices);
if (listener.useSSL_) {
#ifdef OpenSSL_FOUND
auto cert = listener.certFile_;
auto key = listener.keyFile_;
if (cert == "")
cert = globalCertFile;
if (key == "")
key = globalKeyFile;
if (cert == "" || key == "") {
std::cerr << "You can't use https without cert file or key file"
<< std::endl;
exit(1);
}
serverPtr->enableSSL(cert, key);
#endif
}
serverPtr->setIoLoopThreadPool(ioLoopThreadPoolPtr_);
serverPtr->setHttpAsyncCallback(httpCallback);
serverPtr->setNewWebsocketCallback(webSocketCallback);
serverPtr->setConnectionCallback(connectionCallback);
serverPtr->kickoffIdleConnections(connectionTimeout);
serverPtr->start();
servers_.push_back(serverPtr);
}
ioLoops_ = ioLoopThreadPoolPtr_->getLoops();
#endif
return ioLoops_;
}
void ListenerManager::startListening() {
if (listeners_.size() == 0)
return;
for (auto &loopThread : listeningloopThreads_) {
loopThread->run();
}
}
ListenerManager::~ListenerManager() {
}
trantor::EventLoop *ListenerManager::getIOLoop(size_t id) const {
auto const n = listeningloopThreads_.size();
if (0 == n) {
LOG_WARN << "Please call getIOLoop() after drogon::app().run()";
return nullptr;
}
if (id >= n) {
LOG_TRACE << "Loop id (" << id << ") out of range [0-" << n << ").";
id %= n;
LOG_TRACE << "Rounded to : " << id;
}
#ifdef __linux__
assert(listeningloopThreads_[id]);
return listeningloopThreads_[id]->getLoop();
#else
return ioLoopThreadPoolPtr_->getLoop(id);
#endif
}
void ListenerManager::stopListening() {
for (auto &serverPtr : servers_) {
serverPtr->stop();
}
for (auto loop : ioLoops_) {
assert(!loop->isInLoopThread());
if (loop->isRunning()) {
std::promise<int> pro;
auto f = pro.get_future();
loop->queueInLoop([loop, &pro]() {
loop->quit();
pro.set_value(1);
});
(void)f.get();
}
}
#ifndef __linux__
for (auto &listenerLoopPtr : listeningloopThreads_) {
auto loop = listenerLoopPtr->getLoop();
assert(!loop->isInLoopThread());
if (loop->isRunning()) {
std::promise<int> pro;
auto f = pro.get_future();
loop->queueInLoop([loop, &pro]() {
loop->quit();
pro.set_value(1);
});
(void)f.get();
}
}
#endif
}

82
core/listener_manager.h Normal file
View File

@ -0,0 +1,82 @@
/**
*
* ListenerManager.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/net/EventLoopThreadPool.h>
#include <trantor/net/callbacks.h>
#include <trantor/utils/NonCopyable.h>
#include <memory>
#include <string>
#include <vector>
#include "core/http_server_callbacks.h"
class HttpServer;
namespace drogon {
class ListenerManager : public trantor::NonCopyable {
public:
void addListener(const std::string &ip,uint16_t port,bool useSSL = false,const std::string &certFile = "", const std::string &keyFile = "");
std::vector<trantor::EventLoop *> createListeners(
const HttpAsyncCallback &httpCallback,
const WebSocketNewAsyncCallback &webSocketCallback,
const trantor::ConnectionCallback &connectionCallback,
size_t connectionTimeout,
const std::string &globalCertFile,
const std::string &globalKeyFile,
size_t threadNum,
const std::vector<
std::function<HttpResponsePtr(const HttpRequestPtr &)> >
&syncAdvices);
void startListening();
~ListenerManager();
trantor::EventLoop *getIOLoop(size_t id) const;
void stopListening();
std::vector<trantor::EventLoop *> ioLoops_;
private:
struct ListenerInfo {
ListenerInfo(const std::string &ip,
uint16_t port,
bool useSSL,
const std::string &certFile,
const std::string &keyFile) :
ip_(ip),
port_(port),
useSSL_(useSSL),
certFile_(certFile),
keyFile_(keyFile) {
}
std::string ip_;
uint16_t port_;
bool useSSL_;
std::string certFile_;
std::string keyFile_;
};
std::vector<ListenerInfo> listeners_;
std::vector<std::shared_ptr<HttpServer> > servers_;
std::vector<std::shared_ptr<trantor::EventLoopThread> > listeningloopThreads_;
std::shared_ptr<trantor::EventLoopThreadPool> ioLoopThreadPoolPtr_;
};
} // namespace drogon

View File

@ -9,15 +9,15 @@
#include "handler_instance.h"
using namespace brynet;
using namespace brynet::net;
using namespace brynet::net::http;
//using namespace brynet;
//using namespace brynet::net;
//using namespace brynet::net::http;
class Request {
public:
const HTTPParser *http_parser;
const HttpSession::Ptr *session;
HttpResponse *response;
const brynet::net::http::HTTPParser *http_parser;
const brynet::net::http::HttpSession::Ptr *session;
brynet::net::http::HttpResponse *response;
uint32_t current_middleware_index;
HandlerInstance handler_instance;

197
core/session.h Normal file
View File

@ -0,0 +1,197 @@
/**
*
* Session.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <core/any.h>
#include <trantor/utils/Logger.h>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
namespace drogon
{
/**
* @brief This class represents a session stored in the framework.
* One can get or set any type of data to a session object.
*/
class Session
{
public:
/**
* @brief Get the data identified by the key parameter.
* @note if the data is not found, a default value is returned.
* For example:
* @code
auto &userName = sessionPtr->get<std::string>("user name");
@endcode
*/
template <typename T>
const T &get(const std::string &key) const
{
const static T nullVal = T();
std::lock_guard<std::mutex> lck(mutex_);
auto it = sessionMap_.find(key);
if (it != sessionMap_.end())
{
if (typeid(T) == it->second.type())
{
return *(any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
return nullVal;
}
/**
* @brief Get the 'any' object identified by the given key
*/
any &operator[](const std::string &key)
{
std::lock_guard<std::mutex> lck(mutex_);
return sessionMap_[key];
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
sessionPtr->insert("user name", userNameString);
@endcode
*/
void insert(const std::string &key, const any &obj)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_[key] = obj;
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
sessionPtr->insert("user name", userNameString);
@endcode
*/
void insert(const std::string &key, any &&obj)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_[key] = std::move(obj);
}
/**
* @brief Erase the data identified by the given key.
*/
void erase(const std::string &key)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_.erase(key);
}
/**
* @brief Retrun true if the data identified by the key exists.
*/
bool find(const std::string &key)
{
std::lock_guard<std::mutex> lck(mutex_);
if (sessionMap_.find(key) == sessionMap_.end())
{
return false;
}
return true;
}
/**
* @brief Clear all data in the session.
*/
void clear()
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_.clear();
}
/**
* @brief Get the session ID of the current session.
*/
std::string sessionId() const
{
std::lock_guard<std::mutex> lck(mutex_);
return sessionId_;
}
/**
* @brief Let the framework create a new session ID for this session and set
* it to the client.
* @note This method does not change the session ID now.
*/
void changeSessionIdToClient()
{
needToChange_ = true;
needToSet_ = true;
}
Session() = delete;
private:
using SessionMap = std::map<std::string, any>;
SessionMap sessionMap_;
mutable std::mutex mutex_;
std::string sessionId_;
bool needToSet_{false};
bool needToChange_{false};
friend class SessionManager;
friend class HttpAppFrameworkImpl;
/**
* @brief Constructor, usually called by the framework
*/
Session(const std::string &id, bool needToSet)
: sessionId_(id), needToSet_(needToSet)
{
}
/**
* @brief Change the state of the session, usually called by the framework
*/
void hasSet()
{
needToSet_ = false;
}
/**
* @brief If the session ID needs to be changed.
*
*/
bool needToChangeSessionId() const
{
return needToChange_;
}
/**
* @brief If the session ID needs to be set to the client through cookie,
* return true
*/
bool needSetToClient() const
{
return needToSet_;
}
void setSessionId(const std::string &id)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionId_ = id;
needToChange_ = false;
}
};
using SessionPtr = std::shared_ptr<Session>;
} // namespace drogon

351
core/ssl_funcs/Md5.cc Normal file
View File

@ -0,0 +1,351 @@
/**
*
* Md5.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "Md5.h"
#include <math.h>
#include <string.h>
const uint32_t Md5Encode::kA = 0x67452301;
const uint32_t Md5Encode::kB = 0xefcdab89;
const uint32_t Md5Encode::kC = 0x98badcfe;
const uint32_t Md5Encode::kD = 0x10325476;
const uint64_t Md5Encode::tiNumInteger = 4294967296;
// function: CycleMoveLeft
// @param srcNum: the number to be moved left
// @param bitNumToMove: the number of bit of moving
// @return result after moving
uint32_t Md5Encode::cycleMoveLeft(uint32_t srcNum, int bitNumToMove)
{
uint32_t srcNum1 = srcNum;
uint32_t srcNum2 = srcNum;
if (0 >= bitNumToMove)
{
return srcNum;
}
return ((srcNum1 << bitNumToMove) | (srcNum2 >> (32 - bitNumToMove)));
}
// function: FillData
// @param inDataPtr: input data
// @param dataByteLen: length of input data
// @param outDataPtr: output data
// return : length of output data
uint32_t Md5Encode::fillData(const char *inDataPtr,
int dataByteLen,
char **outDataPtr)
{
int bitNum = dataByteLen * BIT_OF_BYTE;
// int grop_num = bitNum / BIT_OF_GROUP;
int modBitNum = bitNum % BIT_OF_GROUP;
int bitNeedFill = 0;
if (modBitNum > (BIT_OF_GROUP - SRC_DATA_LEN))
{
bitNeedFill = (BIT_OF_GROUP - modBitNum);
bitNeedFill += (BIT_OF_GROUP - SRC_DATA_LEN);
}
else
{
bitNeedFill = (BIT_OF_GROUP - SRC_DATA_LEN) - modBitNum;
}
int allBit = bitNum + bitNeedFill;
if (0 < bitNeedFill)
{
*outDataPtr =
new char[allBit / BIT_OF_BYTE + SRC_DATA_LEN / BIT_OF_BYTE];
memset(*outDataPtr,
0,
allBit / BIT_OF_BYTE + SRC_DATA_LEN / BIT_OF_BYTE);
// copy data
memcpy(*outDataPtr, inDataPtr, dataByteLen);
// fill rest data
unsigned char *tmp = reinterpret_cast<unsigned char *>(*outDataPtr);
tmp += dataByteLen;
// fill 1 and 0
*tmp = 0x80;
// fill origin data len
unsigned long long *originNum =
(unsigned long long *)((*outDataPtr) + ((allBit / BIT_OF_BYTE)));
*originNum = dataByteLen * BIT_OF_BYTE;
}
return (allBit / BIT_OF_BYTE + SRC_DATA_LEN / BIT_OF_BYTE);
}
void Md5Encode::roundF(char *data512Ptr, ParamDynamic &param)
{
uint32_t *M = reinterpret_cast<uint32_t *>(data512Ptr);
int s[] = {7, 12, 17, 22};
for (int i = 0; i < 16; ++i)
{
uint64_t ti = tiNumInteger * fabs(sin(i + 1));
if (i % 4 == 0)
{
FF(param.ua_, param.ub_, param.uc_, param.ud_, M[i], s[i % 4], ti);
}
else if (i % 4 == 1)
{
FF(param.ud_, param.ua_, param.ub_, param.uc_, M[i], s[i % 4], ti);
}
else if (i % 4 == 2)
{
FF(param.uc_, param.ud_, param.ua_, param.ub_, M[i], s[i % 4], ti);
}
else if (i % 4 == 3)
{
FF(param.ub_, param.uc_, param.ud_, param.ua_, M[i], s[i % 4], ti);
}
}
}
void Md5Encode::roundG(char *data512Ptr, ParamDynamic &param)
{
uint32_t *M = reinterpret_cast<uint32_t *>(data512Ptr);
int s[] = {5, 9, 14, 20};
for (int i = 0; i < 16; ++i)
{
auto sss = sin(i + 1 + 16);
uint64_t ti = tiNumInteger * fabs(sss);
int index = (i * 5 + 1) % 16;
if (i % 4 == 0)
{
GG(param.ua_,
param.ub_,
param.uc_,
param.ud_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 1)
{
GG(param.ud_,
param.ua_,
param.ub_,
param.uc_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 2)
{
GG(param.uc_,
param.ud_,
param.ua_,
param.ub_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 3)
{
GG(param.ub_,
param.uc_,
param.ud_,
param.ua_,
M[index],
s[i % 4],
ti);
}
}
}
void Md5Encode::roundH(char *data512Ptr, ParamDynamic &param)
{
uint32_t *M = reinterpret_cast<uint32_t *>(data512Ptr);
int s[] = {4, 11, 16, 23};
for (int i = 0; i < 16; ++i)
{
uint64_t ti = tiNumInteger * fabs(sin(i + 1 + 32));
int index = (i * 3 + 5) % 16;
if (i % 4 == 0)
{
HH(param.ua_,
param.ub_,
param.uc_,
param.ud_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 1)
{
HH(param.ud_,
param.ua_,
param.ub_,
param.uc_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 2)
{
HH(param.uc_,
param.ud_,
param.ua_,
param.ub_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 3)
{
HH(param.ub_,
param.uc_,
param.ud_,
param.ua_,
M[index],
s[i % 4],
ti);
}
}
}
void Md5Encode::roundI(char *data512Ptr, ParamDynamic &param)
{
uint32_t *M = reinterpret_cast<uint32_t *>(data512Ptr);
int s[] = {6, 10, 15, 21};
for (int i = 0; i < 16; ++i)
{
uint64_t ti = tiNumInteger * fabs(sin(i + 1 + 48));
int index = (i * 7 + 0) % 16;
if (i % 4 == 0)
{
II(param.ua_,
param.ub_,
param.uc_,
param.ud_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 1)
{
II(param.ud_,
param.ua_,
param.ub_,
param.uc_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 2)
{
II(param.uc_,
param.ud_,
param.ua_,
param.ub_,
M[index],
s[i % 4],
ti);
}
else if (i % 4 == 3)
{
II(param.ub_,
param.uc_,
param.ud_,
param.ua_,
M[index],
s[i % 4],
ti);
}
}
}
void Md5Encode::rotationCalculate(char *data512Ptr, ParamDynamic &param)
{
if (nullptr == data512Ptr)
{
return;
}
roundF(data512Ptr, param);
roundG(data512Ptr, param);
roundH(data512Ptr, param);
roundI(data512Ptr, param);
param.ua_ = param.va_last_ + param.ua_;
param.ub_ = param.vb_last_ + param.ub_;
param.uc_ = param.vc_last_ + param.uc_;
param.ud_ = param.vd_last_ + param.ud_;
param.va_last_ = param.ua_;
param.vb_last_ = param.ub_;
param.vc_last_ = param.uc_;
param.vd_last_ = param.ud_;
}
// Convert to hex format string
std::string Md5Encode::getHexStr(uint32_t numStr)
{
std::string hexstr = "";
char szch[] = {'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F'};
unsigned char *tmptr = (unsigned char *)&numStr;
int len = sizeof(numStr);
for (int i = 0; i < len; ++i)
{
unsigned char ch = tmptr[i] & 0xF0;
ch = ch >> 4;
hexstr.append(1, szch[ch]);
ch = tmptr[i] & 0x0F;
hexstr.append(1, szch[ch]);
}
return hexstr;
}
// function: Encode
// @param srcInfo: the string to be encoded.
// return : the string after encoding
std::string Md5Encode::encode(const char *data, const size_t dataLen)
{
ParamDynamic param;
param.ua_ = kA;
param.ub_ = kB;
param.uc_ = kC;
param.ud_ = kD;
param.va_last_ = kA;
param.vb_last_ = kB;
param.vc_last_ = kC;
param.vd_last_ = kD;
std::string result;
char *outDataPtr = nullptr;
int totalByte = fillData(data, dataLen, &outDataPtr);
for (int i = 0; i < totalByte / (BIT_OF_GROUP / BIT_OF_BYTE); ++i)
{
char *dataBitOfGroup = outDataPtr;
dataBitOfGroup += i * (BIT_OF_GROUP / BIT_OF_BYTE);
rotationCalculate(dataBitOfGroup, param);
}
delete[] outDataPtr, outDataPtr = nullptr;
result.append(getHexStr(param.ua_));
result.append(getHexStr(param.ub_));
result.append(getHexStr(param.uc_));
result.append(getHexStr(param.ud_));
return result;
}

81
core/ssl_funcs/Md5.h Normal file
View File

@ -0,0 +1,81 @@
/*
*******************************************************
* brief: md5 encryption
* author: Monkey.Knight
*******************************************************
*/
/**
*
* Md5.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string>
#include <cstdint>
#define BIT_OF_BYTE 8
#define BIT_OF_GROUP 512
#define SRC_DATA_LEN 64
#define DEF_F(X, Y, Z) ((((X) & (Y)) | ((~X) & (Z))))
#define DEF_G(X, Y, Z) (((X) & (Z)) | ((Y) & (~Z)))
#define DEF_H(X, Y, Z) ((X) ^ (Y) ^ (Z))
#define DEF_I(X, Y, Z) ((Y) ^ ((X) | (~Z)))
#define FF(a, b, c, d, Mj, s, ti) \
(a = b + cycleMoveLeft((a + DEF_F(b, c, d) + Mj + ti), s));
#define GG(a, b, c, d, Mj, s, ti) \
(a = b + cycleMoveLeft((a + DEF_G(b, c, d) + Mj + ti), s));
#define HH(a, b, c, d, Mj, s, ti) \
(a = b + cycleMoveLeft((a + DEF_H(b, c, d) + Mj + ti), s));
#define II(a, b, c, d, Mj, s, ti) \
(a = b + cycleMoveLeft((a + DEF_I(b, c, d) + Mj + ti), s));
class Md5Encode
{
public:
struct ParamDynamic
{
uint32_t ua_;
uint32_t ub_;
uint32_t uc_;
uint32_t ud_;
uint32_t va_last_;
uint32_t vb_last_;
uint32_t vc_last_;
uint32_t vd_last_;
};
public:
static std::string encode(const char *data, const size_t dataLen);
protected:
static uint32_t cycleMoveLeft(uint32_t srcNum, int bitNumToMove);
static void roundF(char *data512Ptr, ParamDynamic &param);
static void roundG(char *data512Ptr, ParamDynamic &param);
static void roundH(char *data512Ptr, ParamDynamic &param);
static void roundI(char *data512Ptr, ParamDynamic &param);
static void rotationCalculate(char *data512Ptr, ParamDynamic &param);
static std::string getHexStr(uint32_t numStr);
static uint32_t fillData(const char *inDataPtr,
int dataByteLen,
char **outDataPtr);
private:
static const uint32_t kA;
static const uint32_t kB;
static const uint32_t kC;
static const uint32_t kD;
static const uint64_t tiNumInteger;
};

159
core/ssl_funcs/Sha1.cc Normal file
View File

@ -0,0 +1,159 @@
/**
*
* Sha1.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "Sha1.h"
#include <string.h>
static inline unsigned int fromBigEndian(unsigned int v)
{
return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) |
((v & 0xff000000) >> 24);
}
static void writeBigEndian64(unsigned char *p, unsigned int v)
{
memset(p, 0, 8);
memcpy(p, &v, 4);
int i = 0;
for (i = 0; i < 4; ++i)
{
unsigned char t = p[i];
p[i] = p[7 - i];
p[7 - i] = t;
}
}
static inline unsigned int leftRoll(unsigned int v, int n)
{
return (v << n) | (v >> (32 - n));
}
unsigned char *SHA1(const unsigned char *dataIn,
size_t dataLen,
unsigned char *dataOut)
{
unsigned char *pbytes = (unsigned char *)dataIn;
unsigned int nbyte = dataLen;
static unsigned int words[80];
unsigned int H[5] = {
0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
unsigned int f, k, temp, bitlen[2], word;
unsigned int i, j, index, p1, p2, maxlen;
unsigned char spec[4] = {0};
i = nbyte % 4;
p1 = nbyte - i;
spec[i] = 1 << 7;
while (i--)
{
spec[i] = pbytes[p1 + i];
}
maxlen = (nbyte + 1) % 64;
if (maxlen <= 56)
{
maxlen = (nbyte + 1) - maxlen + 64;
}
else
{
maxlen = (nbyte + 1) - maxlen + 128;
}
p2 = maxlen - 8;
writeBigEndian64((unsigned char *)bitlen, nbyte * 8);
for (j = 0; j < maxlen; j += 64)
{
unsigned int a, b, c, d, e;
a = H[0];
b = H[1];
c = H[2];
d = H[3];
e = H[4];
for (i = 0; i < 80; ++i)
{
if (i < 16)
{
index = j + (i << 2);
if (index < p1)
{
word = *((unsigned int *)(pbytes + index));
}
else if (index == p1)
{
word = *(unsigned int *)spec;
}
else if (index < p2)
{
word = 0;
}
else
{
word = (index < maxlen - 4) ? bitlen[0] : bitlen[1];
}
words[i] = fromBigEndian(word);
}
else
{
words[i] = leftRoll(words[i - 3] ^ words[i - 8] ^
words[i - 14] ^ words[i - 16],
1);
}
if (i < 20)
{
f = (b & c) | ((~b) & d);
k = 0x5A827999;
}
else if (i < 40)
{
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else if (i < 60)
{
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
}
else
{
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
temp = leftRoll(a, 5) + f + e + k + words[i];
e = d;
d = c;
c = leftRoll(b, 30);
b = a;
a = temp;
}
H[0] += a;
H[1] += b;
H[2] += c;
H[3] += d;
H[4] += e;
}
int ct = 0;
for (i = 0; i < 5; ++i)
{
unsigned char buf[4] = {0};
memcpy(buf, &(H[i]), 4);
for (int r = 3; r >= 0; r--)
{
dataOut[ct] = buf[r];
++ct;
}
}
return dataOut;
}

23
core/ssl_funcs/Sha1.h Normal file
View File

@ -0,0 +1,23 @@
/**
*
* Sha1.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <iostream>
#define SHA_DIGEST_LENGTH 20
unsigned char *SHA1(const unsigned char *dataIn,
size_t dataLen,
unsigned char *dataOut);

92
core/string_view.h Normal file
View File

@ -0,0 +1,92 @@
/**
*
* string_view.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900)
#include <string_view>
#else
#include <boost/utility/string_view.hpp>
#include <boost/functional/hash.hpp>
#endif
#include <trantor/utils/LogStream.h>
namespace drogon
{
#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900)
using std::string_view;
#else
using boost::string_view;
#endif
} // namespace drogon
namespace trantor
{
inline LogStream &operator<<(LogStream &ls, const drogon::string_view &v)
{
ls.append(v.data(), v.length());
return ls;
}
} // namespace trantor
#if __cplusplus < 201703L
namespace std
{
template <>
struct hash<drogon::string_view>
{
size_t operator()(const drogon::string_view &__str) const noexcept
{
// Take from the memory header file
//===-------------------------- memory
//------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of
// Illinois Open Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
const size_t __m = 0x5bd1e995;
const size_t __r = 24;
size_t __h = __str.length();
auto __len = __h;
const unsigned char *__data = (const unsigned char *)(__str.data());
for (; __len >= 4; __data += 4, __len -= 4)
{
size_t __k = *((size_t *)__data);
__k *= __m;
__k ^= __k >> __r;
__k *= __m;
__h *= __m;
__h ^= __k;
}
switch (__len)
{
case 3:
__h ^= __data[2] << 16;
case 2:
__h ^= __data[1] << 8;
case 1:
__h ^= __data[0];
__h *= __m;
}
__h ^= __h >> 13;
__h *= __m;
__h ^= __h >> 15;
return __h;
}
};
} // namespace std
#endif

75
core/upload_file.h Normal file
View File

@ -0,0 +1,75 @@
/**
*
* UploadFile.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string>
namespace drogon
{
/**
* This class represents an upload file which will be transferred to the server
* via the multipart/form-data format
*/
class UploadFile
{
public:
/// Constructor
/**
* @param filePath The file location on local host, including file name.
* @param fileName The file name provided to the server. If it is empty by
* default, the file name in the @param filePath
* is provided to the server.
* @param itemName The item name on the browser form.
*/
explicit UploadFile(const std::string &filePath,
const std::string &fileName = "",
const std::string &itemName = "file")
: path_(filePath), itemName_(itemName)
{
if (!fileName.empty())
{
fileName_ = fileName;
}
else
{
auto pos = filePath.rfind('/');
if (pos != std::string::npos)
{
fileName_ = filePath.substr(pos + 1);
}
else
{
fileName_ = filePath;
}
}
}
const std::string &path() const
{
return path_;
}
const std::string &fileName() const
{
return fileName_;
}
const std::string &itemName() const
{
return itemName_;
}
private:
std::string path_;
std::string fileName_;
std::string itemName_;
};
} // namespace drogon

1162
core/utilities.cpp Normal file

File diff suppressed because it is too large Load Diff

153
core/utilities.h Normal file
View File

@ -0,0 +1,153 @@
/**
*
* Utilities.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Date.h>
#include <trantor/utils/Funcs.h>
#include <core/string_view.h>
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <limits>
#ifdef _WIN32
#include <time.h>
char *strptime(const char *s, const char *f, struct tm *tm);
time_t timegm(struct tm *tm);
#endif
namespace drogon
{
namespace utils
{
/// Determine if the string is an integer
bool isInteger(const std::string &str);
/// Generate random a string
/**
* @param length The string length
* The returned string consists of uppercase and lowercase letters and numbers
*/
std::string genRandomString(int length);
/// Convert a binary string to hex format
std::string binaryStringToHex(const unsigned char *ptr, size_t length);
/// Get a binary string from hexadecimal format
std::string hexToBinaryString(const char *ptr, size_t length);
/// Get a binary vector from hexadecimal format
std::vector<char> hexToBinaryVector(const char *ptr, size_t length);
/// Split the string into multiple separated strings.
/**
* @param acceptEmptyString if true, empty strings are accepted in the
* result, for example, splitting the ",1,2,,3," by "," produces
* ["","1","2","","3",""]
*/
inline std::vector<std::string> splitString(const std::string &str,
const std::string &separator,
bool acceptEmptyString = false)
{
return trantor::splitString(str, separator, acceptEmptyString);
}
std::set<std::string> splitStringToSet(const std::string &str,
const std::string &separator);
/// Get UUID string.
std::string getUuid();
/// Encode the string to base64 format.
std::string base64Encode(const unsigned char *bytes_to_encode,
unsigned int in_len,
bool url_safe = false);
/// Decode the base64 format string.
std::string base64Decode(const std::string &encoded_string);
std::vector<char> base64DecodeToVector(const std::string &encoded_string);
/// Check if the string need decoding
bool needUrlDecoding(const char *begin, const char *end);
/// Decode from or encode to the URL format string
std::string urlDecode(const char *begin, const char *end);
inline std::string urlDecode(const std::string &szToDecode)
{
auto begin = szToDecode.data();
return urlDecode(begin, begin + szToDecode.length());
}
inline std::string urlDecode(const string_view &szToDecode)
{
auto begin = szToDecode.data();
return urlDecode(begin, begin + szToDecode.length());
}
std::string urlEncode(const std::string &);
std::string urlEncodeComponent(const std::string &);
/// Get the MD5 digest of a string.
std::string getMd5(const char *data, const size_t dataLen);
inline std::string getMd5(const std::string &originalString)
{
return getMd5(originalString.data(), originalString.length());
}
/// Commpress or decompress data using gzip lib.
/**
* @param data the input data
* @param ndata the input data length
*/
std::string gzipCompress(const char *data, const size_t ndata);
std::string gzipDecompress(const char *data, const size_t ndata);
/// Commpress or decompress data using brotli lib.
/**
* @param data the input data
* @param ndata the input data length
*/
std::string brotliCompress(const char *data, const size_t ndata);
std::string brotliDecompress(const char *data, const size_t ndata);
/// Get the http full date string
/**
* rfc2616-3.3.1
* Full Date format(RFC 822)
* like this:
* @code
Sun, 06 Nov 1994 08:49:37 GMT
Wed, 12 Sep 2018 09:22:40 GMT
@endcode
*/
char *getHttpFullDate(const trantor::Date &date = trantor::Date::now());
/// Get the trantor::Date object according to the http full date string
/**
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
*/
trantor::Date getHttpDate(const std::string &httpFullDateString);
/// Get a formatted string
std::string formattedString(const char *format, ...);
/// Recursively create a file system path
/**
* Return 0 or -1 on success or failure.
*/
int createPath(const std::string &path);
} // namespace utils
} // namespace drogon