复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+210
View File
@@ -0,0 +1,210 @@
/**
*
* AOPAdvice.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 "AOPAdvice.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include <trantor/net/TcpConnection.h>
namespace drogon
{
static void doAdviceChain(
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>> &adviceChain,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr);
bool AopAdvice::passNewConnectionAdvices(
const trantor::TcpConnectionPtr &conn) const
{
for (auto &advice : newConnectionAdvices_)
{
if (!advice(conn->localAddr(), conn->peerAddr()))
{
return false;
}
}
return true;
}
void AopAdvice::passResponseCreationAdvices(const HttpResponsePtr &resp) const
{
if (!responseCreationAdvices_.empty())
{
for (auto &advice : responseCreationAdvices_)
{
advice(resp);
}
}
}
HttpResponsePtr AopAdvice::passSyncAdvices(const HttpRequestPtr &req) const
{
for (auto &advice : syncAdvices_)
{
if (auto resp = advice(req))
{
return resp;
}
}
return nullptr;
}
void AopAdvice::passPreRoutingObservers(const HttpRequestImplPtr &req) const
{
if (!preRoutingObservers_.empty())
{
for (auto &observer : preRoutingObservers_)
{
observer(req);
}
}
}
void AopAdvice::passPreRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (preRoutingAdvices_.empty())
{
callback(nullptr);
return;
}
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
{
if (!postRoutingObservers_.empty())
{
for (auto &observer : postRoutingObservers_)
{
observer(req);
}
}
}
void AopAdvice::passPostRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (postRoutingAdvices_.empty())
{
callback(nullptr);
return;
}
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
{
if (!preHandlingObservers_.empty())
{
for (auto &observer : preHandlingObservers_)
{
observer(req);
}
}
}
void AopAdvice::passPreHandlingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (preHandlingAdvices_.empty())
{
callback(nullptr);
return;
}
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPostHandlingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const
{
for (auto &advice : postHandlingAdvices_)
{
advice(req, resp);
}
}
void AopAdvice::passPreSendingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const
{
for (auto &advice : preSendingAdvices_)
{
advice(req, resp);
}
}
static void doAdviceChain(
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>> &adviceChain,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr)
{
if (index < adviceChain.size())
{
auto &advice = adviceChain[index];
advice(
req,
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
(*callbackPtr)(resp);
},
[index, req, callbackPtr, &adviceChain]() mutable {
auto ioLoop = req->getLoop();
if (ioLoop && !ioLoop->isInLoopThread())
{
ioLoop->queueInLoop([index,
req,
callbackPtr = std::move(callbackPtr),
&adviceChain]() mutable {
doAdviceChain(adviceChain,
index + 1,
req,
std::move(callbackPtr));
});
}
else
{
doAdviceChain(adviceChain,
index + 1,
req,
std::move(callbackPtr));
}
});
}
else
{
(*callbackPtr)(nullptr);
}
}
} // namespace drogon
+176
View File
@@ -0,0 +1,176 @@
/**
*
* AOPAdvice.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 <drogon/drogon_callbacks.h>
#include <trantor/net/InetAddress.h>
#include <functional>
#include <vector>
namespace drogon
{
class AopAdvice
{
public:
static AopAdvice &instance()
{
static AopAdvice inst;
return inst;
}
// Getters?
bool hasPreRoutingAdvices() const
{
return !preRoutingAdvices_.empty();
}
bool hasPostRoutingAdvices() const
{
return !postRoutingAdvices_.empty();
}
bool hasPreHandlingAdvices() const
{
return !preHandlingAdvices_.empty();
}
// Setters?
void registerNewConnectionAdvice(
std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)> advice)
{
newConnectionAdvices_.emplace_back(std::move(advice));
}
void registerHttpResponseCreationAdvice(
std::function<void(const HttpResponsePtr &)> advice)
{
responseCreationAdvices_.emplace_back(std::move(advice));
}
void registerSyncAdvice(
std::function<HttpResponsePtr(const HttpRequestPtr &)> advice)
{
syncAdvices_.emplace_back(std::move(advice));
}
void registerPreRoutingObserver(
std::function<void(const HttpRequestPtr &)> advice)
{
preRoutingObservers_.emplace_back(std::move(advice));
}
void registerPreRoutingAdvice(
std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> advice)
{
preRoutingAdvices_.emplace_back(std::move(advice));
}
void registerPostRoutingObserver(
std::function<void(const HttpRequestPtr &)> advice)
{
postRoutingObservers_.emplace_back(std::move(advice));
}
void registerPostRoutingAdvice(
std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> advice)
{
postRoutingAdvices_.emplace_back(std::move(advice));
}
void registerPreHandlingObserver(
std::function<void(const HttpRequestPtr &)> advice)
{
preHandlingObservers_.emplace_back(std::move(advice));
}
void registerPreHandlingAdvice(
std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> advice)
{
preHandlingAdvices_.emplace_back(std::move(advice));
}
void registerPostHandlingAdvice(
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>
advice)
{
postHandlingAdvices_.emplace_back(std::move(advice));
}
void registerPreSendingAdvice(
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>
advice)
{
preSendingAdvices_.emplace_back(std::move(advice));
}
// Executors
bool passNewConnectionAdvices(const trantor::TcpConnectionPtr &conn) const;
void passResponseCreationAdvices(const HttpResponsePtr &resp) const;
HttpResponsePtr passSyncAdvices(const HttpRequestPtr &req) const;
void passPreRoutingObservers(const HttpRequestImplPtr &req) const;
void passPreRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void passPostRoutingObservers(const HttpRequestImplPtr &req) const;
void passPostRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void passPreHandlingObservers(const HttpRequestImplPtr &req) const;
void passPreHandlingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void passPostHandlingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const;
void passPreSendingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const;
private:
using SyncAdvice = std::function<HttpResponsePtr(const HttpRequestPtr &)>;
using SyncReqObserver = std::function<void(const HttpRequestPtr &)>;
using SyncObserver =
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>;
using AsyncAdvice = std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>;
// If we want to add aop functions anytime, we can add a mutex here
std::vector<std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)>>
newConnectionAdvices_;
std::vector<std::function<void(const HttpResponsePtr &)>>
responseCreationAdvices_;
std::vector<SyncAdvice> syncAdvices_;
std::vector<SyncReqObserver> preRoutingObservers_;
std::vector<AsyncAdvice> preRoutingAdvices_;
std::vector<SyncReqObserver> postRoutingObservers_;
std::vector<AsyncAdvice> postRoutingAdvices_;
std::vector<SyncReqObserver> preHandlingObservers_;
std::vector<AsyncAdvice> preHandlingAdvices_;
std::vector<SyncObserver> postHandlingAdvices_;
std::vector<SyncObserver> preSendingAdvices_;
};
} // namespace drogon
+661
View File
@@ -0,0 +1,661 @@
/**
*
* @file AccessLogger.cc
* @author 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 "HttpUtils.h"
#include <drogon/drogon.h>
#include <drogon/plugins/AccessLogger.h>
#include <drogon/plugins/RealIpResolver.h>
#include <regex>
#include <thread>
#if !defined _WIN32 && !defined __HAIKU__
#include <unistd.h>
#include <sys/syscall.h>
#elif defined __HAIKU__
#include <unistd.h>
#else
#include <sstream>
#endif
#ifdef __FreeBSD__
#include <pthread_np.h>
#endif
#ifdef DROGON_SPDLOG_SUPPORT
#include <spdlog/spdlog.h>
#include <spdlog/logger.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#ifdef _WIN32
#include <spdlog/sinks/msvc_sink.h>
// Damn antedeluvian M$ macros
#undef min
#undef max
#endif
#ifndef _WIN32
#include <sys/wait.h>
#include <unistd.h>
#define os_access access
#elif !defined(_WIN32) || defined(__MINGW32__)
#include <sys/file.h>
#include <unistd.h>
#define os_access access
#else
#include <io.h>
#define os_access _waccess
#define R_OK 04
#define W_OK 02
#endif
#endif
using namespace drogon;
using namespace drogon::plugin;
bool AccessLogger::useRealIp_ = false;
void AccessLogger::initAndStart(const Json::Value &config)
{
useLocalTime_ = config.get("use_local_time", true).asBool();
showMicroseconds_ = config.get("show_microseconds", true).asBool();
timeFormat_ = config.get("custom_time_format", "").asString();
useCustomTimeFormat_ = !timeFormat_.empty();
useRealIp_ = config.get("use_real_ip", false).asBool();
logFunctionMap_ = {{"$request_path", outputReqPath},
{"$path", outputReqPath},
{"$date",
[this](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
outputDate(stream, req, resp);
}},
{"$request_date",
[this](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
outputReqDate(stream, req, resp);
}},
{"$request_query", outputReqQuery},
{"$request_url", outputReqURL},
{"$query", outputReqQuery},
{"$url", outputReqURL},
{"$request_version", outputVersion},
{"$version", outputVersion},
{"$request", outputReqLine},
{"$remote_addr", outputRemoteAddr},
{"$local_addr", outputLocalAddr},
{"$request_len", outputReqLength},
{"$body_bytes_received", outputReqLength},
{"$method", outputMethod},
{"$thread", outputThreadNumber},
{"$response_len", outputRespLength},
{"$body_bytes_sent", outputRespLength},
{"$status", outputStatusString},
{"$status_code", outputStatusCode},
{"$processing_time", outputProcessingTime},
{"$upstream_http_content-type", outputRespContentType},
{"$upstream_http_content_type", outputRespContentType}};
auto format = config.get("log_format", "").asString();
if (format.empty())
{
format =
"$request_date $method $url [$body_bytes_received] ($remote_addr - "
"$local_addr) $status $body_bytes_sent $processing_time";
}
createLogFunctions(format);
auto logPath = config.get("log_path", "").asString();
if (config.isMember("path_exempt"))
{
if (config["path_exempt"].isArray())
{
const auto &exempts = config["path_exempt"];
size_t exemptsCount = exempts.size();
if (exemptsCount)
{
std::string regexString;
size_t len = 0;
for (const auto &exempt : exempts)
{
assert(exempt.isString());
len += exempt.size();
}
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
const auto last = --exempts.end();
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
regexString.append("(")
.append(exempt->asString())
.append(")|");
regexString.append("(").append(last->asString()).append(")");
exemptRegex_ = std::regex(regexString);
regexFlag_ = true;
}
}
else if (config["path_exempt"].isString())
{
exemptRegex_ = std::regex(config["path_exempt"].asString());
regexFlag_ = true;
}
else
{
LOG_ERROR << "path_exempt must be a string or string array!";
}
}
#ifdef DROGON_SPDLOG_SUPPORT
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
config.get("use_spdlog", false).asBool();
if (logWithSpdlog)
{
logIndex_ = config.get("log_index", 0).asInt();
// Do nothing if already initialized...
if (!trantor::Logger::getSpdLogger(logIndex_))
{
trantor::Logger::enableSpdLog(logIndex_);
// Get the new logger & replace its sinks with the ones of the
// config
auto logger = trantor::Logger::getSpdLogger(logIndex_);
std::vector<spdlog::sink_ptr> sinks;
while (!logPath.empty())
{
// 1. check existence of folder or try to create it
auto fsLogPath =
std::filesystem::path(utils::toNativePath(logPath));
std::error_code fsErr;
if (!std::filesystem::create_directories(fsLogPath, fsErr) &&
fsErr)
{
LOG_ERROR << "could not create log file path";
break;
}
// 2. check if we have rights to create files in the folder
if (os_access(fsLogPath.native().c_str(), W_OK) != 0)
{
LOG_ERROR << "cannot create files in log folder";
break;
}
std::filesystem::path fileName(
config.get("log_file", "access.log").asString());
if (fileName.empty())
fileName = "access.log";
else
fileName.replace_extension(".log");
auto sizeLimit = config.get("log_size_limit", 0).asUInt64();
if (sizeLimit == 0)
sizeLimit = config.get("size_limit", 0).asUInt64();
if (sizeLimit == 0) // 0 is not allowed by this sink
sizeLimit = std::numeric_limits<std::size_t>::max();
std::size_t maxFiles = config.get("max_files", 0).asUInt();
sinks.push_back(
std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
(fsLogPath / fileName).string(),
sizeLimit,
// spdlog limitation
std::min(maxFiles, std::size_t(20000)),
false));
break;
}
if (sinks.empty())
sinks.push_back(
std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
#if defined(_WIN32) && defined(_DEBUG)
// On Windows with debug, it may be interesting to have the logs
// directly in the Visual Studio / WinDbg console
sinks.push_back(std::make_shared<spdlog::sinks::msvc_sink_mt>());
#endif
logger->sinks() = sinks;
// Override the pattern set in
// trantor::Logger::getDefaultSpdLogger() and let AccessLogger
// format the output
logger->set_pattern("%v");
}
}
else
#endif
if (!logPath.empty())
{
auto fileName = config.get("log_file", "access.log").asString();
auto extension = std::string(".log");
auto pos = fileName.rfind('.');
if (pos != std::string::npos)
{
extension = fileName.substr(pos);
fileName = fileName.substr(0, pos);
}
if (fileName.empty())
{
fileName = "access";
}
asyncFileLogger_.setFileName(fileName, extension, logPath);
asyncFileLogger_.startLogging();
logIndex_ = config.get("log_index", 0).asInt();
trantor::Logger::setOutputFunction(
[&](const char *msg, const uint64_t len) {
asyncFileLogger_.output(msg, len);
},
[&]() { asyncFileLogger_.flush(); },
logIndex_);
auto sizeLimit = config.get("log_size_limit", 0).asUInt64();
if (sizeLimit == 0)
{
// In earlier code, "size_limit" is taken instead of
// "log_size_limit" as it said in the comment in AccessLogger.h.
// In order to ensure backward compatibility we still take this
// field as a fallback.
sizeLimit = config.get("size_limit", 0).asUInt64();
}
if (sizeLimit > 0)
{
asyncFileLogger_.setFileSizeLimit(sizeLimit);
}
auto maxFiles = config.get("max_files", 0).asUInt();
asyncFileLogger_.setMaxFiles(maxFiles);
}
drogon::app().registerPreSendingAdvice(
[this](const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
if (regexFlag_)
{
if (!std::regex_match(req->path(), exemptRegex_))
{
logging(LOG_RAW_TO(logIndex_), req, resp);
}
}
else
{
logging(LOG_RAW_TO(logIndex_), req, resp);
}
});
}
void AccessLogger::shutdown()
{
}
void AccessLogger::logging(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp)
{
for (auto &func : logFunctions_)
{
func(stream, req, resp);
}
}
void AccessLogger::createLogFunctions(std::string format)
{
std::string rawString;
while (!format.empty())
{
LOG_TRACE << format;
auto pos = format.find('$');
if (pos != std::string::npos)
{
rawString += format.substr(0, pos);
format = format.substr(pos);
std::regex e{"^\\$[a-zA-Z0-9\\-_]+"};
std::smatch m;
if (std::regex_search(format, m, e))
{
if (!rawString.empty())
{
logFunctions_.emplace_back(
[rawString](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) {
stream << rawString;
});
rawString.clear();
}
auto placeholder = m[0];
logFunctions_.emplace_back(newLogFunction(placeholder));
format = m.suffix().str();
}
else
{
rawString += '$';
format = format.substr(1);
}
}
else
{
rawString += format;
break;
}
}
if (!rawString.empty())
{
logFunctions_.emplace_back(
[rawString =
std::move(rawString)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) {
stream << rawString << "\n";
});
}
else
{
logFunctions_.emplace_back(
[](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) { stream << "\n"; });
}
}
AccessLogger::LogFunction AccessLogger::newLogFunction(
const std::string &placeholder)
{
auto iter = logFunctionMap_.find(placeholder);
if (iter != logFunctionMap_.end())
{
return iter->second;
}
if (placeholder.find("$http_") == 0 && placeholder.size() > 6)
{
auto headerName = placeholder.substr(6);
return [headerName =
std::move(headerName)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &) {
outputReqHeader(stream, req, headerName);
};
}
if (placeholder.find("$cookie_") == 0 && placeholder.size() > 8)
{
auto cookieName = placeholder.substr(8);
return [cookieName =
std::move(cookieName)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &) {
outputReqCookie(stream, req, cookieName);
};
}
if (placeholder.find("$upstream_http_") == 0 && placeholder.size() > 15)
{
auto headerName = placeholder.substr(15);
return [headerName = std::move(
headerName)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp) {
outputRespHeader(stream, resp, headerName);
};
}
return [placeholder](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) {
stream << placeholder;
};
}
void AccessLogger::outputReqPath(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->path();
}
void AccessLogger::outputDate(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) const
{
if (useCustomTimeFormat_)
{
if (useLocalTime_)
{
stream << trantor::Date::now().toCustomFormattedStringLocal(
timeFormat_, showMicroseconds_);
}
else
{
stream << trantor::Date::now().toCustomFormattedString(
timeFormat_, showMicroseconds_);
}
}
else
{
if (useLocalTime_)
{
stream << trantor::Date::now().toFormattedStringLocal(
showMicroseconds_);
}
else
{
stream << trantor::Date::now().toFormattedString(showMicroseconds_);
}
}
}
void AccessLogger::outputReqDate(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &) const
{
if (useCustomTimeFormat_)
{
if (useLocalTime_)
{
stream << req->creationDate().toCustomFormattedStringLocal(
timeFormat_, showMicroseconds_);
}
else
{
stream << req->creationDate().toCustomFormattedString(
timeFormat_, showMicroseconds_);
}
}
else
{
if (useLocalTime_)
{
stream << req->creationDate().toFormattedStringLocal(
showMicroseconds_);
}
else
{
stream << req->creationDate().toFormattedString(showMicroseconds_);
}
}
}
//$request_query
void AccessLogger::outputReqQuery(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->query();
}
//$request_url
void AccessLogger::outputReqURL(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
auto &query = req->query();
if (query.empty())
{
stream << req->path();
}
else
{
stream << req->path() << '?' << query;
}
}
//$request_version
void AccessLogger::outputVersion(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->versionString();
}
//$request
void AccessLogger::outputReqLine(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
auto &query = req->query();
if (query.empty())
{
stream << req->methodString() << " " << req->path() << " "
<< req->versionString();
}
else
{
stream << req->methodString() << " " << req->path() << '?' << query
<< " " << req->versionString();
}
}
void AccessLogger::outputRemoteAddr(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
if (useRealIp_)
{
stream << RealIpResolver::GetRealAddr(req).toIpPort();
}
else
{
stream << req->peerAddr().toIpPort();
}
}
void AccessLogger::outputLocalAddr(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->localAddr().toIpPort();
}
void AccessLogger::outputReqLength(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->body().length();
}
void AccessLogger::outputRespLength(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
stream << resp->body().length();
}
void AccessLogger::outputMethod(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->methodString();
}
void AccessLogger::outputThreadNumber(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &)
{
#ifdef __linux__
static thread_local pid_t threadId_{0};
#else
static thread_local uint64_t threadId_{0};
#endif
#ifdef __linux__
if (threadId_ == 0)
threadId_ = static_cast<pid_t>(::syscall(SYS_gettid));
#elif defined __FreeBSD__
if (threadId_ == 0)
{
threadId_ = pthread_getthreadid_np();
}
#elif defined __OpenBSD__
if (threadId_ == 0)
{
threadId_ = getthrid();
}
#elif defined _WIN32 || defined __HAIKU__
if (threadId_ == 0)
{
std::stringstream ss;
ss << std::this_thread::get_id();
threadId_ = std::stoull(ss.str());
}
#else
if (threadId_ == 0)
{
pthread_threadid_np(NULL, &threadId_);
}
#endif
stream << threadId_;
}
//$http_[header_name]
void AccessLogger::outputReqHeader(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const std::string &headerName)
{
stream << headerName << ": " << req->getHeader(headerName);
}
//$cookie_[cookie_name]
void AccessLogger::outputReqCookie(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const std::string &cookie)
{
stream << "(cookie)" << cookie << "=" << req->getCookie(cookie);
}
//$upstream_http_[header_name]
void AccessLogger::outputRespHeader(trantor::LogStream &stream,
const drogon::HttpResponsePtr &resp,
const std::string &headerName)
{
stream << headerName << ": " << resp->getHeader(headerName);
}
//$status
void AccessLogger::outputStatusString(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
int code = resp->getStatusCode();
stream << code << " " << statusCodeToString(code);
}
//$status_code
void AccessLogger::outputStatusCode(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
stream << resp->getStatusCode();
}
//$processing_time
void AccessLogger::outputProcessingTime(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
auto start = req->creationDate();
auto end = trantor::Date::now();
auto duration =
end.microSecondsSinceEpoch() - start.microSecondsSinceEpoch();
auto seconds = static_cast<double>(duration) / 1000000.0;
stream << seconds;
}
//$upstream_http_content-type $upstream_http_content_type
void AccessLogger::outputRespContentType(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
stream << resp->contentTypeString();
}
+107
View File
@@ -0,0 +1,107 @@
/**
*
* 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 "CacheFile.h"
#include <trantor/utils/Logger.h>
#ifdef _WIN32
#include <mman.h>
#include <drogon/utils/Utilities.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
auto wPath{drogon::utils::toNativePath(path)};
if (_wfopen_s(&file_, wPath.c_str(), L"wb+") != 0)
{
file_ = nullptr;
}
#endif
if (!file_)
LOG_SYSERR << "CacheFile fopen:";
}
CacheFile::~CacheFile()
{
if (data_)
{
munmap(data_, dataLength_);
}
if (autoDelete_ && file_)
{
fclose(file_);
#if defined(_WIN32) && !defined(__MINGW32__)
auto wPath{drogon::utils::toNativePath(path_)};
_wunlink(wPath.c_str());
#else
unlink(path_.data());
#endif
}
else if (file_)
{
fclose(file_);
}
}
void CacheFile::append(const char *data, size_t length)
{
if (file_)
{
if (!fwrite(data, length, 1, file_))
LOG_SYSERR << "CacheFile append:";
}
}
size_t CacheFile::length()
{
if (file_)
#ifdef _WIN32
return _ftelli64(file_);
#else
return ftell(file_);
#endif
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 << "CacheFile mmap:";
}
}
return data_;
}
+53
View File
@@ -0,0 +1,53 @@
/**
*
* 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 <trantor/utils/NonCopyable.h>
#include <string>
#include <string_view>
#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);
std::string_view getStringView()
{
if (data())
return std::string_view(data_, dataLength_);
return std::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
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <json/json.h>
#include <vector>
#include <string>
#include <memory>
#include <fstream>
namespace drogon
{
class ConfigAdapter
{
public:
virtual ~ConfigAdapter() = default;
virtual Json::Value getJson(const std::string &content) const
noexcept(false) = 0;
virtual std::vector<std::string> getExtensions() const = 0;
};
using ConfigAdapterPtr = std::shared_ptr<ConfigAdapter>;
} // namespace drogon
+40
View File
@@ -0,0 +1,40 @@
#include "ConfigAdapterManager.h"
#include "JsonConfigAdapter.h"
#include "YamlConfigAdapter.h"
#include <algorithm>
using namespace drogon;
#define REGISTER_CONFIG_ADAPTER(adapter) \
{ \
auto adapterPtr = std::make_shared<adapter>(); \
auto exts = adapterPtr->getExtensions(); \
for (auto ext : exts) \
{ \
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); \
adapters_[ext] = adapterPtr; \
} \
}
ConfigAdapterManager &ConfigAdapterManager::instance()
{
static ConfigAdapterManager instance;
return instance;
}
Json::Value ConfigAdapterManager::getJson(const std::string &content,
std::string ext) const noexcept(false)
{
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
auto it = adapters_.find(ext);
if (it == adapters_.end())
{
throw std::runtime_error("No valid parser for this config file!");
}
return it->second->getJson(content);
}
ConfigAdapterManager::ConfigAdapterManager()
{
REGISTER_CONFIG_ADAPTER(JsonConfigAdapter);
REGISTER_CONFIG_ADAPTER(YamlConfigAdapter);
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "ConfigAdapterManager.h"
#include "ConfigAdapter.h"
#include <map>
namespace drogon
{
class ConfigAdapterManager
{
public:
static ConfigAdapterManager &instance();
Json::Value getJson(const std::string &content, std::string ext) const
noexcept(false);
private:
ConfigAdapterManager();
std::map<std::string, ConfigAdapterPtr> adapters_;
};
} // namespace drogon
+709
View File
@@ -0,0 +1,709 @@
/**
*
* @file ConfigLoader.cc
* @author 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 "ConfigLoader.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/config.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <thread>
#include <trantor/utils/Logger.h>
#if !defined(_WIN32)
#include <unistd.h>
#define os_access access
#else
#include <io.h>
#ifndef __MINGW32__
#define os_access _waccess
#define R_OK 04
#define W_OK 02
#else
#define os_access access
#endif
#endif
#include <drogon/utils/Utilities.h>
#include "ConfigAdapterManager.h"
#include <filesystem>
using namespace drogon;
static bool bytesSize(std::string &sizeStr, size_t &size)
{
if (sizeStr.empty())
{
size = -1;
return true;
}
else
{
size = 1;
switch (sizeStr[sizeStr.length() - 1])
{
case 'k':
case 'K':
size = 1024;
sizeStr.resize(sizeStr.length() - 1);
break;
case 'M':
case 'm':
size = (1024 * 1024);
sizeStr.resize(sizeStr.length() - 1);
break;
case 'g':
case 'G':
size = (1024 * 1024 * 1024);
sizeStr.resize(sizeStr.length() - 1);
break;
#if ((ULONG_MAX) != (UINT_MAX))
// 64bit system
case 't':
case 'T':
size = (1024L * 1024L * 1024L * 1024L);
sizeStr.resize(sizeStr.length() - 1);
break;
#endif
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '7':
case '8':
case '9':
break;
default:
return false;
break;
}
std::istringstream iss(sizeStr);
size_t tmpSize;
iss >> tmpSize;
if (iss.fail())
{
return false;
}
if ((size_t(-1) / tmpSize) >= size)
size *= tmpSize;
else
{
size = -1;
}
return true;
}
}
ConfigLoader::ConfigLoader(const std::string &configFile)
{
if (os_access(drogon::utils::toNativePath(configFile).c_str(), 0) != 0)
{
throw std::runtime_error("Config file " + configFile + " not found!");
}
if (os_access(drogon::utils::toNativePath(configFile).c_str(), R_OK) != 0)
{
throw std::runtime_error("No permission to read config file " +
configFile);
}
configFile_ = configFile;
auto pos = configFile.find_last_of('.');
if (pos == std::string::npos)
{
throw std::runtime_error("Invalid config file name!");
}
auto ext = configFile.substr(pos + 1);
std::ifstream infile(drogon::utils::toNativePath(configFile).c_str(),
std::ifstream::in);
// get the content of the infile
std::string content((std::istreambuf_iterator<char>(infile)),
std::istreambuf_iterator<char>());
try
{
configJsonRoot_ =
ConfigAdapterManager::instance().getJson(content, std::move(ext));
}
catch (std::exception &e)
{
throw std::runtime_error("Error reading config file " + configFile +
": " + e.what());
}
}
ConfigLoader::ConfigLoader(const Json::Value &data) : configJsonRoot_(data)
{
}
ConfigLoader::ConfigLoader(Json::Value &&data)
: configJsonRoot_(std::move(data))
{
}
ConfigLoader::~ConfigLoader()
{
}
static void loadLogSetting(const Json::Value &log)
{
if (!log)
return;
auto useSpdlog = log.get("use_spdlog", false).asBool();
auto logPath = log.get("log_path", "").asString();
auto baseName = log.get("logfile_base_name", "").asString();
auto logSize = log.get("log_size_limit", 100000000).asUInt64();
auto maxFiles = log.get("max_files", 0).asUInt();
HttpAppFrameworkImpl::instance().setLogPath(
logPath, baseName, logSize, maxFiles, useSpdlog);
auto logLevel = log.get("log_level", "DEBUG").asString();
if (logLevel == "TRACE")
{
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
}
else if (logLevel == "DEBUG")
{
trantor::Logger::setLogLevel(trantor::Logger::kDebug);
}
else if (logLevel == "INFO")
{
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
}
else if (logLevel == "WARN")
{
trantor::Logger::setLogLevel(trantor::Logger::kWarn);
}
auto localTime = log.get("display_local_time", false).asBool();
trantor::Logger::setDisplayLocalTime(localTime);
}
static void loadControllers(const Json::Value &controllers)
{
if (!controllers)
return;
for (auto const &controller : controllers)
{
auto path = controller.get("path", "").asString();
auto ctrlName = controller.get("controller", "").asString();
if (path == "" || ctrlName == "")
continue;
std::vector<internal::HttpConstraint> constraints;
if (!controller["http_methods"].isNull())
{
for (auto const &method : controller["http_methods"])
{
auto strMethod = method.asString();
std::transform(strMethod.begin(),
strMethod.end(),
strMethod.begin(),
[](unsigned char c) { return tolower(c); });
if (strMethod == "get")
{
constraints.push_back(Get);
}
else if (strMethod == "post")
{
constraints.push_back(Post);
}
else if (strMethod == "head") // The branch never work
{
constraints.push_back(Head);
}
else if (strMethod == "put")
{
constraints.push_back(Put);
}
else if (strMethod == "delete")
{
constraints.push_back(Delete);
}
else if (strMethod == "patch")
{
constraints.push_back(Patch);
}
}
}
if (!controller["filters"].isNull())
{
for (auto const &filter : controller["filters"])
{
constraints.push_back(filter.asString());
}
}
drogon::app().registerHttpSimpleController(path, ctrlName, constraints);
}
}
static void loadApp(const Json::Value &app)
{
if (!app)
return;
// threads number
auto threadsNum = app.get("threads_num", 1).asUInt64();
if (threadsNum == 1)
{
threadsNum = app.get("number_of_threads", 1).asUInt64();
}
if (threadsNum == 0)
{
// set the number to the number of processors.
threadsNum = std::thread::hardware_concurrency();
LOG_TRACE << "The number of processors is " << threadsNum;
}
if (threadsNum < 1)
threadsNum = 1;
drogon::app().setThreadNum(threadsNum);
// session
auto enableSession = app.get("enable_session", false).asBool();
if (enableSession)
{
auto timeout = app.get("session_timeout", 0).asUInt64();
auto sameSite = app.get("session_same_site", "Null").asString();
auto cookieKey = app.get("session_cookie_key", "JSESSIONID").asString();
auto maxAge = app.get("session_max_age", -1).asInt();
drogon::app().enableSession(timeout,
Cookie::convertString2SameSite(sameSite),
cookieKey,
maxAge);
}
else
drogon::app().disableSession();
// document root
auto documentRoot = app.get("document_root", "").asString();
if (documentRoot != "")
{
drogon::app().setDocumentRoot(documentRoot);
}
if (!app["static_file_headers"].empty())
{
if (app["static_file_headers"].isArray())
{
std::vector<std::pair<std::string, std::string>> headers;
for (auto &header : app["static_file_headers"])
{
headers.emplace_back(
std::make_pair(header["name"].asString(),
header["value"].asString()));
}
drogon::app().setStaticFileHeaders(headers);
}
else
{
throw std::runtime_error(
"The static_file_headers option must be an array");
}
}
// upload path
auto uploadPath = app.get("upload_path", "uploads").asString();
drogon::app().setUploadPath(uploadPath);
// file types
auto fileTypes = app["file_types"];
if (fileTypes.isArray() && !fileTypes.empty())
{
std::vector<std::string> types;
for (auto const &fileType : fileTypes)
{
types.push_back(fileType.asString());
LOG_TRACE << "file type:" << types.back();
}
drogon::app().setFileTypes(types);
}
// locations
if (app.isMember("locations"))
{
auto &locations = app["locations"];
if (!locations.isArray())
{
throw std::runtime_error("The locations option must be an array");
}
for (auto &location : locations)
{
auto uri = location.get("uri_prefix", "").asString();
if (uri.empty())
continue;
auto defaultContentType =
location.get("default_content_type", "").asString();
auto alias = location.get("alias", "").asString();
auto isCaseSensitive =
location.get("is_case_sensitive", false).asBool();
auto allAll = location.get("allow_all", true).asBool();
auto isRecursive = location.get("is_recursive", true).asBool();
if (!location["filters"].isNull())
{
if (location["filters"].isArray())
{
std::vector<std::string> filters;
for (auto const &filter : location["filters"])
{
filters.push_back(filter.asString());
}
drogon::app().addALocation(uri,
defaultContentType,
alias,
isCaseSensitive,
allAll,
isRecursive,
filters);
}
else
{
throw std::runtime_error("the filters of location '" + uri +
"' should be an array");
}
}
else
{
drogon::app().addALocation(uri,
defaultContentType,
alias,
isCaseSensitive,
allAll,
isRecursive);
}
}
}
// max connections
auto maxConns = app.get("max_connections", 0).asUInt64();
if (maxConns > 0)
{
drogon::app().setMaxConnectionNum(maxConns);
}
// max connections per IP
auto maxConnsPerIP = app.get("max_connections_per_ip", 0).asUInt64();
if (maxConnsPerIP > 0)
{
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
}
#if !defined(_WIN32) && !TARGET_OS_IOS
// dynamic views
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
if (enableDynamicViews)
{
auto viewsPaths = app["dynamic_views_path"];
if (viewsPaths.isArray() && viewsPaths.size() > 0)
{
std::vector<std::string> paths;
for (auto const &viewsPath : viewsPaths)
{
paths.push_back(viewsPath.asString());
LOG_TRACE << "views path:" << paths.back();
}
auto outputPath =
app.get("dynamic_views_output_path", "").asString();
drogon::app().enableDynamicViewsLoading(paths, outputPath);
}
}
#endif
auto stackLimit = app.get("json_parser_stack_limit", 1000).asUInt64();
drogon::app().setJsonParserStackLimit(stackLimit);
auto unicodeEscaping =
app.get("enable_unicode_escaping_in_json", true).asBool();
drogon::app().setUnicodeEscapingInJson(unicodeEscaping);
auto &precision = app["float_precision_in_json"];
if (!precision.isNull())
{
auto precisionLength = precision.get("precision", 0).asUInt64();
auto precisionType =
precision.get("precision_type", "significant").asString();
drogon::app().setFloatPrecisionInJson((unsigned int)precisionLength,
precisionType);
}
// log
loadLogSetting(app["log"]);
// run as daemon
auto runAsDaemon = app.get("run_as_daemon", false).asBool();
if (runAsDaemon)
{
drogon::app().enableRunAsDaemon();
}
// handle SIGTERM
auto handleSigterm = app.get("handle_sig_term", true).asBool();
if (!handleSigterm)
{
drogon::app().disableSigtermHandling();
}
// relaunch
auto relaunch = app.get("relaunch_on_error", false).asBool();
if (relaunch)
{
drogon::app().enableRelaunchOnError();
}
auto useSendfile = app.get("use_sendfile", true).asBool();
drogon::app().enableSendfile(useSendfile);
auto useGzip = app.get("use_gzip", true).asBool();
drogon::app().enableGzip(useGzip);
auto useBr = app.get("use_brotli", false).asBool();
drogon::app().enableBrotli(useBr);
auto staticFilesCacheTime = app.get("static_files_cache_time", 5).asInt();
drogon::app().setStaticFilesCacheTime(staticFilesCacheTime);
loadControllers(app["simple_controllers_map"]);
// Kick off idle connections
auto kickOffTimeout = app.get("idle_connection_timeout", 60).asUInt64();
drogon::app().setIdleConnectionTimeout(kickOffTimeout);
auto server = app.get("server_header_field", "").asString();
if (!server.empty())
drogon::app().setServerHeaderField(server);
auto sendServerHeader = app.get("enable_server_header", true).asBool();
drogon::app().enableServerHeader(sendServerHeader);
auto sendDateHeader = app.get("enable_date_header", true).asBool();
drogon::app().enableDateHeader(sendDateHeader);
auto keepaliveReqs = app.get("keepalive_requests", 0).asUInt64();
drogon::app().setKeepaliveRequestsNumber(keepaliveReqs);
auto pipeliningReqs = app.get("pipelining_requests", 0).asUInt64();
drogon::app().setPipeliningRequestsNumber(pipeliningReqs);
auto useGzipStatic = app.get("gzip_static", true).asBool();
drogon::app().setGzipStatic(useGzipStatic);
auto useBrStatic = app.get("br_static", true).asBool();
drogon::app().setBrStatic(useBrStatic);
auto maxBodySize = app.get("client_max_body_size", "1M").asString();
size_t size;
if (bytesSize(maxBodySize, size))
{
drogon::app().setClientMaxBodySize(size);
}
else
{
throw std::runtime_error("Error format of client_max_body_size");
}
auto maxMemoryBodySize =
app.get("client_max_memory_body_size", "64K").asString();
if (bytesSize(maxMemoryBodySize, size))
{
drogon::app().setClientMaxMemoryBodySize(size);
}
else
{
throw std::runtime_error("Error format of client_max_memory_body_size");
}
auto maxWsMsgSize =
app.get("client_max_websocket_message_size", "128K").asString();
if (bytesSize(maxWsMsgSize, size))
{
drogon::app().setClientMaxWebSocketMessageSize(size);
}
else
{
throw std::runtime_error(
"Error format of client_max_websocket_message_size");
}
drogon::app().enableReusePort(app.get("reuse_port", false).asBool());
drogon::app().setHomePage(app.get("home_page", "index.html").asString());
drogon::app().setImplicitPageEnable(
app.get("use_implicit_page", true).asBool());
drogon::app().setImplicitPage(
app.get("implicit_page", "index.html").asString());
auto mimes = app["mime"];
if (!mimes.isNull())
{
auto names = mimes.getMemberNames();
for (const auto &mime : names)
{
auto ext = mimes[mime];
std::vector<std::string> exts;
if (ext.isString())
exts.push_back(ext.asString());
else if (ext.isArray())
{
for (const auto &extension : ext)
exts.push_back(extension.asString());
}
for (const auto &extension : exts)
drogon::app().registerCustomExtensionMime(extension, mime);
}
}
bool enableCompressedRequests =
app.get("enabled_compressed_request", false).asBool();
drogon::app().enableCompressedRequest(enableCompressedRequests);
drogon::app().enableRequestStream(
app.get("enable_request_stream", false).asBool());
}
static void loadDbClients(const Json::Value &dbClients)
{
if (!dbClients)
return;
for (auto const &client : dbClients)
{
auto type = client.get("rdbms", "postgresql").asString();
std::transform(type.begin(),
type.end(),
type.begin(),
[](unsigned char c) { return tolower(c); });
auto host = client.get("host", "127.0.0.1").asString();
unsigned short port = client.get("port", 5432).asUInt();
auto dbname = client.get("dbname", "").asString();
if (dbname.empty() && type != "sqlite3")
{
throw std::runtime_error(
"Please configure dbname in the configuration file");
}
auto user = client.get("user", "postgres").asString();
auto password = client.get("passwd", "").asString();
if (password.empty())
{
password = client.get("password", "").asString();
}
auto connNum = client.get("connection_number", 1).asUInt();
if (connNum == 1)
{
connNum = client.get("number_of_connections", 1).asUInt();
}
auto name = client.get("name", "default").asString();
auto filename = client.get("filename", "").asString();
auto isFast = client.get("is_fast", false).asBool();
auto characterSet = client.get("characterSet", "").asString();
if (characterSet.empty())
{
characterSet = client.get("client_encoding", "").asString();
}
auto connectOptions = client.get("connect_options", Json::Value());
auto timeout = client.get("timeout", -1.0).asDouble();
auto autoBatch = client.get("auto_batch", false).asBool();
std::unordered_map<std::string, std::string> options;
if (connectOptions.isObject() && !connectOptions.empty())
{
for (const auto &key : connectOptions.getMemberNames())
{
options[key] = connectOptions[key].asString();
}
}
HttpAppFrameworkImpl::instance().addDbClient(type,
host,
port,
dbname,
user,
password,
connNum,
filename,
name,
isFast,
characterSet,
timeout,
autoBatch,
std::move(options));
}
}
static void loadRedisClients(const Json::Value &redisClients)
{
if (!redisClients)
return;
for (auto const &client : redisClients)
{
std::promise<std::string> promise;
auto future = promise.get_future();
auto host = client.get("host", "127.0.0.1").asString();
trantor::Resolver::newResolver()->resolve(
host, [&promise](const trantor::InetAddress &address) {
promise.set_value(address.toIp());
});
auto port = client.get("port", 6379).asUInt();
auto username = client.get("username", "").asString();
auto password = client.get("passwd", "").asString();
if (password.empty())
{
password = client.get("password", "").asString();
}
auto connNum = client.get("connection_number", 1).asUInt();
if (connNum == 1)
{
connNum = client.get("number_of_connections", 1).asUInt();
}
auto name = client.get("name", "default").asString();
auto isFast = client.get("is_fast", false).asBool();
auto timeout = client.get("timeout", -1.0).asDouble();
auto db = client.get("db", 0).asUInt();
auto hostIp = future.get();
drogon::app().createRedisClient(hostIp,
port,
name,
password,
connNum,
isFast,
timeout,
db,
username);
}
}
static void loadListeners(const Json::Value &listeners)
{
if (!listeners)
return;
LOG_TRACE << "Has " << listeners.size() << " listeners";
for (auto const &listener : listeners)
{
auto addr = listener.get("address", "0.0.0.0").asString();
auto port = (uint16_t)listener.get("port", 0).asUInt();
auto useSSL = listener.get("https", false).asBool();
auto cert = listener.get("cert", "").asString();
auto key = listener.get("key", "").asString();
auto useOldTLS = listener.get("use_old_tls", false).asBool();
std::vector<std::pair<std::string, std::string>> sslConfCmds;
if (listener.isMember("ssl_conf"))
{
for (const auto &opt : listener["ssl_conf"])
{
if (opt.size() == 0 || opt.size() > 2)
{
LOG_FATAL << "SSL configuration option should be an 1 or "
"2-element array";
abort();
}
sslConfCmds.emplace_back(opt[0].asString(),
opt.get(1, "").asString());
}
}
LOG_TRACE << "Add listener:" << addr << ":" << port;
drogon::app().addListener(
addr, port, useSSL, cert, key, useOldTLS, sslConfCmds);
}
}
static void loadSSL(const Json::Value &sslConf)
{
if (!sslConf)
return;
auto key = sslConf.get("key", "").asString();
auto cert = sslConf.get("cert", "").asString();
drogon::app().setSSLFiles(cert, key);
std::vector<std::pair<std::string, std::string>> sslConfCmds;
if (sslConf.isMember("conf"))
{
for (const auto &opt : sslConf["conf"])
{
if (opt.size() == 0 || opt.size() > 2)
{
LOG_FATAL << "SSL configuration option should be an 1 or "
"2-element array";
abort();
}
sslConfCmds.emplace_back(opt[0].asString(),
opt.get(1, "").asString());
}
}
drogon::app().setSSLConfigCommands(sslConfCmds);
}
void ConfigLoader::load()
{
// std::cout<<configJsonRoot_<<std::endl;
loadApp(configJsonRoot_["app"]);
loadSSL(configJsonRoot_["ssl"]);
loadListeners(configJsonRoot_["listeners"]);
loadDbClients(configJsonRoot_["db_clients"]);
loadRedisClients(configJsonRoot_["redis_clients"]);
}
+42
View File
@@ -0,0 +1,42 @@
/**
*
* ConfigLoader.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 <json/json.h>
#include <string>
#include <trantor/utils/NonCopyable.h>
namespace drogon
{
class ConfigLoader : public trantor::NonCopyable
{
public:
explicit ConfigLoader(const std::string &configFile) noexcept(false);
explicit ConfigLoader(const Json::Value &data);
explicit ConfigLoader(Json::Value &&data);
~ConfigLoader();
const Json::Value &jsonValue() const
{
return configJsonRoot_;
}
void load() noexcept(false);
private:
std::string configFile_;
Json::Value configJsonRoot_;
};
} // namespace drogon
+63
View File
@@ -0,0 +1,63 @@
/**
*
* @file ControllerBinderBase.h
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/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 <vector>
#include <memory>
#include <drogon/IOThreadStorage.h>
#include <drogon/HttpResponse.h>
#include "HttpRequestImpl.h"
namespace drogon
{
class HttpMiddlewareBase;
/**
* @brief A component to associate router class and controller class
*/
struct ControllerBinderBase
{
std::string handlerName_;
std::vector<std::string> middlewareNames_;
std::vector<std::shared_ptr<HttpMiddlewareBase>> middlewares_;
IOThreadStorage<HttpResponsePtr> responseCache_;
std::shared_ptr<std::string> corsMethods_;
bool isCORS_{false};
virtual ~ControllerBinderBase() = default;
virtual void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const = 0;
virtual bool isStreamHandler() const
{
return false;
}
};
struct RouteResult
{
enum
{
Success,
MethodNotAllowed,
NotFound
} result;
std::shared_ptr<ControllerBinderBase> binderPtr;
};
} // namespace drogon
+87
View File
@@ -0,0 +1,87 @@
/**
*
* 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 <drogon/Cookie.h>
#include <drogon/utils/Utilities.h>
#include <trantor/utils/Logger.h>
using namespace drogon;
std::string Cookie::cookieString() const
{
constexpr std::string_view prefix = "Set-Cookie: ";
std::string ret;
// reserve space to reduce frequency allocation
ret.reserve(prefix.size() + key_.size() + value_.size() + 30);
ret = prefix;
ret.append(key_).append("=").append(value_).append("; ");
if (expiresDate_.microSecondsSinceEpoch() !=
(std::numeric_limits<int64_t>::max)() &&
expiresDate_.microSecondsSinceEpoch() >= 0)
{
ret.append("Expires=")
.append(utils::getHttpFullDateStr(expiresDate_))
.append("; ");
}
if (maxAge_.has_value())
{
ret.append("Max-Age=")
.append(std::to_string(maxAge_.value()))
.append("; ");
}
if (!domain_.empty())
{
ret.append("Domain=").append(domain_).append("; ");
}
if (!path_.empty())
{
ret.append("Path=").append(path_).append("; ");
}
if (sameSite_ != SameSite::kNull)
{
switch (sameSite_)
{
case SameSite::kLax:
ret.append("SameSite=Lax; ");
break;
case SameSite::kStrict:
ret.append("SameSite=Strict; ");
break;
case SameSite::kNone:
ret.append("SameSite=None; ");
// Cookies with SameSite=None must now also specify the Secure
// attribute (they require a secure context/HTTPS).
ret.append("Secure; ");
break;
default:
// Lax replaced None as the default value to ensure that users
// have reasonably robust defense against some CSRF attacks
ret.append("SameSite=Lax; ");
}
}
if ((secure_ && sameSite_ != SameSite::kNone) || partitioned_)
{
ret.append("Secure; ");
}
if (httpOnly_)
{
ret.append("HttpOnly; ");
}
if (partitioned_)
{
ret.append("Partitioned; ");
}
ret.resize(ret.length() - 2); // delete last semicolon
ret.append("\r\n");
return ret;
}
+66
View File
@@ -0,0 +1,66 @@
/**
*
* @file DbClientManager.h
* @author 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/orm/DbClient.h>
#include <drogon/orm/DbConfig.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/IOThreadStorage.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <string>
#include <memory>
namespace drogon
{
namespace orm
{
class DbClientManager : public trantor::NonCopyable
{
public:
void createDbClients(const std::vector<trantor::EventLoop *> &ioLoops);
DbClientPtr getDbClient(const std::string &name)
{
assert(dbClientsMap_.find(name) != dbClientsMap_.end());
return dbClientsMap_[name];
}
~DbClientManager();
DbClientPtr getFastDbClient(const std::string &name)
{
auto iter = dbFastClientsMap_.find(name);
assert(iter != dbFastClientsMap_.end());
return iter->second.getThreadData();
}
void addDbClient(const DbConfig &config);
bool areAllDbClientsAvailable() const noexcept;
private:
std::map<std::string, DbClientPtr> dbClientsMap_;
struct DbInfo
{
std::string connectionInfo_;
DbConfig config_;
};
std::vector<DbInfo> dbInfos_;
std::map<std::string, IOThreadStorage<orm::DbClientPtr>> dbFastClientsMap_;
};
} // namespace orm
} // namespace drogon
@@ -0,0 +1,44 @@
/**
*
* DbClientManagerSkipped.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 "DbClientManager.h"
#include <algorithm>
#include <stdlib.h>
using namespace drogon::orm;
using namespace drogon;
void DbClientManager::createDbClients(
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
{
return;
}
void DbClientManager::addDbClient(const DbConfig &)
{
LOG_FATAL << "No database is supported by drogon, please install the "
"database development library first.";
abort();
}
bool DbClientManager::areAllDbClientsAvailable() const noexcept
{
LOG_FATAL << "No database is supported by drogon, please install the "
"database development library first.";
abort();
}
DbClientManager::~DbClientManager()
{
}
+122
View File
@@ -0,0 +1,122 @@
/**
*
* DrClassMap.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 <drogon/DrClassMap.h>
#include <drogon/DrObject.h>
#include <trantor/utils/Logger.h>
using namespace drogon;
namespace drogon
{
namespace internal
{
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>> &
getObjsMap()
{
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>>
singleInstanceMap;
return singleInstanceMap;
}
static std::mutex &getMapMutex()
{
static std::mutex mtx;
return mtx;
}
} // namespace internal
} // namespace drogon
void DrClassMap::registerClass(const std::string &className,
const DrAllocFunc &func,
const DrSharedAllocFunc &sharedFunc)
{
LOG_TRACE << "Register class:" << className;
getMap().insert(
std::make_pair(className, std::make_pair(func, sharedFunc)));
}
DrObjectBase *DrClassMap::newObject(const std::string &className)
{
auto iter = getMap().find(className);
if (iter != getMap().end())
{
return iter->second.first();
}
else
return nullptr;
}
std::shared_ptr<DrObjectBase> DrClassMap::newSharedObject(
const std::string &className)
{
auto iter = getMap().find(className);
if (iter != getMap().end())
{
if (iter->second.second)
return iter->second.second();
else
return std::shared_ptr<DrObjectBase>(iter->second.first());
}
else
return nullptr;
}
const std::shared_ptr<DrObjectBase> &DrClassMap::getSingleInstance(
const std::string &className)
{
auto &mtx = internal::getMapMutex();
auto &singleInstanceMap = internal::getObjsMap();
{
std::lock_guard<std::mutex> lock(mtx);
auto iter = singleInstanceMap.find(className);
if (iter != singleInstanceMap.end())
return iter->second;
}
auto newObj = newSharedObject(className);
{
std::lock_guard<std::mutex> lock(mtx);
auto ret = singleInstanceMap.insert(
std::make_pair(className, std::move(newObj)));
return ret.first->second;
}
}
void DrClassMap::setSingleInstance(const std::shared_ptr<DrObjectBase> &ins)
{
auto &mtx = internal::getMapMutex();
auto &singleInstanceMap = internal::getObjsMap();
std::lock_guard<std::mutex> lock(mtx);
singleInstanceMap[ins->className()] = ins;
}
std::vector<std::string> DrClassMap::getAllClassName()
{
std::vector<std::string> ret;
for (auto const &iter : getMap())
{
ret.push_back(iter.first);
}
return ret;
}
std::unordered_map<std::string, std::pair<DrAllocFunc, DrSharedAllocFunc>> &
DrClassMap::getMap()
{
static std::unordered_map<std::string,
std::pair<DrAllocFunc, DrSharedAllocFunc>>
map;
return map;
}
+63
View File
@@ -0,0 +1,63 @@
/**
*
* @file DrTemplateBase.cc
* @author 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 <drogon/DrClassMap.h>
#include <drogon/DrTemplateBase.h>
#include <trantor/utils/Logger.h>
#include <memory>
#include <regex>
using namespace drogon;
std::shared_ptr<DrTemplateBase> DrTemplateBase::newTemplate(
const std::string &templateName)
{
LOG_TRACE << "http view name=" << templateName;
auto l = templateName.length();
if (l >= 4 && templateName[l - 4] == '.' && templateName[l - 3] == 'c' &&
templateName[l - 2] == 's' && templateName[l - 1] == 'p')
{
std::string::size_type pos = 0;
std::string newName;
newName.reserve(templateName.size());
if (templateName[0] == '/' || templateName[0] == '\\')
{
pos = 1;
}
else if (templateName[0] == '.' &&
(templateName[1] == '/' || templateName[1] == '\\'))
{
pos = 2;
}
while (pos < l - 4)
{
if (templateName[pos] == '/' || templateName[pos] == '\\')
{
newName.append("::");
}
else
{
newName.append(1, templateName[pos]);
}
++pos;
}
return std::shared_ptr<DrTemplateBase>(dynamic_cast<DrTemplateBase *>(
drogon::DrClassMap::newObject(newName)));
}
else
{
return std::shared_ptr<DrTemplateBase>(dynamic_cast<DrTemplateBase *>(
drogon::DrClassMap::newObject(templateName)));
}
}
@@ -0,0 +1,32 @@
#include "FixedWindowRateLimiter.h"
using namespace drogon;
FixedWindowRateLimiter::FixedWindowRateLimiter(
size_t capacity,
std::chrono::duration<double> timeUnit)
: capacity_(capacity),
lastTime_(std::chrono::steady_clock::now()),
timeUnit_(timeUnit)
{
}
// implementation of the fixed window algorithm
bool FixedWindowRateLimiter::isAllowed()
{
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(
now - lastTime_);
if (duration >= timeUnit_)
{
currentRequests_ = 0;
lastTime_ = now;
}
if (currentRequests_ < capacity_)
{
currentRequests_++;
return true;
}
return false;
}
@@ -0,0 +1,22 @@
#pragma once
#include <drogon/RateLimiter.h>
#include <chrono>
namespace drogon
{
class FixedWindowRateLimiter : public RateLimiter
{
public:
FixedWindowRateLimiter(size_t capacity,
std::chrono::duration<double> timeUnit);
bool isAllowed() override;
~FixedWindowRateLimiter() noexcept override = default;
private:
size_t capacity_;
size_t currentRequests_{0};
std::chrono::steady_clock::time_point lastTime_;
std::chrono::duration<double> timeUnit_;
};
} // namespace drogon
+108
View File
@@ -0,0 +1,108 @@
#include <drogon/plugins/GlobalFilters.h>
#include <drogon/DrClassMap.h>
#include <drogon/HttpAppFramework.h>
#include "MiddlewaresFunction.h"
#include "HttpRequestImpl.h"
#include "HttpAppFrameworkImpl.h"
using namespace drogon::plugin;
void GlobalFilters::initAndStart(const Json::Value &config)
{
if (config.isMember("filters") && config["filters"].isArray())
{
auto &filters = config["filters"];
for (auto const &filter : filters)
{
if (filter.isString())
{
auto filterPtr = std::dynamic_pointer_cast<HttpFilterBase>(
drogon::DrClassMap::getSingleInstance(filter.asString()));
if (filterPtr)
{
filters_.push_back(filterPtr);
}
else
{
LOG_ERROR << "Filter " << filter.asString()
<< " not found!";
}
}
}
}
if (config.isMember("exempt"))
{
auto exempt = config["exempt"];
if (exempt.isArray())
{
std::string regexStr;
for (auto const &ex : exempt)
{
if (ex.isString())
{
regexStr.append("(").append(ex.asString()).append(")|");
}
else
{
LOG_ERROR << "exempt must be a string array!";
}
}
if (!regexStr.empty())
{
regexStr.pop_back();
exemptPegex_ = std::regex(regexStr);
regexFlag_ = true;
}
}
else if (exempt.isString())
{
exemptPegex_ = std::regex(exempt.asString());
regexFlag_ = true;
}
else
{
LOG_ERROR << "exempt must be a string or string array!";
}
}
std::weak_ptr<GlobalFilters> weakPtr = shared_from_this();
drogon::app().registerPreRoutingAdvice(
[weakPtr](const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&acb,
drogon::AdviceChainCallback &&accb) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
accb();
return;
}
if (thisPtr->regexFlag_)
{
if (std::regex_match(req->path(), thisPtr->exemptPegex_))
{
accb();
return;
}
}
drogon::middlewares_function::doFilters(
thisPtr->filters_,
std::static_pointer_cast<HttpRequestImpl>(req),
[acb = std::move(acb),
accb = std::move(accb)](const HttpResponsePtr &resp) {
if (resp)
{
acb(resp);
}
else
{
accb();
}
});
});
}
void GlobalFilters::shutdown()
{
filters_.clear();
}
+80
View File
@@ -0,0 +1,80 @@
#include <drogon/utils/monitoring/Histogram.h>
using namespace drogon;
using namespace drogon::monitoring;
void Histogram::observe(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
if (maxAge_ > std::chrono::seconds(0) &&
timerId_ == trantor::InvalidTimerId)
{
std::weak_ptr<Histogram> weakPtr =
std::dynamic_pointer_cast<Histogram>(shared_from_this());
timerId_ = loopPtr_->runEvery(maxAge_ / timeBucketCount_, [weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
thisPtr->rotateTimeBuckets();
});
}
auto &currentBucket = timeBuckets_.back();
currentBucket.sum += value;
currentBucket.count += 1;
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
{
if (value <= bucketBoundaries_[i])
{
currentBucket.buckets[i] += 1;
break;
}
}
if (value > bucketBoundaries_.back())
{
currentBucket.buckets.back() += 1;
}
}
std::vector<Sample> Histogram::collect() const
{
std::vector<Sample> samples;
std::lock_guard<std::mutex> guard(mutex_);
size_t count{0};
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
{
Sample sample;
for (auto &bucket : timeBuckets_)
{
count += bucket.buckets[i];
}
sample.name = name_ + "_bucket";
sample.exLabels.emplace_back("le",
std::to_string(bucketBoundaries_[i]));
sample.value = count;
samples.emplace_back(std::move(sample));
}
Sample sample;
for (auto &bucket : timeBuckets_)
{
count += bucket.buckets.back();
}
sample.name = name_ + "_bucket";
sample.exLabels.emplace_back("le", "+Inf");
sample.value = count;
samples.emplace_back(std::move(sample));
double sum{0};
uint64_t totalCount{0};
for (auto &bucket : timeBuckets_)
{
sum += bucket.sum;
totalCount += bucket.count;
}
Sample sumSample;
sumSample.name = name_ + "_sum";
sumSample.value = sum;
samples.emplace_back(std::move(sumSample));
Sample countSample;
countSample.name = name_ + "_count";
countSample.value = totalCount;
samples.emplace_back(std::move(countSample));
return samples;
}
+249
View File
@@ -0,0 +1,249 @@
#include <drogon/plugins/Hodor.h>
#include <drogon/plugins/RealIpResolver.h>
using namespace drogon::plugin;
Hodor::LimitStrategy Hodor::makeLimitStrategy(const Json::Value &config)
{
LimitStrategy strategy;
strategy.capacity = config.get("capacity", 0).asUInt();
if (config.isMember("urls") && config["urls"].isArray())
{
std::string regexString;
for (auto &str : config["urls"])
{
assert(str.isString());
regexString.append("(").append(str.asString()).append(")|");
}
if (!regexString.empty())
{
regexString.resize(regexString.length() - 1);
strategy.urlsRegex = std::regex(regexString);
strategy.regexFlag = true;
}
}
if (strategy.capacity > 0)
{
if (multiThreads_)
{
strategy.globalLimiterPtr = std::make_shared<SafeRateLimiter>(
RateLimiter::newRateLimiter(algorithm_,
strategy.capacity,
timeUnit_));
}
else
{
strategy.globalLimiterPtr =
RateLimiter::newRateLimiter(algorithm_,
strategy.capacity,
timeUnit_);
}
}
strategy.ipCapacity = config.get("ip_capacity", 0).asUInt();
if (strategy.ipCapacity > 0)
{
strategy.ipLimiterMapPtr =
std::make_unique<CacheMap<std::string, RateLimiterPtr>>(
drogon::app().getLoop(),
float(timeUnit_.count() / 60 < 1 ? 1 : timeUnit_.count() / 60),
2,
100);
}
strategy.userCapacity = config.get("user_capacity", 0).asUInt();
if (strategy.userCapacity > 0)
{
strategy.userLimiterMapPtr =
std::make_unique<CacheMap<std::string, RateLimiterPtr>>(
drogon::app().getLoop(),
float(timeUnit_.count() / 60 < 1 ? 1 : timeUnit_.count() / 60),
2,
100);
}
return strategy;
}
void Hodor::initAndStart(const Json::Value &config)
{
algorithm_ = stringToRateLimiterType(
config.get("algorithm", "token_bucket").asString());
timeUnit_ = std::chrono::seconds(config.get("time_unit", 60).asUInt());
multiThreads_ = config.get("multi_threads", true).asBool();
useRealIpResolver_ = config.get("use_real_ip_resolver", false).asBool();
rejectResponse_ = HttpResponse::newHttpResponse();
rejectResponse_->setStatusCode(k429TooManyRequests);
rejectResponse_->setBody(
config.get("rejection_message", "Too many requests").asString());
rejectResponse_->setCloseConnection(true);
limiterExpireTime_ =
(std::max)(static_cast<size_t>(
config.get("limiter_expire_time", 600).asUInt()),
static_cast<size_t>(timeUnit_.count() * 3));
limitStrategies_.emplace_back(makeLimitStrategy(config));
if (config.isMember("sub_limits") && config["sub_limits"].isArray())
{
for (auto &subLimit : config["sub_limits"])
{
assert(subLimit.isObject());
if (!subLimit["urls"].isArray() || subLimit["urls"].size() == 0)
{
LOG_ERROR
<< "The urls of sub_limits must be an array and not empty!";
continue;
}
if (subLimit["capacity"].asUInt() == 0 &&
subLimit["ip_capacity"].asUInt() == 0 &&
subLimit["user_capacity"].asUInt() == 0)
{
LOG_ERROR << "At least one capacity of sub_limits must be "
"greater than 0!";
continue;
}
limitStrategies_.emplace_back(makeLimitStrategy(subLimit));
}
}
const Json::Value &trustIps = config["trust_ips"];
if (!trustIps.isNull() && !trustIps.isArray())
{
throw std::runtime_error("Invalid trusted_ips. Should be array.");
}
for (const auto &ipOrCidr : trustIps)
{
trustCIDRs_.emplace_back(ipOrCidr.asString());
}
app().registerPreHandlingAdvice([this](const drogon::HttpRequestPtr &req,
AdviceCallback &&acb,
AdviceChainCallback &&accb) {
onHttpRequest(req, std::move(acb), std::move(accb));
});
}
void Hodor::shutdown()
{
LOG_TRACE << "Hodor plugin is shutdown!";
}
bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
const LimitStrategy &strategy,
const trantor::InetAddress &ip,
const std::optional<std::string> &userId)
{
if (RealIpResolver::matchCidr(ip, trustCIDRs_))
{
return true;
}
if (strategy.regexFlag)
{
if (!std::regex_match(req->path(), strategy.urlsRegex))
{
return true;
}
}
if (strategy.globalLimiterPtr)
{
if (!strategy.globalLimiterPtr->isAllowed())
{
return false;
}
}
if (strategy.ipCapacity > 0)
{
RateLimiterPtr limiterPtr;
strategy.ipLimiterMapPtr->modify(
ip.toIpNetEndian(),
[this, &limiterPtr, &strategy](RateLimiterPtr &ptr) {
if (!ptr)
{
if (multiThreads_)
{
ptr = std::make_shared<SafeRateLimiter>(
RateLimiter::newRateLimiter(algorithm_,
strategy.ipCapacity,
timeUnit_));
}
else
{
ptr = RateLimiter::newRateLimiter(algorithm_,
strategy.ipCapacity,
timeUnit_);
}
}
limiterPtr = ptr;
},
limiterExpireTime_);
if (!limiterPtr->isAllowed())
{
return false;
}
}
if (strategy.userCapacity > 0)
{
if (!userId.has_value())
{
return true;
}
RateLimiterPtr limiterPtr;
strategy.userLimiterMapPtr->modify(
*userId,
[this, &strategy, &limiterPtr](RateLimiterPtr &ptr) {
if (!ptr)
{
if (multiThreads_)
{
ptr = std::make_shared<SafeRateLimiter>(
RateLimiter::newRateLimiter(algorithm_,
strategy.userCapacity,
timeUnit_));
}
else
{
ptr = RateLimiter::newRateLimiter(algorithm_,
strategy.userCapacity,
timeUnit_);
}
}
limiterPtr = ptr;
},
limiterExpireTime_);
if (!limiterPtr->isAllowed())
{
return false;
}
}
return true;
}
void Hodor::onHttpRequest(const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&adviceCallback,
drogon::AdviceChainCallback &&chainCallback)
{
const trantor::InetAddress &ip =
useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
: req->peerAddr();
std::optional<std::string> userId;
if (userIdGetter_)
{
userId = userIdGetter_(req);
}
for (auto &strategy : limitStrategies_)
{
if (!checkLimit(req, strategy, ip, userId))
{
if (rejectResponseFactory_)
{
adviceCallback(rejectResponseFactory_(req));
}
else
{
adviceCallback(rejectResponse_);
}
return;
}
}
chainCallback();
}
File diff suppressed because it is too large Load Diff
+768
View File
@@ -0,0 +1,768 @@
/**
*
* @file HttpAppFrameworkImpl.h
* @author 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/HttpAppFramework.h>
#include <drogon/config.h>
#include <json/json.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "SessionManager.h"
#include "drogon/utils/Utilities.h"
#include "impl_forwards.h"
namespace trantor
{
class EventLoopThreadPool;
}
namespace drogon
{
HttpResponsePtr defaultErrorHandler(HttpStatusCode code,
const HttpRequestPtr &req);
void defaultExceptionHandler(const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
struct InitBeforeMainFunction
{
explicit InitBeforeMainFunction(const std::function<void()> &func)
{
func();
}
};
class HttpAppFrameworkImpl final : public HttpAppFramework
{
public:
HttpAppFrameworkImpl();
const Json::Value &getCustomConfig() const override
{
return jsonConfig_["custom_config"];
}
PluginBase *getPlugin(const std::string &name) override;
std::shared_ptr<PluginBase> getSharedPlugin(
const std::string &name) override;
void addPlugins(const Json::Value &configs) override;
void addPlugin(const std::string &name,
const std::vector<std::string> &dependencies,
const Json::Value &config) override;
HttpAppFramework &addListener(
const std::string &ip,
uint16_t port,
bool useSSL,
const std::string &certFile,
const std::string &keyFile,
bool useOldTLS,
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
override;
HttpAppFramework &setThreadNum(size_t threadNum) override;
size_t getThreadNum() const override
{
return threadNum_;
}
HttpAppFramework &setSSLConfigCommands(
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
override;
HttpAppFramework &setSSLFiles(const std::string &certPath,
const std::string &keyPath) override;
HttpAppFramework &reloadSSLFiles() override;
void run() override;
HttpAppFramework &registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints) override;
HttpAppFramework &registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints) override;
HttpAppFramework &registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints) override;
HttpAppFramework &setCustom404Page(const HttpResponsePtr &resp,
bool set404) override
{
if (set404)
{
resp->setStatusCode(k404NotFound);
}
custom404_ = resp;
return *this;
}
HttpAppFramework &setCustomErrorHandler(
std::function<HttpResponsePtr(HttpStatusCode,
const HttpRequestPtr &req)>
&&resp_generator) override;
const HttpResponsePtr &getCustom404Page();
void forward(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString,
double timeout) override;
void forward(const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString,
double timeout = 0);
HttpAppFramework &registerBeginningAdvice(
const std::function<void()> &advice) override
{
beginningAdvices_.emplace_back(advice);
return *this;
}
HttpAppFramework &registerNewConnectionAdvice(
const std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)> &advice)
override;
HttpAppFramework &registerHttpResponseCreationAdvice(
const std::function<void(const HttpResponsePtr &)> &advice) override;
HttpAppFramework &registerSyncAdvice(
const std::function<HttpResponsePtr(const HttpRequestPtr &)> &advice)
override;
HttpAppFramework &registerPreRoutingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override;
HttpAppFramework &registerPostRoutingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override;
HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override;
HttpAppFramework &registerPreRoutingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override;
HttpAppFramework &registerPostRoutingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override;
HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override;
HttpAppFramework &registerPostHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
const HttpResponsePtr &)> &advice) override;
HttpAppFramework &registerPreSendingAdvice(
const std::function<void(const HttpRequestPtr &,
const HttpResponsePtr &)> &advice) override;
HttpAppFramework &setDefaultHandler(DefaultHandler handler) override;
HttpAppFramework &setupFileLogger() override;
HttpAppFramework &enableSession(
const size_t timeout,
Cookie::SameSite sameSite = Cookie::SameSite::kNull,
const std::string &cookieKey = "JSESSIONID",
int maxAge = -1,
SessionManager::IdGeneratorCallback idGeneratorCallback =
nullptr) override
{
useSession_ = true;
sessionTimeout_ = timeout;
sessionSameSite_ = sameSite;
sessionCookieKey_ = cookieKey;
sessionMaxAge_ = maxAge;
return setSessionIdGenerator(idGeneratorCallback);
}
HttpAppFramework &setSessionIdGenerator(
SessionManager::IdGeneratorCallback idGeneratorCallback = nullptr)
{
sessionIdGeneratorCallback_ =
idGeneratorCallback ? idGeneratorCallback
: []() { return utils::getUuid(true); };
return *this;
}
HttpAppFramework &disableSession() override
{
useSession_ = false;
return *this;
}
HttpAppFramework &registerSessionStartAdvice(
const AdviceStartSessionCallback &advice) override
{
sessionStartAdvices_.emplace_back(advice);
return *this;
}
HttpAppFramework &registerSessionDestroyAdvice(
const AdviceDestroySessionCallback &advice) override
{
sessionDestroyAdvices_.emplace_back(advice);
return *this;
}
const std::string &getDocumentRoot() const override
{
return rootPath_;
}
HttpAppFramework &setDocumentRoot(const std::string &rootPath) override
{
rootPath_ = rootPath;
return *this;
}
HttpAppFramework &setStaticFileHeaders(
const std::vector<std::pair<std::string, std::string>> &headers)
override;
HttpAppFramework &addALocation(
const std::string &uriPrefix,
const std::string &defaultContentType,
const std::string &alias,
bool isCaseSensitive,
bool allowAll,
bool isRecursive,
const std::vector<std::string> &middlewareNames) override;
const std::string &getUploadPath() const override
{
return uploadPath_;
}
const std::shared_ptr<trantor::Resolver> &getResolver() const override
{
static auto resolver = trantor::Resolver::newResolver(getLoop());
return resolver;
}
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
HttpAppFramework &setFileTypes(
const std::vector<std::string> &types) override;
#if !defined(_WIN32) && !TARGET_OS_IOS
HttpAppFramework &enableDynamicViewsLoading(
const std::vector<std::string> &libPaths,
const std::string &outputPath) override;
#endif
HttpAppFramework &setMaxConnectionNum(size_t maxConnections) override;
HttpAppFramework &setMaxConnectionNumPerIP(
size_t maxConnectionsPerIP) override;
HttpAppFramework &loadConfigFile(const std::string &fileName) noexcept(
false) override;
HttpAppFramework &loadConfigJson(const Json::Value &data) noexcept(
false) override;
HttpAppFramework &loadConfigJson(Json::Value &&data) noexcept(
false) override;
HttpAppFramework &enableRunAsDaemon() override
{
runAsDaemon_ = true;
return *this;
}
HttpAppFramework &disableSigtermHandling() override
{
handleSigterm_ = false;
return *this;
}
HttpAppFramework &enableRelaunchOnError() override
{
relaunchOnError_ = true;
return *this;
}
HttpAppFramework &setLogPath(const std::string &logPath,
const std::string &logfileBaseName,
size_t logfileSize,
size_t maxFiles,
bool useSpdlog) override;
HttpAppFramework &setLogLevel(trantor::Logger::LogLevel level) override;
HttpAppFramework &setLogLocalTime(bool on) override;
HttpAppFramework &enableSendfile(bool sendFile) override
{
useSendfile_ = sendFile;
return *this;
}
HttpAppFramework &enableGzip(bool useGzip) override
{
useGzip_ = useGzip;
return *this;
}
bool isGzipEnabled() const override
{
return useGzip_;
}
HttpAppFramework &enableBrotli(bool useBrotli) override
{
useBrotli_ = useBrotli;
return *this;
}
bool isBrotliEnabled() const override
{
return useBrotli_;
}
HttpAppFramework &setStaticFilesCacheTime(int cacheTime) override;
int staticFilesCacheTime() const override;
HttpAppFramework &setIdleConnectionTimeout(size_t timeout) override
{
idleConnectionTimeout_ = timeout;
return *this;
}
size_t getIdleConnectionTimeout() const // could expose in base class
{
return idleConnectionTimeout_;
}
HttpAppFramework &setKeepaliveRequestsNumber(const size_t number) override
{
keepaliveRequestsNumber_ = number;
return *this;
}
HttpAppFramework &setPipeliningRequestsNumber(const size_t number) override
{
pipeliningRequestsNumber_ = number;
return *this;
}
HttpAppFramework &setGzipStatic(bool useGzipStatic) override;
HttpAppFramework &setBrStatic(bool useGzipStatic) override;
HttpAppFramework &setClientMaxBodySize(size_t maxSize) override
{
clientMaxBodySize_ = maxSize;
return *this;
}
HttpAppFramework &setClientMaxMemoryBodySize(size_t maxSize) override
{
clientMaxMemoryBodySize_ = maxSize;
return *this;
}
HttpAppFramework &setClientMaxWebSocketMessageSize(size_t maxSize) override
{
clientMaxWebSocketMessageSize_ = maxSize;
return *this;
}
HttpAppFramework &setHomePage(const std::string &homePageFile) override
{
homePageFile_ = homePageFile;
return *this;
}
const std::string &getHomePage() const override
{
return homePageFile_;
}
HttpAppFramework &setTermSignalHandler(
const std::function<void()> &handler) override
{
termSignalHandler_ = handler;
return *this;
}
const std::function<void()> &getTermSignalHandler() const
{
return termSignalHandler_;
}
HttpAppFramework &setIntSignalHandler(
const std::function<void()> &handler) override
{
intSignalHandler_ = handler;
return *this;
}
const std::function<void()> &getIntSignalHandler() const
{
return intSignalHandler_;
}
HttpAppFramework &setImplicitPageEnable(bool useImplicitPage) override;
bool isImplicitPageEnabled() const override;
HttpAppFramework &setImplicitPage(
const std::string &implicitPageFile) override;
const std::string &getImplicitPage() const override;
size_t getClientMaxBodySize() const
{
return clientMaxBodySize_;
}
size_t getClientMaxMemoryBodySize() const
{
return clientMaxMemoryBodySize_;
}
size_t getClientMaxWebSocketMessageSize() const
{
return clientMaxWebSocketMessageSize_;
}
std::vector<HttpHandlerInfo> getHandlersInfo() const override;
size_t keepaliveRequestsNumber() const
{
return keepaliveRequestsNumber_;
}
size_t pipeliningRequestsNumber() const
{
return pipeliningRequestsNumber_;
}
~HttpAppFrameworkImpl() noexcept override;
bool isRunning() override
{
return running_;
}
HttpAppFramework &setJsonParserStackLimit(size_t limit) noexcept override
{
jsonStackLimit_ = limit;
return *this;
}
size_t getJsonParserStackLimit() const noexcept override
{
return jsonStackLimit_;
}
HttpAppFramework &setUnicodeEscapingInJson(bool enable) noexcept override
{
usingUnicodeEscaping_ = enable;
return *this;
}
bool isUnicodeEscapingUsedInJson() const noexcept override
{
return usingUnicodeEscaping_;
}
HttpAppFramework &setFloatPrecisionInJson(
unsigned int precision,
const std::string &precisionType) noexcept override
{
floatPrecisionInJson_ = std::make_pair(precision, precisionType);
return *this;
}
const std::pair<unsigned int, std::string> &getFloatPrecisionInJson()
const noexcept override
{
return floatPrecisionInJson_;
}
trantor::EventLoop *getLoop() const override;
trantor::EventLoop *getIOLoop(size_t id) const override;
void quit() override;
HttpAppFramework &setServerHeaderField(const std::string &server) override
{
assert(!running_);
assert(server.find("\r\n") == std::string::npos);
serverHeader_ = "server: " + server + "\r\n";
return *this;
}
HttpAppFramework &enableServerHeader(bool flag) override
{
enableServerHeader_ = flag;
return *this;
}
HttpAppFramework &enableDateHeader(bool flag) override
{
enableDateHeader_ = flag;
return *this;
}
bool sendServerHeader() const
{
return enableServerHeader_;
}
bool sendDateHeader() const
{
return enableDateHeader_;
}
const std::string &getServerHeaderString() const
{
return serverHeader_;
}
orm::DbClientPtr getDbClient(const std::string &name) override;
orm::DbClientPtr getFastDbClient(const std::string &name) override;
HttpAppFramework &createDbClient(const std::string &dbType,
const std::string &host,
unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
size_t connectionNum,
const std::string &filename,
const std::string &name,
bool isFast,
const std::string &characterSet,
double timeout,
bool autoBatch) override;
// a helper method
void addDbClient(const std::string &dbType,
const std::string &host,
unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
size_t connectionNum,
const std::string &filename,
const std::string &name,
bool isFast,
const std::string &characterSet,
double timeout,
bool autoBatch,
std::unordered_map<std::string, std::string> options);
HttpAppFramework &addDbClient(const orm::DbConfig &config) override;
HttpAppFramework &createRedisClient(const std::string &ip,
unsigned short port,
const std::string &name,
const std::string &password,
size_t connectionNum,
bool isFast,
double timeout,
unsigned int db,
const std::string &username) override;
nosql::RedisClientPtr getRedisClient(const std::string &name) override;
nosql::RedisClientPtr getFastRedisClient(const std::string &name) override;
std::vector<trantor::InetAddress> getListeners() const override;
inline static HttpAppFrameworkImpl &instance()
{
static HttpAppFrameworkImpl instance;
return instance;
}
bool useSendfile() const
{
return useSendfile_;
}
bool supportSSL() const override
{
return trantor::utils::tlsBackend() != "None";
}
size_t getCurrentThreadIndex() const override
{
auto *loop = trantor::EventLoop::getEventLoopOfCurrentThread();
if (loop)
{
return loop->index();
}
return (std::numeric_limits<size_t>::max)();
}
bool areAllDbClientsAvailable() const noexcept override;
const std::function<HttpResponsePtr(HttpStatusCode,
const HttpRequestPtr &req)> &
getCustomErrorHandler() const override;
bool isUsingCustomErrorHandler() const
{
return usingCustomErrorHandler_;
}
void enableReusePort(bool enable) override
{
reusePort_ = enable;
}
bool reusePort() const override
{
return reusePort_;
}
HttpAppFramework &setExceptionHandler(ExceptionHandler handler) override
{
exceptionHandler_ = std::move(handler);
return *this;
}
const ExceptionHandler &getExceptionHandler() const override
{
return exceptionHandler_;
}
HttpAppFramework &enableCompressedRequest(bool enable) override
{
enableCompressedRequest_ = enable;
return *this;
}
bool isCompressedRequestEnabled() const override
{
return enableCompressedRequest_;
}
HttpAppFramework &registerCustomExtensionMime(
const std::string &ext,
const std::string &mime) override;
// should return unsigned type!
int64_t getConnectionCount() const override;
// TODO: move session related codes to its own singleton class
void findSessionForRequest(const HttpRequestImplPtr &req);
HttpResponsePtr handleSessionForResponse(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp);
HttpAppFramework &setBeforeListenSockOptCallback(
std::function<void(int)> cb) override;
HttpAppFramework &setAfterAcceptSockOptCallback(
std::function<void(int)> cb) override;
HttpAppFramework &setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb) override;
HttpAppFramework &enableRequestStream(bool enable) override;
bool isRequestStreamEnabled() const override;
private:
void registerHttpController(const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName) override;
void registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName) override;
// We use an uuid string as session id;
// set sessionTimeout_=0 to make location session valid forever based on
// cookies;
size_t sessionTimeout_{0};
Cookie::SameSite sessionSameSite_{Cookie::SameSite::kNull};
std::string sessionCookieKey_{"JSESSIONID"};
int sessionMaxAge_{-1};
size_t idleConnectionTimeout_{60};
bool useSession_{false};
std::string serverHeader_{"server: drogon/" + drogon::getVersion() +
"\r\n"};
std::unique_ptr<ListenerManager> listenerManagerPtr_;
std::unique_ptr<PluginsManager> pluginsManagerPtr_;
std::unique_ptr<orm::DbClientManager> dbClientManagerPtr_;
std::unique_ptr<nosql::RedisClientManager> redisClientManagerPtr_;
std::string rootPath_{"./"};
std::string uploadPath_;
std::atomic_bool running_{false};
std::atomic_bool routersInit_{false};
size_t threadNum_{1};
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
#if !defined(_WIN32) && !TARGET_OS_IOS
std::vector<std::string> libFilePaths_;
std::string libFileOutputPath_;
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;
#endif
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
std::string sslCertPath_;
std::string sslKeyPath_;
bool runAsDaemon_{false};
bool handleSigterm_{true};
bool relaunchOnError_{false};
bool logWithSpdlog_{false};
std::string logPath_;
std::string logfileBaseName_;
size_t logfileSize_{100000000};
size_t logfileMaxNum_{0};
size_t keepaliveRequestsNumber_{0};
size_t pipeliningRequestsNumber_{0};
size_t jsonStackLimit_{1000};
bool useSendfile_{true};
bool useGzip_{true};
bool useBrotli_{false};
bool usingUnicodeEscaping_{true};
std::pair<unsigned int, std::string> floatPrecisionInJson_{0,
"significant"};
bool usingCustomErrorHandler_{false};
size_t clientMaxBodySize_{1024 * 1024};
size_t clientMaxMemoryBodySize_{64 * 1024};
size_t clientMaxWebSocketMessageSize_{128 * 1024};
std::string homePageFile_{"index.html"};
std::function<void()> termSignalHandler_{[]() { app().quit(); }};
std::function<void()> intSignalHandler_{[]() { app().quit(); }};
std::unique_ptr<SessionManager> sessionManagerPtr_;
std::vector<AdviceStartSessionCallback> sessionStartAdvices_;
std::vector<AdviceDestroySessionCallback> sessionDestroyAdvices_;
SessionManager::IdGeneratorCallback sessionIdGeneratorCallback_;
std::shared_ptr<trantor::AsyncFileLogger> asyncFileLoggerPtr_;
Json::Value jsonConfig_;
Json::Value jsonRuntimeConfig_;
HttpResponsePtr custom404_;
std::function<HttpResponsePtr(HttpStatusCode, const HttpRequestPtr &req)>
customErrorHandler_ = &defaultErrorHandler;
static InitBeforeMainFunction initFirst_;
bool enableServerHeader_{true};
bool enableDateHeader_{true};
bool reusePort_{false};
std::vector<std::function<void()>> beginningAdvices_;
ExceptionHandler exceptionHandler_{defaultExceptionHandler};
bool enableCompressedRequest_{false};
bool enableRequestStream_{false};
};
} // namespace drogon
+29
View File
@@ -0,0 +1,29 @@
/**
*
* HttpBinder.h
* Martin Chang
*
* Copyright 2021, Martin Chang. 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 <drogon/HttpBinder.h>
#include <drogon/HttpAppFramework.h>
namespace drogon
{
namespace internal
{
void handleException(const std::exception &e,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
app().getExceptionHandler()(e, req, std::move(callback));
}
} // namespace internal
} // namespace drogon
+728
View File
@@ -0,0 +1,728 @@
/**
*
* @file HttpClientImpl.cc
* @author 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 "HttpClientImpl.h"
#include "HttpAppFrameworkImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "HttpResponseParser.h"
#include <drogon/config.h>
#include <stdlib.h>
#include <algorithm>
using namespace trantor;
using namespace drogon;
using namespace std::placeholders;
namespace trantor
{
static const size_t kDefaultDNSTimeout{600};
}
void HttpClientImpl::createTcpClient()
{
LOG_TRACE << "New TcpClient," << serverAddr_.toIpPort();
tcpClientPtr_ =
std::make_shared<trantor::TcpClient>(loop_, serverAddr_, "httpClient");
if (useSSL_ && utils::supportsTls())
{
LOG_TRACE << "useOldTLS=" << useOldTLS_;
LOG_TRACE << "domain=" << domain_;
auto policy = trantor::TLSPolicy::defaultClientPolicy();
policy->setUseOldTLS(useOldTLS_)
.setValidate(validateCert_)
.setHostname(domain_)
.setConfCmds(sslConfCmds_)
.setCertPath(clientCertPath_)
.setKeyPath(clientKeyPath_);
tcpClientPtr_->enableSSL(std::move(policy));
}
auto thisPtr = shared_from_this();
std::weak_ptr<HttpClientImpl> weakPtr = thisPtr;
tcpClientPtr_->setSockOptCallback([weakPtr](int fd) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (thisPtr->sockOptCallback_)
thisPtr->sockOptCallback_(fd);
});
tcpClientPtr_->setConnectionCallback(
[weakPtr](const trantor::TcpConnectionPtr &connPtr) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (connPtr->connected())
{
connPtr->setContext(
std::make_shared<HttpResponseParser>(connPtr));
// send request;
LOG_TRACE << "Connection established!";
while (thisPtr->pipeliningCallbacks_.size() <=
thisPtr->pipeliningDepth_ &&
!thisPtr->requestsBuffer_.empty())
{
thisPtr->sendReq(connPtr,
thisPtr->requestsBuffer_.front().first);
thisPtr->pipeliningCallbacks_.push(
std::move(thisPtr->requestsBuffer_.front()));
thisPtr->requestsBuffer_.pop_front();
}
}
else
{
LOG_TRACE << "connection disconnect";
auto responseParser = connPtr->getContext<HttpResponseParser>();
if (responseParser && responseParser->parseResponseOnClose() &&
responseParser->gotAll())
{
auto &firstReq = thisPtr->pipeliningCallbacks_.front();
if (firstReq.first->method() == Head)
{
responseParser->setForHeadMethod();
}
auto resp = responseParser->responseImpl();
responseParser->reset();
// temporary fix of dead tcpClientPtr_
// TODO: fix HttpResponseParser when content-length absence
thisPtr->tcpClientPtr_.reset();
thisPtr->handleResponse(resp, std::move(firstReq), connPtr);
if (!thisPtr->requestsBuffer_.empty())
{
thisPtr->createTcpClient();
}
return;
}
thisPtr->onError(ReqResult::NetworkFailure);
}
});
tcpClientPtr_->setConnectionErrorCallback([weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
// can't connect to server
thisPtr->onError(ReqResult::BadServerAddress);
});
tcpClientPtr_->setMessageCallback(
[weakPtr](const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
thisPtr->onRecvMessage(connPtr, msg);
}
});
tcpClientPtr_->setSSLErrorCallback([weakPtr](SSLError err) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (err == trantor::SSLError::kSSLHandshakeError)
thisPtr->onError(ReqResult::HandshakeError);
else if (err == trantor::SSLError::kSSLInvalidCertificate)
thisPtr->onError(ReqResult::InvalidCertificate);
else if (err == trantor::SSLError::kSSLProtocolError)
thisPtr->onError(ReqResult::EncryptionFailure);
else
{
LOG_FATAL << "Invalid value for SSLError";
abort();
}
});
tcpClientPtr_->connect();
}
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
const trantor::InetAddress &addr,
bool useSSL,
bool useOldTLS,
bool validateCert)
: loop_(loop),
serverAddr_(addr),
useSSL_(useSSL),
validateCert_(validateCert),
useOldTLS_(useOldTLS)
{
}
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
const std::string &hostString,
bool useOldTLS,
bool validateCert)
: loop_(loop), validateCert_(validateCert), useOldTLS_(useOldTLS)
{
auto lowerHost = hostString;
std::transform(lowerHost.begin(),
lowerHost.end(),
lowerHost.begin(),
[](unsigned char c) { return tolower(c); });
if (lowerHost.find("https://") == 0)
{
useSSL_ = true;
lowerHost = lowerHost.substr(8);
}
else if (lowerHost.find("http://") == 0)
{
useSSL_ = false;
lowerHost = lowerHost.substr(7);
}
else
{
return;
}
auto pos = lowerHost.find(']');
if (lowerHost[0] == '[' && pos != std::string::npos)
{
// ipv6
domain_ = lowerHost.substr(1, pos - 1);
if (lowerHost[pos + 1] == ':')
{
auto portStr = lowerHost.substr(pos + 2);
pos = portStr.find('/');
if (pos != std::string::npos)
{
portStr = portStr.substr(0, pos);
}
auto port = atoi(portStr.c_str());
if (port > 0 && port < 65536)
{
serverAddr_ = InetAddress(domain_, port, true);
}
}
else
{
if (useSSL_)
{
serverAddr_ = InetAddress(domain_, 443, true);
}
else
{
serverAddr_ = InetAddress(domain_, 80, true);
}
}
}
else
{
auto pos = lowerHost.find(':');
if (pos != std::string::npos)
{
domain_ = lowerHost.substr(0, pos);
auto portStr = lowerHost.substr(pos + 1);
pos = portStr.find('/');
if (pos != std::string::npos)
{
portStr = portStr.substr(0, pos);
}
auto port = atoi(portStr.c_str());
if (port > 0 && port < 65536)
{
serverAddr_ = InetAddress(domain_, port);
}
}
else
{
domain_ = lowerHost;
pos = domain_.find('/');
if (pos != std::string::npos)
{
domain_ = domain_.substr(0, pos);
}
if (useSSL_)
{
serverAddr_ = InetAddress(domain_, 443);
}
else
{
serverAddr_ = InetAddress(domain_, 80);
}
}
}
if (serverAddr_.isUnspecified())
{
isDomainName_ = true;
}
LOG_TRACE << "userSSL=" << useSSL_ << " domain=" << domain_;
}
HttpClientImpl::~HttpClientImpl()
{
LOG_TRACE << "Deconstruction HttpClient";
if (resolverPtr_ && !(loop_->isInLoopThread()))
{
// Make sure the resolverPtr_ is destroyed in the correct thread.
loop_->queueInLoop([resolverPtr = std::move(resolverPtr_)]() {});
}
}
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req,
const drogon::HttpReqCallback &callback,
double timeout)
{
auto thisPtr = shared_from_this();
loop_->runInLoop([thisPtr, req, callback = callback, timeout]() mutable {
thisPtr->sendRequestInLoop(req, std::move(callback), timeout);
});
}
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req,
drogon::HttpReqCallback &&callback,
double timeout)
{
auto thisPtr = shared_from_this();
loop_->runInLoop(
[thisPtr, req, callback = std::move(callback), timeout]() mutable {
thisPtr->sendRequestInLoop(req, std::move(callback), timeout);
});
}
struct RequestCallbackParams
{
RequestCallbackParams(HttpReqCallback &&cb,
HttpClientImplPtr client,
HttpRequestPtr req)
: callback(std::move(cb)),
clientPtr(std::move(client)),
requestPtr(std::move(req))
{
}
const drogon::HttpReqCallback callback;
const HttpClientImplPtr clientPtr;
const HttpRequestPtr requestPtr;
bool timeoutFlag{false};
};
void HttpClientImpl::sendRequestInLoop(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout)
{
if (timeout <= 0)
{
sendRequestInLoop(req, std::move(callback));
return;
}
auto callbackParamsPtr =
std::make_shared<RequestCallbackParams>(std::move(callback),
shared_from_this(),
req);
loop_->runAfter(
timeout,
[weakCallbackBackPtr =
std::weak_ptr<RequestCallbackParams>(callbackParamsPtr)] {
auto callbackParamsPtr = weakCallbackBackPtr.lock();
if (callbackParamsPtr != nullptr)
{
auto &thisPtr = callbackParamsPtr->clientPtr;
if (callbackParamsPtr->timeoutFlag)
{
return;
}
callbackParamsPtr->timeoutFlag = true;
for (auto iter = thisPtr->requestsBuffer_.begin();
iter != thisPtr->requestsBuffer_.end();
++iter)
{
if (iter->first == callbackParamsPtr->requestPtr)
{
thisPtr->requestsBuffer_.erase(iter);
break;
}
}
(callbackParamsPtr->callback)(ReqResult::Timeout, nullptr);
}
});
sendRequestInLoop(req,
[callbackParamsPtr](ReqResult r,
const HttpResponsePtr &resp) {
if (callbackParamsPtr->timeoutFlag)
{
return;
}
callbackParamsPtr->timeoutFlag = true;
(callbackParamsPtr->callback)(r, resp);
});
}
static bool isValidIpAddr(const trantor::InetAddress &addr)
{
if (addr.portNetEndian() == 0)
{
return false;
}
if (!addr.isIpV6())
{
return addr.ipNetEndian() != 0;
}
// Is ipv6
auto ipaddr = addr.ip6NetEndian();
for (int i = 0; i < 4; ++i)
{
if (ipaddr[i] != 0)
{
return true;
}
}
return false;
}
void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
drogon::HttpReqCallback &&callback)
{
loop_->assertInLoopThread();
if (!static_cast<drogon::HttpRequestImpl *>(req.get())->passThrough())
{
req->addHeader("connection", "Keep-Alive");
if (!userAgent_.empty())
req->addHeader("user-agent", userAgent_);
}
// Set the host header if not already set
if (req->getHeader("host").empty())
{
if (onDefaultPort())
{
req->addHeader("host", host());
}
else
{
req->addHeader("host", host() + ":" + std::to_string(port()));
}
}
for (auto &cookie : validCookies_)
{
if ((cookie.expiresDate().microSecondsSinceEpoch() == 0 ||
cookie.expiresDate() > trantor::Date::now()) &&
(cookie.path().empty() || req->path().find(cookie.path()) == 0))
{
req->addCookie(cookie.key(), cookie.value());
}
}
if (!tcpClientPtr_)
{
auto callbackPtr =
std::make_shared<drogon::HttpReqCallback>(std::move(callback));
requestsBuffer_.push_back(
{req,
[thisPtr = shared_from_this(),
callbackPtr](ReqResult result, const HttpResponsePtr &response) {
(*callbackPtr)(result, response);
}});
if (domain_.empty() || !isDomainName_)
{
// Valid ip address, no domain, connect directly
if (isValidIpAddr(serverAddr_))
{
createTcpClient();
}
// No ip address and no domain, respond with BadServerAddress
else
{
requestsBuffer_.pop_front();
(*callbackPtr)(ReqResult::BadServerAddress, nullptr);
assert(requestsBuffer_.empty());
}
return;
}
// A dns query is on going.
if (dns_)
{
return;
}
// Always do dns query when (re)connects a domain.
dns_ = true;
if (!resolverPtr_)
{
resolverPtr_ =
trantor::Resolver::newResolver(loop_, kDefaultDNSTimeout);
}
auto thisPtr = shared_from_this();
resolverPtr_->resolve(
domain_, [thisPtr](const trantor::InetAddress &addr) {
thisPtr->loop_->runInLoop([thisPtr, addr]() {
// Retrieve port from old serverAddr_
auto port = thisPtr->serverAddr_.portNetEndian();
thisPtr->serverAddr_ = addr;
thisPtr->serverAddr_.setPortNetEndian(port);
LOG_TRACE << "dns:domain=" << thisPtr->domain_
<< ";ip=" << thisPtr->serverAddr_.toIp();
thisPtr->dns_ = false;
if (isValidIpAddr(thisPtr->serverAddr_))
{
thisPtr->createTcpClient();
return;
}
// DNS fail to get valid ip address,
// respond all requests with BadServerAddress
while (!(thisPtr->requestsBuffer_).empty())
{
auto &reqAndCb = (thisPtr->requestsBuffer_).front();
reqAndCb.second(ReqResult::BadServerAddress, nullptr);
(thisPtr->requestsBuffer_).pop_front();
}
});
});
return;
}
// send request;
auto connPtr = tcpClientPtr_->connection();
auto thisPtr = shared_from_this();
// Not connected, push request to buffer and wait for connection
if (!connPtr || connPtr->disconnected())
{
requestsBuffer_.push_back(
{req,
[thisPtr,
callback = std::move(callback)](ReqResult result,
const HttpResponsePtr &response) {
callback(result, response);
}});
return;
}
// Connected, send request now
if (pipeliningCallbacks_.size() <= pipeliningDepth_ &&
requestsBuffer_.empty())
{
sendReq(connPtr, req);
pipeliningCallbacks_.push(
{req,
[thisPtr,
callback = std::move(callback)](ReqResult result,
const HttpResponsePtr &response) {
callback(result, response);
}});
}
else
{
requestsBuffer_.push_back(
{req,
[thisPtr,
callback = std::move(callback)](ReqResult result,
const HttpResponsePtr &response) {
callback(result, response);
}});
}
}
void HttpClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr,
const HttpRequestPtr &req)
{
trantor::MsgBuffer buffer;
assert(req);
auto implPtr = static_cast<HttpRequestImpl *>(req.get());
implPtr->appendToBuffer(&buffer);
LOG_TRACE << "Send request:"
<< std::string(buffer.peek(), buffer.readableBytes());
bytesSent_ += buffer.readableBytes();
connPtr->send(std::move(buffer));
}
void HttpClientImpl::handleResponse(
const HttpResponseImplPtr &resp,
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
const trantor::TcpConnectionPtr &connPtr)
{
assert(!pipeliningCallbacks_.empty());
auto &coding = resp->getHeaderBy("content-encoding");
if (coding == "gzip")
{
resp->gunzip();
}
#ifdef USE_BROTLI
else if (coding == "br")
{
resp->brDecompress();
}
#endif
auto cb = std::move(reqAndCb);
pipeliningCallbacks_.pop();
handleCookies(resp);
cb.second(ReqResult::Ok, resp);
// LOG_TRACE << "pipelining buffer size=" <<
// pipeliningCallbacks_.size(); LOG_TRACE << "requests buffer size="
// << requestsBuffer_.size();
if (connPtr->connected())
{
if (!requestsBuffer_.empty())
{
auto &reqAndCallback = requestsBuffer_.front();
sendReq(connPtr, reqAndCallback.first);
pipeliningCallbacks_.push(std::move(reqAndCallback));
requestsBuffer_.pop_front();
}
else
{
if (resp->ifCloseConnection() && pipeliningCallbacks_.empty())
{
tcpClientPtr_.reset();
}
}
}
else
{
while (!pipeliningCallbacks_.empty())
{
auto cb = std::move(pipeliningCallbacks_.front());
pipeliningCallbacks_.pop();
cb.second(ReqResult::NetworkFailure, nullptr);
}
}
}
void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg)
{
auto responseParser = connPtr->getContext<HttpResponseParser>();
// LOG_TRACE << "###:" << msg->readableBytes();
auto msgSize = msg->readableBytes();
while (msg->readableBytes() > 0)
{
if (pipeliningCallbacks_.empty())
{
LOG_ERROR << "More responses than expected!";
connPtr->shutdown();
return;
}
auto &firstReq = pipeliningCallbacks_.front();
if (firstReq.first->method() == Head)
{
responseParser->setForHeadMethod();
}
if (!responseParser->parseResponse(msg))
{
onError(ReqResult::BadResponse);
bytesReceived_ += (msgSize - msg->readableBytes());
return;
}
if (responseParser->gotAll())
{
auto resp = responseParser->responseImpl();
resp->setPeerCertificate(connPtr->peerCertificate());
responseParser->reset();
bytesReceived_ += (msgSize - msg->readableBytes());
msgSize = msg->readableBytes();
handleResponse(resp, std::move(firstReq), connPtr);
}
else
{
bytesReceived_ += (msgSize - msg->readableBytes());
break;
}
}
}
HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
uint16_t port,
bool useSSL,
trantor::EventLoop *loop,
bool useOldTLS,
bool validateCert)
{
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
return std::make_shared<HttpClientImpl>(
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
trantor::InetAddress(ip, port, isIpv6),
useSSL,
useOldTLS,
validateCert);
}
HttpClientPtr HttpClient::newHttpClient(const std::string &hostString,
trantor::EventLoop *loop,
bool useOldTLS,
bool validateCert)
{
return std::make_shared<HttpClientImpl>(
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
hostString,
useOldTLS,
validateCert);
}
void HttpClientImpl::onError(ReqResult result)
{
while (!pipeliningCallbacks_.empty())
{
auto cb = std::move(pipeliningCallbacks_.front());
pipeliningCallbacks_.pop();
cb.second(result, nullptr);
}
while (!requestsBuffer_.empty())
{
auto cb = std::move(requestsBuffer_.front().second);
requestsBuffer_.pop_front();
cb(result, nullptr);
}
tcpClientPtr_.reset();
}
void HttpClientImpl::handleCookies(const HttpResponseImplPtr &resp)
{
loop_->assertInLoopThread();
if (!enableCookies_)
return;
for (auto &iter : resp->getCookies())
{
auto &cookie = iter.second;
if (!cookie.domain().empty() && cookie.domain() != domain_)
{
continue;
}
if (cookie.isSecure())
{
if (useSSL_)
{
validCookies_.emplace_back(cookie);
}
}
else
{
validCookies_.emplace_back(cookie);
}
}
}
void HttpClientImpl::setCertPath(const std::string &cert,
const std::string &key)
{
clientCertPath_ = cert;
clientKeyPath_ = key;
}
void HttpClientImpl::addSSLConfigs(
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
{
for (const auto &cmd : sslConfCmds)
{
sslConfCmds_.push_back(cmd);
}
}
+176
View File
@@ -0,0 +1,176 @@
/**
*
* @file HttpClientImpl.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by the MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/Cookie.h>
#include <drogon/HttpClient.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/Resolver.h>
#include <trantor/net/TcpClient.h>
#include <cstddef>
#include <functional>
#include <future>
#include <list>
#include <mutex>
#include <queue>
#include <vector>
#include "impl_forwards.h"
namespace drogon
{
class HttpClientImpl final : public HttpClient,
public std::enable_shared_from_this<HttpClientImpl>
{
public:
HttpClientImpl(trantor::EventLoop *loop,
const trantor::InetAddress &addr,
bool useSSL = false,
bool useOldTLS = false,
bool validateCert = true);
HttpClientImpl(trantor::EventLoop *loop,
const std::string &hostString,
bool useOldTLS = false,
bool validateCert = true);
void sendRequest(const HttpRequestPtr &req,
const HttpReqCallback &callback,
double timeout = 0) override;
void sendRequest(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout = 0) override;
trantor::EventLoop *getLoop() override
{
return loop_;
}
void setPipeliningDepth(size_t depth) override
{
pipeliningDepth_ = depth;
}
~HttpClientImpl();
void enableCookies(bool flag = true) override
{
enableCookies_ = flag;
}
void addCookie(const std::string &key, const std::string &value) override
{
validCookies_.emplace_back(Cookie(key, value));
}
void addCookie(const Cookie &cookie) override
{
validCookies_.emplace_back(cookie);
}
size_t bytesSent() const override
{
return bytesSent_;
}
size_t bytesReceived() const override
{
return bytesReceived_;
}
void setUserAgent(const std::string &userAgent) override
{
userAgent_ = userAgent;
}
uint16_t port() const override
{
return serverAddr_.toPort();
}
std::string host() const override
{
if (domain_.empty())
return serverAddr_.toIp();
return domain_;
}
bool secure() const override
{
return useSSL_;
}
void setCertPath(const std::string &cert, const std::string &key) override;
void addSSLConfigs(const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) override;
void setSockOptCallback(std::function<void(int)> cb) override
{
sockOptCallback_ = std::move(cb);
}
std::size_t requestsBufferSize() override
{
if (loop_->isInLoopThread())
{
return requestsBuffer_.size();
}
else
{
std::promise<std::size_t> bufferSize;
loop_->queueInLoop(
[&] { bufferSize.set_value(requestsBuffer_.size()); });
return bufferSize.get_future().get();
}
}
private:
std::shared_ptr<trantor::TcpClient> tcpClientPtr_;
trantor::EventLoop *loop_;
trantor::InetAddress serverAddr_;
bool useSSL_;
bool validateCert_;
void sendReq(const trantor::TcpConnectionPtr &connPtr,
const HttpRequestPtr &req);
void sendRequestInLoop(const HttpRequestPtr &req,
HttpReqCallback &&callback);
void sendRequestInLoop(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout);
void handleCookies(const HttpResponseImplPtr &resp);
void handleResponse(const HttpResponseImplPtr &resp,
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
const trantor::TcpConnectionPtr &connPtr);
void createTcpClient();
std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> pipeliningCallbacks_;
std::list<std::pair<HttpRequestPtr, HttpReqCallback>> requestsBuffer_;
void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
void onError(ReqResult result);
std::string domain_;
bool isDomainName_{true}; // true if domain_ is name
size_t pipeliningDepth_{0};
bool enableCookies_{false};
std::vector<Cookie> validCookies_;
size_t bytesSent_{0};
size_t bytesReceived_{0};
bool dns_{false};
std::shared_ptr<trantor::Resolver> resolverPtr_;
bool useOldTLS_{false};
std::string userAgent_{"DrogonClient"};
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
std::string clientCertPath_;
std::string clientKeyPath_;
std::function<void(int)> sockOptCallback_;
};
using HttpClientImplPtr = std::shared_ptr<HttpClientImpl>;
} // namespace drogon
+80
View File
@@ -0,0 +1,80 @@
/**
*
* @file HttpConnectionLimit.cc
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpConnectionLimit.h"
#include <trantor/utils/Logger.h>
using namespace drogon;
void HttpConnectionLimit::setMaxConnectionNum(size_t num)
{
maxConnectionNum_ = num;
}
void HttpConnectionLimit::setMaxConnectionNumPerIP(size_t num)
{
maxConnectionNumPerIP_ = num;
}
bool HttpConnectionLimit::tryAddConnection(
const trantor::TcpConnectionPtr &conn)
{
assert(conn->connected());
if (connectionNum_.fetch_add(1, std::memory_order_relaxed) >
maxConnectionNum_)
{
return false;
}
if (maxConnectionNumPerIP_ > 0)
{
std::string ip = conn->peerAddr().toIp();
size_t numOnThisIp;
{
std::lock_guard<std::mutex> lock(mutex_);
numOnThisIp = (++ipConnectionsMap_[ip]);
}
if (numOnThisIp > maxConnectionNumPerIP_)
{
return false;
}
}
return true;
}
void HttpConnectionLimit::releaseConnection(
const trantor::TcpConnectionPtr &conn)
{
assert(!conn->connected());
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::string ip = conn->peerAddr().toIp();
std::lock_guard<std::mutex> lock(mutex_);
auto iter = ipConnectionsMap_.find(ip);
if (iter != ipConnectionsMap_.end())
{
if (--iter->second <= 0)
{
ipConnectionsMap_.erase(iter);
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
/**
*
* @file HttpConnectionLimit.h
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/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 <unordered_map>
#include <atomic>
#include <cstddef>
#include <mutex>
#include <trantor/net/TcpConnection.h>
namespace drogon
{
class HttpConnectionLimit
{
public:
static HttpConnectionLimit &instance()
{
static HttpConnectionLimit inst;
return inst;
}
size_t getConnectionNum() const
{
return connectionNum_.load(std::memory_order_relaxed);
}
// don't set after start
void setMaxConnectionNum(size_t num);
void setMaxConnectionNumPerIP(size_t num);
bool tryAddConnection(const trantor::TcpConnectionPtr &conn);
void releaseConnection(const trantor::TcpConnectionPtr &conn);
private:
std::mutex mutex_;
size_t maxConnectionNum_{100000};
std::atomic<size_t> connectionNum_{0};
size_t maxConnectionNumPerIP_{0};
std::unordered_map<std::string, size_t> ipConnectionsMap_;
};
} // namespace drogon
+93
View File
@@ -0,0 +1,93 @@
/**
*
* @file HttpControllerBinder.cc
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpControllerBinder.h"
#include "HttpResponseImpl.h"
#include <drogon/HttpSimpleController.h>
#include <drogon/WebSocketController.h>
namespace drogon
{
void HttpSimpleControllerBinder::handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
// Binders without controller should be removed after run()
assert(controller_);
try
{
auto cb = callback; // copy
controller_->asyncHandleHttpRequest(req, std::move(cb));
}
catch (const std::exception &e)
{
app().getExceptionHandler()(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
void HttpControllerBinder::handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
auto &paramsVector = req->getRoutingParameters();
std::deque<std::string> params(paramsVector.begin(), paramsVector.end());
binderPtr_->handleHttpRequest(params, req, std::move(callback));
}
void WebsocketControllerBinder::handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
std::string wsKey = req->getHeaderBy("sec-websocket-key");
wsKey.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
unsigned char accKey[20];
auto sha1 = trantor::utils::sha1(wsKey.c_str(), wsKey.length());
memcpy(accKey, &sha1, sizeof(sha1));
auto base64Key = utils::base64Encode(accKey, sizeof(accKey));
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k101SwitchingProtocols);
resp->addHeader("Upgrade", "websocket");
resp->addHeader("Connection", "Upgrade");
resp->addHeader("Sec-WebSocket-Accept", base64Key);
callback(resp);
}
void WebsocketControllerBinder::handleNewConnection(
const HttpRequestImplPtr &req,
const WebSocketConnectionImplPtr &wsConnPtr) const
{
auto ctrlPtr = controller_;
assert(ctrlPtr);
wsConnPtr->setMessageCallback(
[ctrlPtr](std::string &&message,
const WebSocketConnectionImplPtr &connPtr,
const WebSocketMessageType &type) {
ctrlPtr->handleNewMessage(connPtr, std::move(message), type);
});
wsConnPtr->setCloseCallback(
[ctrlPtr](const WebSocketConnectionImplPtr &connPtr) {
ctrlPtr->handleConnectionClosed(connPtr);
});
ctrlPtr->handleNewConnection(req, wsConnPtr);
}
} // namespace drogon
+65
View File
@@ -0,0 +1,65 @@
/**
*
* @file HttpControllerBinder.h
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "ControllerBinderBase.h"
#include "HttpRequestImpl.h"
#include "WebSocketConnectionImpl.h"
#include <drogon/HttpBinder.h>
namespace drogon
{
class HttpSimpleControllerBinder : public ControllerBinderBase
{
public:
void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const override;
std::shared_ptr<HttpSimpleControllerBase> controller_;
};
class HttpControllerBinder : public ControllerBinderBase
{
public:
void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const override;
bool isStreamHandler() const override
{
assert(binderPtr_);
return binderPtr_->isStreamHandler();
}
internal::HttpBinderBasePtr binderPtr_;
std::vector<size_t> parameterPlaces_;
std::vector<std::pair<std::string, size_t>> queryParametersPlaces_;
};
struct WebsocketControllerBinder : public ControllerBinderBase
{
std::shared_ptr<WebSocketControllerBase> controller_;
void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const override;
void handleNewConnection(const HttpRequestImplPtr &req,
const WebSocketConnectionImplPtr &wsConnPtr) const;
};
} // namespace drogon
+764
View File
@@ -0,0 +1,764 @@
/**
*
* @file HttpControllersRouter.cc
* @author 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 "HttpControllersRouter.h"
#include "HttpControllerBinder.h"
#include "HttpRequestImpl.h"
#include "HttpAppFrameworkImpl.h"
#include "MiddlewaresFunction.h"
#include <drogon/HttpSimpleController.h>
#include <drogon/WebSocketController.h>
#include <algorithm>
using namespace drogon;
void HttpControllersRouter::init(
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
{
auto initMiddlewaresAndCorsMethods = [](const auto &item) {
auto corsMethods = std::make_shared<std::string>("OPTIONS,");
for (size_t i = 0; i < Invalid; ++i)
{
auto &binder = item.binders_[i];
if (binder)
{
binder->middlewares_ = middlewares_function::createMiddlewares(
binder->middlewareNames_);
binder->corsMethods_ = corsMethods;
if (binder->isCORS_)
{
if (i == Get)
{
corsMethods->append("GET,HEAD,");
}
else if (i != Options)
{
corsMethods->append(to_string_view((HttpMethod)i));
corsMethods->append(",");
}
}
}
}
corsMethods->pop_back(); // remove last comma
};
for (auto &iter : simpleCtrlMap_)
{
initMiddlewaresAndCorsMethods(iter.second);
}
for (auto &iter : wsCtrlMap_)
{
initMiddlewaresAndCorsMethods(iter.second);
}
for (auto &router : ctrlVector_)
{
router.regex_ = std::regex(router.pathParameterPattern_,
std::regex_constants::icase);
initMiddlewaresAndCorsMethods(router);
}
for (auto &p : ctrlMap_)
{
auto &router = p.second;
router.regex_ = std::regex(router.pathParameterPattern_,
std::regex_constants::icase);
initMiddlewaresAndCorsMethods(router);
}
}
void HttpControllersRouter::reset()
{
simpleCtrlMap_.clear();
ctrlMap_.clear();
ctrlVector_.clear();
wsCtrlMap_.clear();
}
std::vector<HttpHandlerInfo> HttpControllersRouter::getHandlersInfo() const
{
std::vector<HttpHandlerInfo> ret;
auto gatherInfo = [&ret](const std::string &path, const auto &item) {
for (size_t i = 0; i < Invalid; ++i)
{
if (item.binders_[i])
{
std::string description;
if constexpr (std::is_same_v<std::decay_t<decltype(item)>,
SimpleControllerRouterItem>)
{
description = std::string("HttpSimpleController: ") +
item.binders_[i]->handlerName_;
}
else if constexpr (std::is_same_v<
std::decay_t<decltype(item)>,
WebSocketControllerRouterItem> ||
std::is_same_v<
std::decay_t<decltype(item)>,
RegExWebSocketControllerRouterItem>)
{
description = std::string("WebsocketController: ") +
item.binders_[i]->handlerName_;
}
else
{
description =
item.binders_[i]->handlerName_.empty()
? std::string("Handler: ") +
item.binders_[i]->binderPtr_->handlerName()
: std::string("HttpController: ") +
item.binders_[i]->handlerName_;
}
ret.emplace_back(path, (HttpMethod)i, std::move(description));
}
}
};
for (auto &[path, item] : simpleCtrlMap_)
{
gatherInfo(path, item);
}
for (auto &item : ctrlVector_)
{
gatherInfo(item.pathPattern_, item);
}
for (auto &[key, item] : ctrlMap_)
{
gatherInfo(item.pathPattern_, item);
}
for (auto &[path, item] : wsCtrlMap_)
{
gatherInfo(path, item);
}
for (auto &item : wsCtrlVector_)
{
gatherInfo(item.pathPattern_, item);
}
return ret;
}
template <typename Binder, typename RouterItem>
static void addCtrlBinderToRouterItem(const std::shared_ptr<Binder> &binderPtr,
RouterItem &router,
const std::vector<HttpMethod> &methods)
{
if (!methods.empty())
{
for (const auto &method : methods)
{
router.binders_[method] = binderPtr;
if (method == Options)
{
binderPtr->isCORS_ = true;
}
}
}
else
{
// All HTTP methods are valid
binderPtr->isCORS_ = true;
for (int i = 0; i < Invalid; ++i)
{
router.binders_[i] = binderPtr;
}
}
}
struct SimpleControllerProcessResult
{
std::string lowerPath;
std::vector<HttpMethod> validMethods;
std::vector<std::string> middlewares;
};
static SimpleControllerProcessResult processSimpleControllerParams(
const std::string &pathName,
const std::vector<internal::HttpConstraint> &constraints)
{
std::string path(pathName);
std::transform(pathName.begin(),
pathName.end(),
path.begin(),
[](unsigned char c) { return tolower(c); });
std::vector<HttpMethod> validMethods;
std::vector<std::string> middlewareNames;
for (const auto &constraint : constraints)
{
if (constraint.type() == internal::ConstraintType::HttpMiddleware)
{
middlewareNames.push_back(constraint.getMiddlewareName());
}
else if (constraint.type() == internal::ConstraintType::HttpMethod)
{
validMethods.push_back(constraint.getHttpMethod());
}
else
{
LOG_ERROR << "Invalid controller constraint type";
// Used to call exit() here, but that's not very nice.
}
}
return {
std::move(path),
std::move(validMethods),
std::move(middlewareNames),
};
}
void HttpControllersRouter::registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints)
{
assert(!pathName.empty());
assert(!ctrlName.empty());
// Note: some compiler version failed to handle structural bindings with
// lambda capture
auto result = processSimpleControllerParams(pathName, constraints);
std::string path = std::move(result.lowerPath);
auto &item = simpleCtrlMap_[path];
auto binder = std::make_shared<HttpSimpleControllerBinder>();
binder->handlerName_ = ctrlName;
binder->middlewareNames_ = result.middlewares;
drogon::app().getLoop()->queueInLoop([this, binder, ctrlName, path]() {
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
auto controller =
std::dynamic_pointer_cast<HttpSimpleControllerBase>(object_);
if (!controller)
{
LOG_ERROR << "Controller class not found: " << ctrlName;
simpleCtrlMap_.erase(path);
return;
}
binder->controller_ = controller;
// Recreate this with the correct number of threads.
binder->responseCache_ = IOThreadStorage<HttpResponsePtr>();
});
addCtrlBinderToRouterItem(binder, item, result.validMethods);
}
void HttpControllersRouter::registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints)
{
assert(!pathName.empty());
assert(!ctrlName.empty());
auto result = processSimpleControllerParams(pathName, constraints);
std::string path = std::move(result.lowerPath);
auto &item = wsCtrlMap_[path];
auto binder = std::make_shared<WebsocketControllerBinder>();
binder->handlerName_ = ctrlName;
binder->middlewareNames_ = result.middlewares;
drogon::app().getLoop()->queueInLoop([this, binder, ctrlName, path]() {
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
auto controller =
std::dynamic_pointer_cast<WebSocketControllerBase>(object_);
if (!controller)
{
LOG_ERROR << "Websocket controller class not found: " << ctrlName;
wsCtrlMap_.erase(path);
return;
}
binder->controller_ = controller;
});
addCtrlBinderToRouterItem(binder, item, result.validMethods);
}
void HttpControllersRouter::registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints)
{
assert(!regExp.empty());
assert(!ctrlName.empty());
auto result = processSimpleControllerParams(regExp, constraints);
auto binder = std::make_shared<WebsocketControllerBinder>();
binder->handlerName_ = ctrlName;
binder->middlewareNames_ = result.middlewares;
drogon::app().getLoop()->queueInLoop([binder, ctrlName]() {
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
auto controller =
std::dynamic_pointer_cast<WebSocketControllerBase>(object_);
binder->controller_ = controller;
});
struct RegExWebSocketControllerRouterItem router;
router.pathPattern_ = regExp;
router.regex_ = regExp;
addCtrlBinderToRouterItem(binder, router, result.validMethods);
wsCtrlVector_.push_back(std::move(router));
}
void HttpControllersRouter::addHttpRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName)
{
auto binderInfo = std::make_shared<HttpControllerBinder>();
binderInfo->middlewareNames_ = middlewareNames;
binderInfo->handlerName_ = handlerName;
binderInfo->binderPtr_ = binder;
drogon::app().getLoop()->queueInLoop([binderInfo]() {
// Recreate this with the correct number of threads.
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
});
addRegexCtrlBinder(binderInfo, regExp, regExp, validMethods);
}
void HttpControllersRouter::addHttpPath(
const std::string &path,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName)
{
// Path is like /api/v1/service/method/{1}/{2}/xxx...
std::vector<size_t> places;
std::string tmpPath = path;
std::string paras;
static const std::regex regex("\\{([^/]*)\\}");
std::smatch results;
auto pos = tmpPath.find('?');
if (pos != std::string::npos)
{
paras = tmpPath.substr(pos + 1);
tmpPath = tmpPath.substr(0, pos);
}
std::string originPath = tmpPath;
size_t placeIndex = 1;
// Process path parameter placeholders
while (std::regex_search(tmpPath, results, regex))
{
if (results.size() > 1)
{
auto result = results[1].str();
if (!result.empty() &&
std::all_of(result.begin(), result.end(), [](const char c) {
return std::isdigit(c);
}))
{
auto place = (size_t)std::stoi(result);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to " << binder->paramCount()
<< ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) { return i != place; }))
{
LOG_ERROR << "Parameter placeholders are duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
places.push_back(place);
}
else
{
static const std::regex regNumberAndName("([0-9]+):.*");
std::smatch regexResult;
if (std::regex_match(result, regexResult, regNumberAndName))
{
assert(regexResult.size() == 2 && regexResult[1].matched);
auto num = regexResult[1].str();
auto place = (size_t)std::stoi(num);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to "
<< binder->paramCount() << ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) { return i != place; }))
{
LOG_ERROR
<< "Parameter placeholders are duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
places.push_back(place);
}
else
{
if (!std::all_of(places.begin(),
places.end(),
[placeIndex](size_t i) {
return i != placeIndex;
}))
{
LOG_ERROR
<< "Parameter placeholders are duplicated: index="
<< placeIndex;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
places.push_back(placeIndex);
}
}
++placeIndex;
}
tmpPath = results.suffix();
}
// Process query parameter placeholders
std::vector<std::pair<std::string, size_t>> parametersPlaces;
if (!paras.empty())
{
static const std::regex pregex("([^&]*)=\\{([^&]*)\\}&*");
while (std::regex_search(paras, results, pregex))
{
if (results.size() > 2)
{
auto result = results[2].str();
if (!result.empty() &&
std::all_of(result.begin(), result.end(), [](const char c) {
return std::isdigit(c);
}))
{
auto place = (size_t)std::stoi(result);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to "
<< binder->paramCount() << ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) {
return i != place;
}) ||
!all_of(parametersPlaces.begin(),
parametersPlaces.end(),
[place](const std::pair<std::string, size_t>
&item) {
return item.second != place;
}))
{
LOG_ERROR << "Parameter placeholders are "
"duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
parametersPlaces.emplace_back(results[1].str(), place);
}
else
{
std::regex regNumberAndName("([0-9]+):.*");
std::smatch regexResult;
if (std::regex_match(result, regexResult, regNumberAndName))
{
assert(regexResult.size() == 2 &&
regexResult[1].matched);
auto num = regexResult[1].str();
auto place = (size_t)std::stoi(num);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to "
<< binder->paramCount() << ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) {
return i != place;
}) ||
!all_of(parametersPlaces.begin(),
parametersPlaces.end(),
[place](const std::pair<std::string, size_t>
&item) {
return item.second != place;
}))
{
LOG_ERROR << "Parameter placeholders are "
"duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
parametersPlaces.emplace_back(results[1].str(), place);
}
else
{
if (!std::all_of(places.begin(),
places.end(),
[placeIndex](size_t i) {
return i != placeIndex;
}) ||
!all_of(parametersPlaces.begin(),
parametersPlaces.end(),
[placeIndex](
const std::pair<std::string, size_t>
&item) {
return item.second != placeIndex;
}))
{
LOG_ERROR << "Parameter placeholders are "
"duplicated: index="
<< placeIndex;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
parametersPlaces.emplace_back(results[1].str(),
placeIndex);
}
}
++placeIndex;
}
paras = results.suffix();
}
}
// Create new ControllerBinder
auto binderInfo = std::make_shared<HttpControllerBinder>();
binderInfo->middlewareNames_ = middlewareNames;
binderInfo->handlerName_ = handlerName;
binderInfo->binderPtr_ = binder;
binderInfo->parameterPlaces_ = std::move(places);
binderInfo->queryParametersPlaces_ = std::move(parametersPlaces);
drogon::app().getLoop()->queueInLoop([binderInfo]() {
// Recreate this with the correct number of threads.
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
});
// Create or update RouterItem
auto pathParameterPattern =
std::regex_replace(originPath, regex, "([^/]*)");
if (originPath != pathParameterPattern) // require regex
{
addRegexCtrlBinder(binderInfo,
path,
pathParameterPattern,
validMethods);
return;
}
std::string loweredPath;
std::transform(originPath.begin(),
originPath.end(),
std::back_inserter(loweredPath),
[](unsigned char c) { return tolower(c); });
HttpControllerRouterItem *routerItemPtr;
// If exists another controllers on the same route, update them
auto it = ctrlMap_.find(loweredPath);
if (it != ctrlMap_.end())
{
routerItemPtr = &it->second;
}
// Create new router item if not exists
else
{
struct HttpControllerRouterItem router;
router.pathParameterPattern_ = pathParameterPattern;
router.pathPattern_ = path;
routerItemPtr =
&ctrlMap_.emplace(loweredPath, std::move(router)).first->second;
}
addCtrlBinderToRouterItem(binderInfo, *routerItemPtr, validMethods);
}
RouteResult HttpControllersRouter::route(const HttpRequestImplPtr &req)
{
// Find simple controller
std::string loweredPath(req->path().length(), 0);
std::transform(req->path().begin(),
req->path().end(),
loweredPath.begin(),
[](unsigned char c) { return tolower(c); });
{
auto it = simpleCtrlMap_.find(loweredPath);
if (it != simpleCtrlMap_.end())
{
auto &ctrlInfo = it->second;
req->setMatchedPathPattern(it->first);
auto &binder = ctrlInfo.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
return {RouteResult::Success, binder};
}
}
// Find http controller
HttpControllerRouterItem *routerItemPtr = nullptr;
std::smatch result;
auto it = ctrlMap_.find(loweredPath);
// Try to find a controller in the hash map. If can't linear search
// with regex.
if (it != ctrlMap_.end())
{
routerItemPtr = &it->second;
}
else
{
for (auto &item : ctrlVector_)
{
const auto &ctrlRegex = item.regex_;
if (item.binders_[req->method()] &&
std::regex_match(req->path(), result, ctrlRegex))
{
routerItemPtr = &item;
break;
}
}
}
// No handler found
if (!routerItemPtr)
{
return {RouteResult::NotFound, nullptr};
}
HttpControllerRouterItem &routerItem = *routerItemPtr;
assert(Invalid > req->method());
req->setMatchedPathPattern(routerItem.pathPattern_);
auto &binder = routerItem.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
std::vector<std::string> params;
for (size_t j = 1; j < result.size(); ++j)
{
if (!result[j].matched)
continue;
size_t place = j;
if (j <= binder->parameterPlaces_.size())
{
place = binder->parameterPlaces_[j - 1];
}
if (place > params.size())
params.resize(place);
params[place - 1] = result[j].str();
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
}
if (!binder->queryParametersPlaces_.empty())
{
auto &queryPara = req->getParameters();
for (const auto &paraPlace : binder->queryParametersPlaces_)
{
auto place = paraPlace.second;
if (place > params.size())
params.resize(place);
auto iter = queryPara.find(paraPlace.first);
if (iter != queryPara.end())
{
params[place - 1] = iter->second;
}
else
{
params[place - 1] = std::string{};
}
}
}
req->setRoutingParameters(std::move(params));
return {RouteResult::Success, binder};
}
RouteResult HttpControllersRouter::routeWs(const HttpRequestImplPtr &req)
{
std::string wsKey = req->getHeaderBy("sec-websocket-key");
if (!wsKey.empty())
{
std::string pathLower(req->path().length(), 0);
std::transform(req->path().begin(),
req->path().end(),
pathLower.begin(),
[](unsigned char c) { return tolower(c); });
auto iter = wsCtrlMap_.find(pathLower);
if (iter != wsCtrlMap_.end())
{
auto &ctrlInfo = iter->second;
req->setMatchedPathPattern(iter->first);
auto &binder = ctrlInfo.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
return {RouteResult::Success, binder};
}
else
{
for (auto &ctrlInfo : wsCtrlVector_)
{
auto const &wsCtrlRegex = ctrlInfo.regex_;
std::smatch result;
if (std::regex_match(req->path(), result, wsCtrlRegex))
{
req->setMatchedPathPattern(ctrlInfo.pathPattern_);
auto &binder = ctrlInfo.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
return {RouteResult::Success, binder};
}
}
}
}
return {RouteResult::NotFound, nullptr};
}
void HttpControllersRouter::addRegexCtrlBinder(
const std::shared_ptr<HttpControllerBinder> &binderPtr,
const std::string &pathPattern,
const std::string &pathParameterPattern,
const std::vector<HttpMethod> &methods)
{
HttpControllerRouterItem *routerItemPtr;
auto existRouter = std::find_if(ctrlVector_.begin(),
ctrlVector_.end(),
[&pathParameterPattern](const auto &item) {
return item.pathParameterPattern_ ==
pathParameterPattern;
});
if (existRouter == ctrlVector_.end())
{
struct HttpControllerRouterItem router;
router.pathParameterPattern_ = pathParameterPattern;
router.pathPattern_ = pathPattern;
ctrlVector_.push_back(std::move(router));
routerItemPtr = &ctrlVector_.back();
}
else
{
routerItemPtr = &(*existRouter);
}
addCtrlBinderToRouterItem(binderPtr, *routerItemPtr, methods);
}
+109
View File
@@ -0,0 +1,109 @@
/**
*
* HttpControllersRouter.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 "ControllerBinderBase.h"
#include <trantor/utils/NonCopyable.h>
#include <memory>
#include <regex>
#include <string>
#include <vector>
#include <unordered_map>
namespace drogon
{
class HttpControllerBinder;
class HttpSimpleControllerBinder;
struct WebsocketControllerBinder;
class HttpControllersRouter : public trantor::NonCopyable
{
public:
static HttpControllersRouter &instance()
{
static HttpControllersRouter inst;
return inst;
}
void init(const std::vector<trantor::EventLoop *> &ioLoops);
// clean all resources
void reset();
void registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints);
void registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints);
void registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints);
void addHttpPath(const std::string &path,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName = "");
void addHttpRegex(const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName = "");
RouteResult route(const HttpRequestImplPtr &req);
RouteResult routeWs(const HttpRequestImplPtr &req);
std::vector<HttpHandlerInfo> getHandlersInfo() const;
private:
void addRegexCtrlBinder(
const std::shared_ptr<HttpControllerBinder> &binderPtr,
const std::string &pathPattern,
const std::string &pathParameterPattern,
const std::vector<HttpMethod> &methods);
struct SimpleControllerRouterItem
{
std::shared_ptr<HttpSimpleControllerBinder> binders_[Invalid]{nullptr};
};
struct HttpControllerRouterItem
{
std::string pathParameterPattern_;
std::string pathPattern_;
std::regex regex_;
std::shared_ptr<HttpControllerBinder> binders_[Invalid]{nullptr};
};
struct WebSocketControllerRouterItem
{
std::shared_ptr<WebsocketControllerBinder> binders_[Invalid]{nullptr};
};
struct RegExWebSocketControllerRouterItem
{
std::string pathPattern_;
std::regex regex_;
std::shared_ptr<WebsocketControllerBinder> binders_[Invalid]{nullptr};
};
std::unordered_map<std::string, SimpleControllerRouterItem> simpleCtrlMap_;
std::unordered_map<std::string, HttpControllerRouterItem> ctrlMap_;
std::vector<HttpControllerRouterItem> ctrlVector_; // for regexp path
std::unordered_map<std::string, WebSocketControllerRouterItem> wsCtrlMap_;
std::vector<RegExWebSocketControllerRouterItem> wsCtrlVector_;
};
} // namespace drogon
+218
View File
@@ -0,0 +1,218 @@
/**
*
* @file HttpFileImpl.cc
* @author 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 <drogon/utils/Utilities.h>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <filesystem>
using namespace drogon;
int HttpFileImpl::save() const noexcept
{
return save(HttpAppFrameworkImpl::instance().getUploadPath());
}
int HttpFileImpl::save(const std::string &path) const noexcept
{
assert(!path.empty());
if (fileName_.empty())
return -1;
std::filesystem::path fsUploadDir(utils::toNativePath(path));
if (fsUploadDir.is_absolute())
{ // do nothing
}
else if ((!fsUploadDir.has_parent_path() ||
(fsUploadDir.begin()->string() != "." &&
fsUploadDir.begin()->string() != "..")))
{
fsUploadDir = utils::toNativePath(
HttpAppFrameworkImpl::instance().getUploadPath()) /
fsUploadDir;
}
else
{
fsUploadDir = std::filesystem::current_path() / fsUploadDir;
}
fsUploadDir = std::filesystem::weakly_canonical(fsUploadDir);
if (!std::filesystem::exists(fsUploadDir))
{
LOG_TRACE << "create path:" << fsUploadDir;
std::error_code err;
std::filesystem::create_directories(fsUploadDir, err);
if (err)
{
LOG_SYSERR;
return -1;
}
}
std::filesystem::path fsSaveToPath(std::filesystem::weakly_canonical(
fsUploadDir / utils::toNativePath(fileName_)));
LOG_TRACE << "save to path:" << fsSaveToPath;
if (!std::equal(fsUploadDir.begin(),
fsUploadDir.end(),
fsSaveToPath.begin()))
{
LOG_ERROR
<< "Attempt writing outside of upload directory detected. Path: "
<< fileName_;
return -1;
}
return saveTo(fsSaveToPath);
}
int HttpFileImpl::saveAs(const std::string &fileName) const noexcept
{
assert(!fileName.empty());
std::filesystem::path fsFileName(utils::toNativePath(fileName));
if (!fsFileName.is_absolute() && (!fsFileName.has_parent_path() ||
(fsFileName.begin()->string() != "." &&
fsFileName.begin()->string() != "..")))
{
std::filesystem::path fsUploadPath(utils::toNativePath(
HttpAppFrameworkImpl::instance().getUploadPath()));
fsFileName = fsUploadPath / fsFileName;
}
if (fsFileName.has_parent_path() &&
!std::filesystem::exists(fsFileName.parent_path()))
{
LOG_TRACE << "create path:" << fsFileName.parent_path();
std::error_code err;
std::filesystem::create_directories(fsFileName.parent_path(), err);
if (err)
{
LOG_SYSERR;
return -1;
}
}
return saveTo(fsFileName);
}
int HttpFileImpl::saveTo(
const std::filesystem::path &pathAndFileName) const noexcept
{
LOG_TRACE << "save uploaded file:" << pathAndFileName;
auto wPath = utils::toNativePath(pathAndFileName.native());
std::ofstream file(wPath, 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 noexcept
{
return utils::getMd5(fileContent_.data(), fileContent_.size());
}
std::string HttpFileImpl::getSha256() const noexcept
{
return utils::getSha256(fileContent_.data(), fileContent_.size());
}
std::string HttpFileImpl::getSha3() const noexcept
{
return utils::getSha3(fileContent_.data(), fileContent_.size());
}
const std::string &HttpFile::getFileName() const noexcept
{
return implPtr_->getFileName();
}
void HttpFile::setFileName(const std::string &fileName) noexcept
{
implPtr_->setFileName(fileName);
}
std::string_view HttpFile::getFileExtension() const noexcept
{
return implPtr_->getFileExtension();
}
FileType HttpFile::getFileType() const noexcept
{
return implPtr_->getFileType();
}
void HttpFile::setFile(const char *data, size_t length) noexcept
{
implPtr_->setFile(data, length);
}
int HttpFile::save() const noexcept
{
return implPtr_->save();
}
int HttpFile::save(const std::string &path) const noexcept
{
return implPtr_->save(path);
}
int HttpFile::saveAs(const std::string &fileName) const noexcept
{
return implPtr_->saveAs(fileName);
}
size_t HttpFile::fileLength() const noexcept
{
return implPtr_->fileLength();
}
drogon::ContentType HttpFile::getContentType() const noexcept
{
return implPtr_->getContentType();
}
const char *HttpFile::fileData() const noexcept
{
return implPtr_->fileData();
}
std::string HttpFile::getMd5() const noexcept
{
return implPtr_->getMd5();
}
const std::string &HttpFile::getContentTransferEncoding() const noexcept
{
return implPtr_->getContentTransferEncoding();
}
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept
: implPtr_(std::move(implPtr))
{
}
const std::string &HttpFile::getItemName() const noexcept
{
return implPtr_->getItemName();
}
+175
View File
@@ -0,0 +1,175 @@
/**
*
* @file HttpFileImpl.h
* @author 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 "HttpUtils.h"
#include <drogon/HttpRequest.h>
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <filesystem>
#include <string_view>
namespace drogon
{
class HttpFileImpl
{
public:
/// Return the file name;
const std::string &getFileName() const noexcept
{
return fileName_;
}
/// Set the file name, usually called by the MultiPartParser parser.
void setFileName(const std::string &fileName) noexcept
{
fileName_ = fileName;
}
void setFileName(std::string &&fileName) noexcept
{
fileName_ = std::move(fileName);
}
/// Return the file extension;
std::string_view getFileExtension() const noexcept
{
return drogon::getFileExtension(fileName_);
}
/// Set the contents of the file, usually called by the MultiPartParser
/// parser.
void setFile(const char *data, size_t length) noexcept
{
fileContent_ = std::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 noexcept;
/// 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 noexcept;
/// 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 noexcept;
/// Return the file length.
size_t fileLength() const noexcept
{
return fileContent_.length();
}
const char *fileData() const noexcept
{
return fileContent_.data();
}
const std::string_view &fileContent() const noexcept
{
return fileContent_;
}
/// Return the name of the item in multiple parts.
const std::string &getItemName() const noexcept
{
return itemName_;
}
void setItemName(const std::string &itemName) noexcept
{
itemName_ = itemName;
}
void setItemName(std::string &&itemName) noexcept
{
itemName_ = std::move(itemName);
}
/// Return the type of file.
FileType getFileType() const noexcept
{
auto ft = drogon::getFileType(contentType_);
if ((ft != FT_UNKNOWN) && (ft != FT_CUSTOM))
return ft;
return parseFileType(getFileExtension());
}
/// Return md5 hash of the file
std::string getMd5() const noexcept;
// Return sha1 hash of the file
std::string getSha256() const noexcept;
// Return sha512 hash of the file
std::string getSha3() const noexcept;
// int saveTo(const std::string &pathAndFileName) const;
int saveTo(const std::filesystem::path &pathAndFileName) const noexcept;
void setRequest(const HttpRequestPtr &req) noexcept
{
requestPtr_ = req;
}
drogon::ContentType getContentType() const noexcept
{
return contentType_;
}
void setContentType(drogon::ContentType contentType) noexcept
{
contentType_ = contentType;
}
void setContentTransferEncoding(
const std::string &contentTransferEncoding) noexcept
{
transferEncoding_ = contentTransferEncoding;
}
void setContentTransferEncoding(
std::string &&contentTransferEncoding) noexcept
{
transferEncoding_ = std::move(contentTransferEncoding);
}
const std::string &getContentTransferEncoding() const noexcept
{
return transferEncoding_;
}
private:
std::string fileName_;
std::string itemName_;
std::string transferEncoding_;
std::string_view fileContent_;
HttpRequestPtr requestPtr_;
drogon::ContentType contentType_{drogon::CT_NONE};
};
} // namespace drogon
@@ -0,0 +1,31 @@
/**
*
* 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 "HttpFileUploadRequest.h"
#include <drogon/UploadFile.h>
#include <drogon/utils/Utilities.h>
using namespace drogon;
HttpFileUploadRequest::HttpFileUploadRequest(
const std::vector<UploadFile> &files)
: HttpRequestImpl(nullptr),
boundary_(utils::genRandomString(32)),
files_(files)
{
setMethod(drogon::Post);
setVersion(drogon::Version::kHttp11);
setContentType("multipart/form-data; boundary=" + boundary_);
contentType_ = CT_MULTIPART_FORM_DATA;
}
+42
View File
@@ -0,0 +1,42 @@
/**
*
* 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 "HttpRequestImpl.h"
#include <string>
#include <vector>
namespace drogon
{
class HttpFileUploadRequest : public HttpRequestImpl
{
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
+145
View File
@@ -0,0 +1,145 @@
/**
*
* 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 <string_view>
#include <memory>
#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 std::string_view getString() const = 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;
}
const char *data() const override
{
return body_.data();
}
char *data() override
{
return const_cast<char *>(body_.data());
}
size_t length() const override
{
return body_.length();
}
std::string_view getString() const override
{
return std::string_view{body_.data(), body_.length()};
}
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;
}
const char *data() const override
{
return body_.data();
}
char *data() override
{
return const_cast<char *>(body_.data());
}
size_t length() const override
{
return body_.length();
}
std::string_view getString() const override
{
return body_;
}
private:
std::string_view body_;
};
} // namespace drogon
File diff suppressed because it is too large Load Diff
+750
View File
@@ -0,0 +1,750 @@
/**
*
* @file HttpRequestImpl.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 "HttpUtils.h"
#include "CacheFile.h"
#include "impl_forwards.h"
#include <drogon/utils/Utilities.h>
#include <drogon/HttpRequest.h>
#include <drogon/RequestStream.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/InetAddress.h>
#include <trantor/net/Certificate.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/TcpConnection.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <future>
#include <unordered_map>
#include <assert.h>
#include <stdio.h>
namespace drogon
{
enum class StreamDecompressStatus
{
TooLarge,
DecompressError,
NotSupported,
Ok
};
enum class ReqStreamStatus
{
None = 0,
Open = 1,
Finish = 2,
Error = 3
};
class HttpRequestImpl : public HttpRequest
{
public:
friend class HttpRequestParser;
explicit HttpRequestImpl(trantor::EventLoop *loop)
: creationDate_(trantor::Date::now()), loop_(loop)
{
}
void reset()
{
method_ = Invalid;
previousMethod_ = Invalid;
version_ = Version::kUnknown;
flagForParsingJson_ = false;
headers_.clear();
cookies_.clear();
contentLengthHeaderValue_.reset();
realContentLength_ = 0;
flagForParsingParameters_ = false;
path_.clear();
originalPath_.clear();
pathEncode_ = true;
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();
peerCertificate_.reset();
routingParams_.clear();
// stream
streamStatus_ = ReqStreamStatus::None;
streamReaderPtr_.reset();
streamFinishCb_ = nullptr;
streamExceptionPtr_ = nullptr;
startProcessing_ = false;
connPtr_.reset();
}
trantor::EventLoop *getLoop()
{
return loop_;
}
void setVersion(Version v)
{
version_ = v;
if (version_ == Version::kHttp10)
{
keepAlive_ = false;
}
}
Version version() const override
{
return version_;
}
const char *versionString() const override;
bool setMethod(const char *start, const char *end);
void setSecure(bool secure)
{
isOnSecureConnection_ = secure;
}
void setMethod(const HttpMethod method) override
{
previousMethod_ = method_;
method_ = method;
return;
}
HttpMethod method() const override
{
return method_;
}
bool isHead() const override
{
return (method_ == HttpMethod::Head) ||
((method_ == HttpMethod::Get) &&
(previousMethod_ == HttpMethod::Head));
}
const char *methodString() const override;
void setPath(const char *start, const char *end)
{
if (utils::needUrlDecoding(start, end))
{
originalPath_.append(start, end);
path_ = utils::urlDecode(start, end);
}
else
{
path_.append(start, end);
}
}
const std::vector<std::string> &getRoutingParameters() const override
{
return routingParams_;
}
void setRoutingParameters(std::vector<std::string> &&params) override
{
routingParams_ = std::move(params);
}
void setPath(const std::string &path) override
{
path_ = path;
}
void setPath(std::string &&path) override
{
path_ = std::move(path);
}
void setPathEncode(bool pathEncode) override
{
pathEncode_ = pathEncode;
}
const SafeStringMap<std::string> &parameters() const override
{
parseParametersOnce();
return parameters_;
}
const std::string &getParameter(const std::string &key) const override
{
static const std::string defaultVal;
parseParametersOnce();
auto iter = parameters_.find(key);
if (iter != parameters_.end())
return iter->second;
return defaultVal;
}
const std::string &path() const override
{
return path_;
}
const std::string &getOriginalPath() const override
{
return originalPath_.empty() ? path_ : originalPath_;
}
void setQuery(const char *start, const char *end)
{
query_.assign(start, end);
}
void setQuery(const std::string &query)
{
query_ = query;
}
std::string_view bodyView() const
{
if (isStreamMode())
{
return emptySv_;
}
if (cacheFilePtr_)
{
return cacheFilePtr_->getStringView();
}
return content_;
}
const char *bodyData() const override
{
if (isStreamMode())
{
return emptySv_.data();
}
if (cacheFilePtr_)
{
return cacheFilePtr_->getStringView().data();
}
return content_.data();
}
size_t bodyLength() const override
{
if (isStreamMode())
{
return emptySv_.length();
}
if (cacheFilePtr_)
{
return cacheFilePtr_->getStringView().length();
}
return content_.length();
}
void appendToBody(const char *data, size_t length);
void reserveBodySize(size_t length);
std::string_view queryView() const
{
return query_;
}
std::string_view contentView() const
{
if (isStreamMode())
{
return emptySv_;
}
if (cacheFilePtr_)
return cacheFilePtr_->getStringView();
return content_;
}
const std::string &query() const override
{
return query_;
}
const trantor::InetAddress &peerAddr() const override
{
return peer_;
}
const trantor::InetAddress &localAddr() const override
{
return local_;
}
const trantor::Date &creationDate() const override
{
return creationDate_;
}
const trantor::CertificatePtr &peerCertificate() const override
{
return peerCertificate_;
}
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 setPeerCertificate(const trantor::CertificatePtr &cert)
{
peerCertificate_ = cert;
}
void setConnectionPtr(const std::shared_ptr<trantor::TcpConnection> &ptr)
{
connPtr_ = ptr;
}
void addHeader(const char *start, const char *colon, const char *end);
void removeHeader(std::string key) override
{
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
return tolower(c);
});
removeHeaderBy(key);
}
void removeHeaderBy(const std::string &lowerKey)
{
headers_.erase(lowerKey);
}
const std::string &getHeader(std::string field) const override
{
std::transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
return getHeaderBy(field);
}
const std::string &getHeaderBy(const std::string &lowerField) const
{
static const std::string defaultVal;
auto it = headers_.find(lowerField);
if (it != headers_.end())
{
return it->second;
}
return defaultVal;
}
const std::string &getCookie(const std::string &field) const override
{
static const std::string defaultVal;
auto it = cookies_.find(field);
if (it != cookies_.end())
{
return it->second;
}
return defaultVal;
}
const SafeStringMap<std::string> &headers() const override
{
return headers_;
}
const SafeStringMap<std::string> &cookies() const override
{
return cookies_;
}
std::optional<size_t> getContentLengthHeaderValue() const
{
return contentLengthHeaderValue_;
}
size_t realContentLength() const override
{
return realContentLength_;
}
void setParameter(const std::string &key, const std::string &value) override
{
flagForParsingParameters_ = true;
parameters_[key] = value;
}
const std::string &getContent() const
{
return content_;
}
void swap(HttpRequestImpl &that) noexcept;
void setContent(const std::string &content)
{
content_ = content;
}
void setBody(const std::string &body) override
{
content_ = body;
}
void setBody(std::string &&body) override
{
content_ = std::move(body);
}
void addHeader(std::string field, const std::string &value) override
{
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = value;
}
void addHeader(std::string field, std::string &&value) override
{
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = std::move(value);
}
void addCookie(std::string key, std::string value) override
{
cookies_[std::move(key)] = std::move(value);
}
void setPassThrough(bool flag) override
{
passThrough_ = flag;
}
bool passThrough() const
{
return passThrough_;
}
void appendToBuffer(trantor::MsgBuffer *output) const;
const SessionPtr &session() const override
{
return sessionPtr_;
}
void setSession(const SessionPtr &session)
{
sessionPtr_ = session;
}
const AttributesPtr &attributes() const override
{
if (!attributesPtr_)
{
attributesPtr_ = std::make_shared<Attributes>();
}
return attributesPtr_;
}
const std::shared_ptr<Json::Value> &jsonObject() const override
{
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingJson_)
{
flagForParsingJson_ = true;
parseJson();
}
return jsonPtr_;
}
void setCustomContentTypeString(const std::string &type) override
{
contentType_ = CT_NONE;
flagForParsingContentType_ = true;
bool haveHeader = type.find("content-type: ") == 0;
bool haveCRLF = type.rfind("\r\n") == type.size() - 2;
size_t endOffset = 0;
if (haveHeader)
endOffset += 14;
if (haveCRLF)
endOffset += 2;
contentTypeString_ = std::string(type.begin() + (haveHeader ? 14 : 0),
type.end() - endOffset);
}
void setContentTypeCode(const ContentType type) override
{
contentType_ = type;
flagForParsingContentType_ = true;
auto &typeStr = contentTypeToMime(type);
setContentType(std::string(typeStr.data(), typeStr.length()));
}
void setContentTypeString(const char *typeString,
size_t typeStringLength) override;
// void setContentTypeCodeAndCharacterSet(ContentType type, const
// std::string &charSet = "utf-8") override
// {
// contentType_ = type;
// setContentType(webContentTypeAndCharsetToString(type, charSet));
// }
ContentType contentType() const override
{
parseContentTypeAndString();
return contentType_;
}
const char *matchedPathPatternData() const override
{
return matchedPathPattern_.data();
}
size_t matchedPathPatternLength() const override
{
return matchedPathPattern_.length();
}
void setMatchedPathPattern(const std::string &pathPattern)
{
matchedPathPattern_ = pathPattern;
}
const std::string &expect() const
{
static const std::string none{""};
if (expectPtr_)
return *expectPtr_;
return none;
}
bool keepAlive() const
{
return keepAlive_;
}
bool connected() const noexcept override
{
if (auto conn = connPtr_.lock())
{
return conn->connected();
}
return false;
}
const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept override
{
return connPtr_;
}
bool isOnSecureConnection() const noexcept override
{
return isOnSecureConnection_;
}
const std::string &getJsonError() const override
{
static const std::string none{""};
if (jsonParsingErrorPtr_)
return *jsonParsingErrorPtr_;
return none;
}
StreamDecompressStatus decompressBody();
// Stream mode api
ReqStreamStatus streamStatus() const
{
return streamStatus_;
}
bool isStreamMode() const
{
return streamStatus_ > ReqStreamStatus::None;
}
void streamStart();
void streamFinish();
void streamError(std::exception_ptr ex);
void setStreamReader(RequestStreamReaderPtr reader);
void waitForStreamFinish(std::function<void()> &&cb);
void quitStreamMode();
void startProcessing()
{
startProcessing_ = true;
}
bool isProcessingStarted() const
{
return startProcessing_;
}
~HttpRequestImpl() override;
protected:
friend class HttpRequest;
void setContentType(const std::string &contentType)
{
contentTypeString_ = contentType;
}
void setContentType(std::string &&contentType)
{
contentTypeString_ = std::move(contentType);
}
void parseContentTypeAndString() 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(
std::string_view(contentTypeString.data(), pos));
}
else
{
contentType_ =
parseContentType(std::string_view(contentTypeString));
}
if (contentType_ == CT_NONE)
contentType_ = CT_CUSTOM;
contentTypeString_ = contentTypeString;
}
}
}
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;
#ifdef USE_BROTLI
StreamDecompressStatus decompressBodyBrotli() noexcept;
#endif
StreamDecompressStatus decompressBodyGzip() noexcept;
static constexpr const std::string_view emptySv_{""};
mutable bool flagForParsingParameters_{false};
mutable bool flagForParsingJson_{false};
HttpMethod method_{Invalid};
HttpMethod previousMethod_{Invalid};
Version version_{Version::kUnknown};
std::string path_;
/// Contains the encoded `path_` if and only if `path_` is set in encoded
/// form. If path is in a normal form and needed no decoding, then this will
/// be empty, as we do not need to store a duplicate.
std::string originalPath_;
bool pathEncode_{true};
std::string_view matchedPathPattern_{""};
std::string query_;
SafeStringMap<std::string> headers_;
SafeStringMap<std::string> cookies_;
std::optional<size_t> contentLengthHeaderValue_;
size_t realContentLength_{0};
mutable SafeStringMap<std::string> parameters_;
mutable std::shared_ptr<Json::Value> jsonPtr_;
SessionPtr sessionPtr_;
mutable AttributesPtr attributesPtr_;
trantor::InetAddress peer_;
trantor::InetAddress local_;
trantor::Date creationDate_;
trantor::CertificatePtr peerCertificate_;
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};
std::vector<std::string> routingParams_;
ReqStreamStatus streamStatus_{ReqStreamStatus::None};
std::function<void()> streamFinishCb_;
RequestStreamReaderPtr streamReaderPtr_;
std::exception_ptr streamExceptionPtr_;
bool startProcessing_{false};
std::weak_ptr<trantor::TcpConnection> connPtr_;
protected:
std::string content_;
trantor::EventLoop *loop_;
mutable ContentType contentType_{CT_TEXT_PLAIN};
mutable bool flagForParsingContentType_{false};
mutable std::string contentTypeString_;
};
using HttpRequestImplPtr = std::shared_ptr<HttpRequestImpl>;
inline void swap(HttpRequestImpl &one, HttpRequestImpl &two) noexcept
{
one.swap(two);
}
} // namespace drogon
+491
View File
@@ -0,0 +1,491 @@
/**
*
* 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 "HttpRequestParser.h"
#include <drogon/HttpTypes.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <iostream>
#include "HttpAppFrameworkImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "HttpUtils.h"
using namespace trantor;
using namespace drogon;
static constexpr size_t CRLF_LEN = 2; // strlen("crlf")
static constexpr size_t METHOD_MAX_LEN = 7; // strlen("OPTIONS")
static constexpr size_t TRUNK_LEN_MAX_LEN = 16; // 0xFFFFFFFF,FFFFFFFF
HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr)
: status_(HttpRequestParseStatus::kExpectMethod),
loop_(connPtr->getLoop()),
conn_(connPtr)
{
}
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 *slash = std::find(start, space, '/');
if (slash != start && slash + 1 < space && *(slash + 1) == '/')
{
// scheme precedents
slash = std::find(slash + 2, space, '/');
}
const char *question = std::find(slash, space, '?');
if (slash != space)
{
request_->setPath(slash, question);
}
else
{
// An empty abs_path is equivalent to an abs_path of "/"
request_->setPath("/");
}
if (question != space)
{
request_->setQuery(question + 1, 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;
}
HttpRequestImplPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr)
{
return std::shared_ptr<HttpRequestImpl>(
ptr, [weakPtr = weak_from_this()](HttpRequestImpl *p) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
if (thisPtr->loop_->isInLoopThread())
{
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
}
else
{
auto &loop = thisPtr->loop_;
loop->queueInLoop([thisPtr = std::move(thisPtr), p]() {
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
});
}
}
else
{
delete p;
}
});
}
void HttpRequestParser::reset()
{
assert(loop_->isInLoopThread());
remainContentLength_ = 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 return -HttpStatusCode if encounters any http errors in request
* @return return -1 if encounters any other errors in request
* @return return 0 if request is not ready
* @return return 1 if request is ready
* @return return 2 if request is ready and entering stream mode
* @return return 3 if request header is ready and entering stream mode
*/
int HttpRequestParser::parseRequest(MsgBuffer *buf)
{
while (true)
{
switch (status_)
{
case (HttpRequestParseStatus::kExpectMethod):
{
auto *space = std::find(buf->peek(),
(const char *)buf->beginWrite(),
' ');
// no space in buffer
if (space == buf->beginWrite())
{
if (buf->readableBytes() > METHOD_MAX_LEN)
{
return -k400BadRequest;
}
return 0;
}
// try read method
if (!request_->setMethod(buf->peek(), space))
{
return -k405MethodNotAllowed;
}
status_ = HttpRequestParseStatus::kExpectRequestLine;
buf->retrieveUntil(space + 1);
continue;
}
case HttpRequestParseStatus::kExpectRequestLine:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for request line is 64K bytes. response
/// k414RequestURITooLarge
/// TODO: Make this configurable?
return -k414RequestURITooLarge;
}
return 0;
}
if (!processRequestLine(buf->peek(), crlf))
{
// error
return -k400BadRequest;
}
buf->retrieveUntil(crlf + CRLF_LEN);
status_ = HttpRequestParseStatus::kExpectHeaders;
continue;
}
case HttpRequestParseStatus::kExpectHeaders:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for every request header is 64K bytes;
/// TODO: Make this configurable?
return -k400BadRequest;
}
return 0;
}
const char *colon = std::find(buf->peek(), crlf, ':');
// found colon
if (colon != crlf)
{
request_->addHeader(buf->peek(), colon, crlf);
buf->retrieveUntil(crlf + CRLF_LEN);
continue;
}
buf->retrieveUntil(crlf + CRLF_LEN);
// end of headers
// We might want a kProcessHeaders status for code readability
// and maintainability.
// process header information
auto &len = request_->getHeaderBy("content-length");
if (!len.empty())
{
try
{
remainContentLength_ =
static_cast<size_t>(std::stoull(len));
}
catch (...)
{
return -k400BadRequest;
}
request_->contentLengthHeaderValue_ = remainContentLength_;
if (remainContentLength_ == 0)
{
// content-length = 0, request is over.
status_ = HttpRequestParseStatus::kGotAll;
}
else
{
status_ = HttpRequestParseStatus::kExpectBody;
}
}
else
{
const std::string &encode =
request_->getHeaderBy("transfer-encoding");
if (encode.empty())
{
// no content-length and no transfer-encoding,
// request is over.
status_ = HttpRequestParseStatus::kGotAll;
}
else if (encode == "chunked")
{
status_ = HttpRequestParseStatus::kExpectChunkLen;
}
else
{
return -k501NotImplemented;
}
}
// Check max body size
if (remainContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
return -k413RequestEntityTooLarge;
}
// Check expect:100-continue
auto &expect = request_->expect();
if (expect == "100-continue" &&
request_->getVersion() >= Version::kHttp11)
{
if (remainContentLength_ == 0)
{
// error
return -k400BadRequest;
}
else
{
// rfc2616-8.2.3
// TODO: consider adding an AOP for expect header
auto connPtr = conn_.lock(); // ugly
if (!connPtr)
{
return -1;
}
auto resp = HttpResponse::newHttpResponse();
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 << "\"";
return -k417ExpectationFailed;
}
assert(status_ == HttpRequestParseStatus::kGotAll ||
status_ == HttpRequestParseStatus::kExpectBody ||
status_ == HttpRequestParseStatus::kExpectChunkLen);
if (app().isRequestStreamEnabled())
{
request_->streamStart();
if (status_ == HttpRequestParseStatus::kGotAll)
{
++requestsCounter_;
return 2;
}
else
{
return 3;
}
}
// Reserve space for full body in non-stream mode.
// For stream mode requests that match a non-stream handler,
// we will reserve full body before waitForStreamFinish().
if (remainContentLength_)
{
request_->reserveBodySize(remainContentLength_);
}
continue;
}
case HttpRequestParseStatus::kExpectBody:
{
size_t bytesToConsume =
remainContentLength_ <= buf->readableBytes()
? remainContentLength_
: buf->readableBytes();
if (bytesToConsume)
{
request_->appendToBody(buf->peek(), bytesToConsume);
buf->retrieve(bytesToConsume);
remainContentLength_ -= bytesToConsume;
}
if (remainContentLength_ == 0)
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
return 1;
}
// readableBytes() == 0, function should return.
return 0;
}
case HttpRequestParseStatus::kExpectChunkLen:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() > TRUNK_LEN_MAX_LEN + CRLF_LEN)
{
return -k400BadRequest;
}
return 0;
}
// chunk length line
std::string len(buf->peek(), crlf - buf->peek());
char *end;
currentChunkLength_ = strtol(len.c_str(), &end, 16);
if (currentChunkLength_ != 0)
{
if (currentChunkLength_ + remainContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
return -k413RequestEntityTooLarge;
}
status_ = HttpRequestParseStatus::kExpectChunkBody;
}
else
{
status_ = HttpRequestParseStatus::kExpectLastEmptyChunk;
}
buf->retrieveUntil(crlf + CRLF_LEN);
continue;
}
case HttpRequestParseStatus::kExpectChunkBody:
{
if (buf->readableBytes() < (currentChunkLength_ + CRLF_LEN))
{
return 0;
}
if (*(buf->peek() + currentChunkLength_) != '\r' ||
*(buf->peek() + currentChunkLength_ + 1) != '\n')
{
// error!
return -k400BadRequest;
}
request_->appendToBody(buf->peek(), currentChunkLength_);
buf->retrieve(currentChunkLength_ + CRLF_LEN);
remainContentLength_ += currentChunkLength_;
currentChunkLength_ = 0;
status_ = HttpRequestParseStatus::kExpectChunkLen;
continue;
}
case HttpRequestParseStatus::kExpectLastEmptyChunk:
{
// last empty chunk
if (buf->readableBytes() < CRLF_LEN)
{
return 0;
}
if (*(buf->peek()) != '\r' || *(buf->peek() + 1) != '\n')
{
// error!
return -k400BadRequest;
}
buf->retrieve(CRLF_LEN);
if (!request_->isStreamMode())
{
// Previously we only have non-stream mode, drogon handled
// chunked encoding internally, and give user a regular
// request as if it has a content-length header.
//
// We have to keep compatibility for non-stream mode.
//
// But I don't think it's a good implementation. We should
// instead add an api to access real content-length of
// requests.
// Now HttpRequest::realContentLength() is added, and user
// should no longer parse content-length header by
// themselves.
//
// NOTE: request forward behavior may be infected in stream
// mode, we should check it out.
request_->addHeader("content-length",
std::to_string(
request_->realContentLength()));
request_->removeHeaderBy("transfer-encoding");
}
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
return 1;
}
case HttpRequestParseStatus::kGotAll:
{
++requestsCounter_;
return 1;
}
}
}
return -1; // won't reach here, just to make compiler happy
}
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req,
bool isHeadMethod)
{
assert(loop_->isInLoopThread());
requestPipelining_.push_back({req, {nullptr, isHeadMethod}});
}
/**
* @return returns true if the the response is the first in pipeline
*/
bool HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req,
HttpResponsePtr resp)
{
assert(loop_->isInLoopThread());
for (size_t i = 0; i != requestPipelining_.size(); ++i)
{
if (requestPipelining_[i].first == req)
{
requestPipelining_[i].second.first = std::move(resp);
return i == 0;
}
}
assert(false); // Should always find a match
return false;
}
void HttpRequestParser::popReadyResponses(
std::vector<std::pair<HttpResponsePtr, bool>> &buffer)
{
while (!requestPipelining_.empty() &&
requestPipelining_.front().second.first)
{
buffer.push_back(std::move(requestPipelining_.front().second));
requestPipelining_.pop_front();
}
}
+160
View File
@@ -0,0 +1,160 @@
/**
*
* 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 <drogon/HttpTypes.h>
#include <trantor/net/TcpConnection.h>
#include <trantor/utils/MsgBuffer.h>
#include <trantor/utils/NonCopyable.h>
#include <deque>
#include <memory>
#include <mutex>
#include "impl_forwards.h"
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);
int parseRequest(trantor::MsgBuffer *buf);
bool gotAll() const
{
return status_ == HttpRequestParseStatus::kGotAll;
}
void reset();
const HttpRequestImplPtr &requestImpl() const
{
return request_;
}
bool firstReq()
{
if (firstRequest_)
{
firstRequest_ = false;
return true;
}
return false;
}
const WebSocketConnectionImplPtr &webSocketConn() const
{
return websockConnPtr_;
}
void setWebsockConnection(const WebSocketConnectionImplPtr &conn)
{
websockConnPtr_ = conn;
}
// to support request pipelining(rfc2616-8.1.2.2)
void pushRequestToPipelining(const HttpRequestPtr &, bool isHeadMethod);
bool pushResponseToPipelining(const HttpRequestPtr &, HttpResponsePtr);
void popReadyResponses(std::vector<std::pair<HttpResponsePtr, bool>> &);
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<HttpRequestImplPtr> &getRequestBuffer()
{
assert(loop_->isInLoopThread());
if (!requestBuffer_)
{
requestBuffer_ = std::unique_ptr<std::vector<HttpRequestImplPtr>>(
new std::vector<HttpRequestImplPtr>);
}
return *requestBuffer_;
}
private:
HttpRequestImplPtr makeRequestForPool(HttpRequestImpl *p);
bool processRequestLine(const char *begin, const char *end);
HttpRequestParseStatus status_;
trantor::EventLoop *loop_;
HttpRequestImplPtr request_;
bool firstRequest_{true};
WebSocketConnectionImplPtr 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<HttpRequestImplPtr>> requestBuffer_;
std::vector<HttpRequestImplPtr> requestsPool_;
size_t currentChunkLength_{0};
size_t remainContentLength_{0};
};
} // namespace drogon
+983
View File
@@ -0,0 +1,983 @@
/**
*
* @file HttpResponseImpl.cc
* @author 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 "HttpResponseImpl.h"
#include "AOPAdvice.h"
#include "HttpAppFrameworkImpl.h"
#include "HttpUtils.h"
#include <drogon/HttpViewData.h>
#include <drogon/IOThreadStorage.h>
#include <filesystem>
#include <fstream>
#include <memory>
#include <cstdio>
#include <string>
#include <sys/stat.h>
#include <trantor/utils/Logger.h>
using namespace trantor;
using namespace drogon;
using namespace std::literals::string_literals;
using namespace std::placeholders;
#ifdef _WIN32
#undef max
#endif
namespace drogon
{
// "Fri, 23 Aug 2019 12:58:03 GMT" length = 29
static const size_t httpFullDateStringLength = 29;
static inline HttpResponsePtr genHttpResponse(const std::string &viewName,
const HttpViewData &data,
const HttpRequestPtr &req)
{
auto templ = DrTemplateBase::newTemplate(viewName);
if (templ)
{
auto res = HttpResponse::newHttpResponse();
res->setBody(templ->genText(data));
return res;
}
return drogon::HttpResponse::newNotFoundResponse(req);
}
} // namespace drogon
HttpResponsePtr HttpResponse::newHttpResponse()
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_TEXT_HTML);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpResponse(HttpStatusCode code,
ContentType type)
{
auto res = std::make_shared<HttpResponseImpl>(code, type);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(data);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data)
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(std::move(data));
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
const char *HttpResponseImpl::versionString() const
{
const char *result = "UNKNOWN";
switch (version_)
{
case Version::kHttp10:
result = "HTTP/1.0";
break;
case Version::kHttp11:
result = "HTTP/1.1";
break;
default:
break;
}
return result;
}
void HttpResponseImpl::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;
}
auto &precision = app().getFloatPrecisionInJson();
if (precision.first != 0)
{
builder["precision"] = precision.first;
builder["precisionType"] = precision.second;
}
});
bodyPtr_ = std::make_shared<HttpMessageStringBody>(
writeString(builder, *jsonPtr_));
}
HttpResponsePtr HttpResponse::newNotFoundResponse(const HttpRequestPtr &req)
{
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
auto &resp = HttpAppFrameworkImpl::instance().getCustom404Page();
if (resp)
{
if (loop && loop->index() < app().getThreadNum())
{
return resp;
}
else
{
return HttpResponsePtr{new HttpResponseImpl(
*static_cast<HttpResponseImpl *>(resp.get()))};
}
}
else
{
if (HttpAppFrameworkImpl::instance().isUsingCustomErrorHandler())
{
return app().getCustomErrorHandler()(k404NotFound, req);
}
else if (loop && loop->index() < app().getThreadNum())
{
// If the current thread is an IO thread
static std::once_flag threadOnce;
static IOThreadStorage<HttpResponsePtr> thread404Pages;
std::call_once(threadOnce, [req = req] {
thread404Pages.init([req = req](drogon::HttpResponsePtr &resp,
size_t /*index*/) {
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
{
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<HttpResponseImpl>();
res->setStatusCode(status);
res->redirect(location);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName,
const HttpViewData &data,
const HttpRequestPtr &req)
{
return genHttpResponse(viewName, data, req);
}
HttpResponsePtr HttpResponse::newFileResponse(
const unsigned char *pBuffer,
size_t bufferLength,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString)
{
// Make Raw HttpResponse
auto resp = std::make_shared<HttpResponseImpl>();
// Set response body and length
resp->setBody(
std::string(reinterpret_cast<const char *>(pBuffer), bufferLength));
// Set status of message
resp->setStatusCode(k200OK);
// Check for type and assign proper content type in header
if (!typeString.empty())
{
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_APPLICATION_OCTET_STREAM; // XXX: Is this Ok?
static_cast<HttpResponse *>(resp.get())
->setContentTypeCodeAndCustomString(type,
typeString.c_str(),
typeString.size());
}
else if (type != CT_NONE)
{
resp->setContentTypeCode(type);
}
else if (!attachmentFileName.empty())
{
resp->setContentTypeCode(drogon::getContentType(attachmentFileName));
}
else
{
resp->setContentTypeCode(
CT_APPLICATION_OCTET_STREAM); // default content-type for file;
}
// Add additional header values
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
// Finalize and return response
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
HttpResponsePtr HttpResponse::newFileResponse(
const std::string &fullPath,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString,
const HttpRequestPtr &req)
{
return newFileResponse(
fullPath, 0, 0, false, attachmentFileName, type, typeString, req);
}
HttpResponsePtr HttpResponse::newFileResponse(
const std::string &fullPath,
size_t offset,
size_t length,
bool setContentRange,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString,
const HttpRequestPtr &req)
{
std::ifstream infile(utils::toNativePath(fullPath), std::ifstream::binary);
LOG_TRACE << "send http file:" << fullPath << " offset " << offset
<< " length " << length;
if (!infile)
{
auto resp = HttpResponse::newNotFoundResponse(req);
return resp;
}
auto resp = std::make_shared<HttpResponseImpl>();
std::streambuf *pbuf = infile.rdbuf();
size_t filesize =
static_cast<size_t>(pbuf->pubseekoff(0, std::ifstream::end));
if (offset > filesize || length > filesize || // in case of overflow
offset + length > filesize)
{
resp->setStatusCode(k416RequestedRangeNotSatisfiable);
if (setContentRange)
{
char buf[64];
snprintf(buf, sizeof(buf), "bytes */%zu", filesize);
resp->addHeader("Content-Range", std::string(buf));
}
return resp;
}
if (length == 0)
{
length = filesize - offset;
}
pbuf->pubseekoff(offset, std::ifstream::beg); // rewind
if (HttpAppFrameworkImpl::instance().useSendfile() && length > 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);
// Must set length with the right value! Content-Length header relies on
// this value.
resp->setSendfileRange(offset, length);
}
else
{
std::string str;
str.resize(length);
pbuf->sgetn(&str[0], length);
resp->setBody(std::move(str));
resp->setSendfileRange(offset, length);
}
// Set correct status code
if (length < filesize)
{
resp->setStatusCode(k206PartialContent);
}
else
{
resp->setStatusCode(k200OK);
}
// Infer content type
if (type == CT_NONE)
{
if (!typeString.empty())
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
else if (!attachmentFileName.empty())
{
resp->setContentTypeCode(
drogon::getContentType(attachmentFileName));
}
else
{
resp->setContentTypeCode(drogon::getContentType(fullPath));
}
}
else
{
if (typeString.empty())
resp->setContentTypeCode(type);
else
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
}
// Set headers
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
if (setContentRange && length > 0)
{
char buf[128];
snprintf(buf,
sizeof(buf),
"bytes %zu-%zu/%zu",
offset,
offset + length - 1,
filesize);
resp->addHeader("Content-Range", std::string(buf));
}
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
HttpResponsePtr HttpResponse::newStreamResponse(
const std::function<std::size_t(char *, std::size_t)> &callback,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString,
const HttpRequestPtr &req)
{
LOG_TRACE << "send stream as "s
<< (attachmentFileName.empty() ? "raw data"s
: "file: "s + attachmentFileName);
if (!callback)
{
auto resp = HttpResponse::newNotFoundResponse();
return resp;
}
auto resp = std::make_shared<HttpResponseImpl>();
resp->setStreamCallback(callback);
resp->setStatusCode(k200OK);
// Infer content type
if (type == CT_NONE)
{
if (!typeString.empty())
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
else if (!attachmentFileName.empty())
{
resp->setContentTypeCode(
drogon::getContentType(attachmentFileName));
}
}
else
{
if (typeString.empty())
resp->setContentTypeCode(type);
else
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
}
// Set headers
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
HttpResponsePtr HttpResponse::newAsyncStreamResponse(
const std::function<void(ResponseStreamPtr)> &callback,
bool disableKickoffTimeout)
{
if (!callback)
{
auto resp = HttpResponse::newNotFoundResponse();
return resp;
}
auto resp = std::make_shared<HttpResponseImpl>();
resp->setAsyncStreamCallback(callback, disableKickoffTimeout);
resp->setStatusCode(k200OK);
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
void HttpResponseImpl::makeHeaderString(trantor::MsgBuffer &buffer)
{
buffer.ensureWritableBytes(128);
int len{0};
if (version_ == Version::kHttp11)
{
if (customStatusCode_ >= 0)
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.1 %d ",
customStatusCode_);
}
else
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.1 %d ",
statusCode_);
}
}
else
{
if (customStatusCode_ >= 0)
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.0 %d ",
customStatusCode_);
}
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 (!contentLengthIsAllowed())
{
len = 0;
if ((bodyPtr_ && bodyPtr_->length() > 0) ||
!sendfileName_.empty() || streamCallback_ ||
asyncStreamCallback_)
{
LOG_ERROR << "The body should be empty when the content-length "
"is not allowed!";
}
}
else if (streamCallback_ || asyncStreamCallback_)
{
// When the headers are created, it is time to set the transfer
// encoding to chunked if the contents size is not specified
if (!ifCloseConnection() &&
headers_.find("content-length") == headers_.end())
{
LOG_DEBUG << "send stream with transfer-encoding chunked";
headers_["transfer-encoding"] = "chunked";
}
len = 0;
}
else if (sendfileName_.empty())
{
auto bodyLength = bodyPtr_ ? bodyPtr_->length() : 0;
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(bodyLength)>(),
bodyLength);
}
else
{
auto bodyLength = sendfileRange_.second;
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(bodyLength)>(),
bodyLength);
}
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");
}
}
if (!contentTypeString_.empty())
{
buffer.append("content-type: ");
buffer.append(contentTypeString_);
buffer.append("\r\n");
}
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 HttpResponseImpl::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_.empty())
{
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::getHttpFullDateStr(trantor::Date::date()));
buffer.append("\r\n\r\n");
}
else
{
buffer.append("\r\n");
}
if (bodyPtr_ && contentLengthIsAllowed())
buffer.append(bodyPtr_->data(), bodyPtr_->length());
}
std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::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() /
trantor::Date::MICRO_SECONDS_PER_SEC) != httpStringDate_);
assert(httpString_);
if (isDateChanged)
{
httpStringDate_ = now.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_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_.empty())
{
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::getHttpFullDateStr(trantor::Date::date()));
httpString->append("\r\n\r\n");
datePos_ = datePos;
}
else
{
httpString->append("\r\n");
}
LOG_TRACE << "response(no body):"
<< std::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> HttpResponseImpl::
renderHeaderForHeadMethod()
{
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_)
{
makeHeaderString(*httpString);
}
else
{
httpString->append(*fullHeaderString_);
}
// output cookies
if (!cookies_.empty())
{
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 HttpResponseImpl::addHeader(const char *start,
const char *colon,
const char *end)
{
fullHeaderString_.reset();
std::string field(start, colon);
transform(field.begin(), field.end(), field.begin(), [](unsigned char c) {
return tolower(c);
});
++colon;
while (colon < end && isspace(static_cast<unsigned char>(*colon)))
{
++colon;
}
std::string value(colon, end);
while (!value.empty() &&
isspace(static_cast<unsigned char>(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(static_cast<unsigned char>(cookie_name[cpos])))
++cpos;
cookie_name = cookie_name.substr(cpos);
++epos;
while (epos < coo.length() &&
isspace(static_cast<unsigned char>(coo[epos])))
++epos;
cookie_value = coo.substr(epos);
}
else
{
std::string::size_type cpos = 0;
while (cpos < coo.length() &&
isspace(static_cast<unsigned char>(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(),
[](unsigned char c) { return tolower(c); });
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);
}
else if (cookie_name == "samesite")
{
cookie.setSameSite(
cookie.convertString2SameSite(cookie_value));
}
else if (cookie_name == "max-age")
{
cookie.setMaxAge(std::stoi(cookie_value));
}
}
}
if (!cookie.key().empty())
{
cookies_[cookie.key()] = std::move(cookie);
}
}
else
{
headers_[std::move(field)] = std::move(value);
}
}
void HttpResponseImpl::swap(HttpResponseImpl &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_);
swap(streamCallback_, that.streamCallback_);
swap(asyncStreamCallback_, that.asyncStreamCallback_);
jsonPtr_.swap(that.jsonPtr_);
fullHeaderString_.swap(that.fullHeaderString_);
httpString_.swap(that.httpString_);
swap(datePos_, that.datePos_);
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
}
void HttpResponseImpl::clear()
{
statusCode_ = kUnknown;
version_ = Version::kHttp11;
statusMessage_ = std::string_view{};
fullHeaderString_.reset();
jsonParsingErrorPtr_.reset();
sendfileName_.clear();
if (streamCallback_)
{
LOG_TRACE << "Cleanup HttpResponse stream callback";
streamCallback_(nullptr, 0); // callback internal cleanup
streamCallback_ = {};
}
if (asyncStreamCallback_)
{
// asyncStreamCallback_(nullptr);
asyncStreamCallback_ = {};
}
headers_.clear();
cookies_.clear();
bodyPtr_.reset();
jsonPtr_.reset();
expriedTime_ = -1;
datePos_ = std::string::npos;
flagForParsingContentType_ = false;
flagForParsingJson_ = false;
}
void HttpResponseImpl::parseJson() const
{
static std::once_flag once;
static Json::CharReaderBuilder builder;
std::call_once(once, []() {
builder["collectComments"] = false;
builder["stackLimit"] =
static_cast<Json::UInt>(drogon::app().getJsonParserStackLimit());
});
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");
}
}
bool HttpResponseImpl::shouldBeCompressed() const
{
if (streamCallback_ || asyncStreamCallback_ || !sendfileName_.empty() ||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
getBody().length() < 1024 ||
!(getHeaderBy("content-encoding").empty()) || !contentLengthIsAllowed())
{
return false;
}
return true;
}
void HttpResponseImpl::setContentTypeString(const char *typeString,
size_t typeStringLength)
{
std::string sv(typeString, typeStringLength);
auto contentType = parseContentType(sv);
if (contentType == CT_NONE)
contentType = CT_CUSTOM;
contentType_ = contentType;
contentTypeString_ = std::string(sv);
flagForParsingContentType_ = true;
}
+559
View File
@@ -0,0 +1,559 @@
/**
*
* @file HttpResponseImpl.h
* @author 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 "HttpUtils.h"
#include "HttpMessageBody.h"
#include <drogon/exports.h>
#include <drogon/HttpResponse.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/MsgBuffer.h>
#include <memory>
#include <mutex>
#include <string>
#include <atomic>
#include <unordered_map>
namespace drogon
{
class DROGON_EXPORT HttpResponseImpl : public HttpResponse
{
friend class HttpResponseParser;
public:
HttpResponseImpl() : creationDate_(trantor::Date::now())
{
}
HttpResponseImpl(HttpStatusCode code, ContentType type)
: statusCode_(code),
statusMessage_(statusCodeToString(code)),
creationDate_(trantor::Date::now()),
contentType_(type),
flagForParsingContentType_(true),
contentTypeString_(contentTypeToMime(type))
{
}
void setPassThrough(bool flag) override
{
passThrough_ = flag;
}
HttpStatusCode statusCode() const override
{
return statusCode_;
}
const trantor::Date &creationDate() const override
{
return creationDate_;
}
void setStatusCode(HttpStatusCode code) override
{
statusCode_ = code;
setStatusMessage(statusCodeToString(code));
}
void setVersion(const Version v) override
{
version_ = v;
if (version_ == Version::kHttp10)
{
closeConnection_ = true;
}
}
Version version() const override
{
return version_;
}
const char *versionString() const override;
void setCloseConnection(bool on) override
{
closeConnection_ = on;
}
bool ifCloseConnection() const override
{
return closeConnection_;
}
void setContentTypeCode(ContentType type) override
{
contentType_ = type;
auto ct = contentTypeToMime(type);
contentTypeString_ = std::string(ct.data(), ct.size());
flagForParsingContentType_ = true;
}
// void setContentTypeCodeAndCharacterSet(ContentType type, const
// std::string &charSet = "utf-8") override
// {
// contentType_ = type;
// setContentType(webContentTypeAndCharsetToString(type, charSet));
// }
ContentType contentType() const override
{
parseContentTypeAndString();
return contentType_;
}
const std::string &getHeader(std::string key) const override
{
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
return tolower(c);
});
return getHeaderBy(key);
}
void removeHeader(std::string key) override
{
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
return tolower(c);
});
removeHeaderBy(key);
}
const SafeStringMap<std::string> &headers() const override
{
return headers_;
}
const std::string &getHeaderBy(const std::string &lowerKey) const
{
static const std::string defaultVal;
auto iter = headers_.find(lowerKey);
if (iter == headers_.end())
{
return defaultVal;
}
return iter->second;
}
void removeHeaderBy(const std::string &lowerKey)
{
fullHeaderString_.reset();
headers_.erase(lowerKey);
}
void addHeader(std::string field, const std::string &value) override
{
fullHeaderString_.reset();
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = value;
}
void addHeader(std::string field, std::string &&value) override
{
fullHeaderString_.reset();
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = std::move(value);
}
void addHeader(const char *start, const char *colon, const char *end);
void addCookie(const std::string &key, const std::string &value) override
{
cookies_[key] = Cookie(key, value);
}
void addCookie(const Cookie &cookie) override
{
cookies_[cookie.key()] = cookie;
}
void addCookie(Cookie &&cookie) override
{
cookies_[cookie.key()] = std::move(cookie);
}
const Cookie &getCookie(const std::string &key) const override
{
static const Cookie defaultCookie;
auto it = cookies_.find(key);
if (it != cookies_.end())
{
return it->second;
}
return defaultCookie;
}
const SafeStringMap<Cookie> &cookies() const override
{
return cookies_;
}
void removeCookie(const std::string &key) override
{
cookies_.erase(key);
}
void setBody(const std::string &body) override
{
bodyPtr_ = std::make_shared<HttpMessageStringBody>(body);
if (passThrough_)
{
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
void setBody(std::string &&body) override
{
bodyPtr_ = std::make_shared<HttpMessageStringBody>(std::move(body));
if (passThrough_)
{
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
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();
void clear() override;
void setExpiredTime(ssize_t expiredTime) override
{
expriedTime_ = expiredTime;
datePos_ = std::string::npos;
if (expriedTime_ < 0 && version_ == Version::kHttp10)
{
fullHeaderString_.reset();
}
}
ssize_t expiredTime() const override
{
return expriedTime_;
}
const char *getBodyData() const override
{
if (!flagForSerializingJson_ && jsonPtr_)
{
generateBodyFromJson();
}
else if (!bodyPtr_)
{
return nullptr;
}
return bodyPtr_->data();
}
size_t getBodyLength() const override
{
if (bodyPtr_)
return bodyPtr_->length();
return 0;
}
void swap(HttpResponseImpl &that) noexcept;
void parseJson() const;
const std::shared_ptr<Json::Value> &jsonObject() const override
{
// 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::string &getJsonError() const override
{
static const std::string none;
if (jsonParsingErrorPtr_)
return *jsonParsingErrorPtr_;
return none;
}
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 override
{
return sendfileName_;
}
const SendfileRange &sendfileRange() const override
{
return sendfileRange_;
}
const trantor::CertificatePtr &peerCertificate() const override
{
return peerCertificate_;
}
void setPeerCertificate(const trantor::CertificatePtr &cert)
{
peerCertificate_ = cert;
}
void setSendfile(const std::string &filename)
{
sendfileName_ = filename;
}
void setSendfileRange(size_t offset, size_t len)
{
sendfileRange_.first = offset;
sendfileRange_.second = len;
}
const std::function<std::size_t(char *, std::size_t)> &streamCallback()
const override
{
return streamCallback_;
}
void setStreamCallback(
const std::function<std::size_t(char *, std::size_t)> &callback)
{
streamCallback_ = callback;
}
const std::function<void(ResponseStreamPtr)> &asyncStreamCallback()
const override
{
return asyncStreamCallback_;
}
void setAsyncStreamCallback(
const std::function<void(ResponseStreamPtr)> &callback,
bool disableKickoffTimeout)
{
asyncStreamCallback_ = callback;
asyncStreamDisableKickoff_ = disableKickoffTimeout;
}
bool asyncStreamKickoffDisabled() const
{
return asyncStreamDisableKickoff_;
}
void makeHeaderString()
{
fullHeaderString_ = std::make_shared<trantor::MsgBuffer>(128);
makeHeaderString(*fullHeaderString_);
}
std::string contentTypeString() const override
{
parseContentTypeAndString();
return contentTypeString_;
}
void gunzip()
{
if (bodyPtr_)
{
auto gunzipBody =
utils::gzipDecompress(bodyPtr_->data(), bodyPtr_->length());
removeHeaderBy("content-encoding");
bodyPtr_ =
std::make_shared<HttpMessageStringBody>(std::move(gunzipBody));
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
bool contentLengthIsAllowed() const
{
int statusCode =
customStatusCode_ >= 0 ? customStatusCode_ : statusCode_;
// return false if status code is 1xx or 204
return (statusCode >= k200OK || statusCode < k100Continue) &&
statusCode != k204NoContent;
}
#ifdef USE_BROTLI
void brDecompress()
{
if (bodyPtr_)
{
auto gunzipBody =
utils::brotliDecompress(bodyPtr_->data(), bodyPtr_->length());
removeHeaderBy("content-encoding");
bodyPtr_ =
std::make_shared<HttpMessageStringBody>(std::move(gunzipBody));
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
#endif
~HttpResponseImpl() override = default;
protected:
void makeHeaderString(trantor::MsgBuffer &headerString);
void parseContentTypeAndString() 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(
std::string_view(contentTypeString.data(), pos));
}
else
{
contentType_ =
parseContentType(std::string_view(contentTypeString));
}
if (contentType_ == CT_NONE)
contentType_ = CT_CUSTOM;
contentTypeString_ = contentTypeString;
}
}
}
private:
void setBody(const char *body, size_t len) override
{
bodyPtr_ = std::make_shared<HttpMessageStringViewBody>(body, len);
if (passThrough_)
{
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
void setContentTypeCodeAndCustomString(ContentType type,
const char *typeString,
size_t typeStringLength) override
{
contentType_ = type;
flagForParsingContentType_ = true;
std::string_view sv(typeString, typeStringLength);
bool haveHeader = sv.find("content-type: ") == 0;
bool haveCRLF = sv.rfind("\r\n") == sv.size() - 2;
size_t endOffset = 0;
if (haveHeader)
endOffset += 14;
if (haveCRLF)
endOffset += 2;
setContentType(std::string_view{typeString + (haveHeader ? 14 : 0),
typeStringLength - endOffset});
}
void setContentTypeString(const char *typeString,
size_t typeStringLength) override;
void setCustomStatusCode(int code,
const char *message,
size_t messageLength) override
{
assert(code >= 0);
customStatusCode_ = code;
statusMessage_ = std::string_view{message, messageLength};
}
SafeStringMap<std::string> headers_;
SafeStringMap<Cookie> cookies_;
int customStatusCode_{-1};
HttpStatusCode statusCode_{kUnknown};
std::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_;
SendfileRange sendfileRange_{0, 0};
std::function<std::size_t(char *, std::size_t)> streamCallback_;
std::function<void(ResponseStreamPtr)> asyncStreamCallback_;
bool asyncStreamDisableKickoff_{false};
mutable std::shared_ptr<Json::Value> jsonPtr_;
std::shared_ptr<trantor::MsgBuffer> fullHeaderString_;
trantor::CertificatePtr peerCertificate_;
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_;
mutable std::string contentTypeString_{"text/html; charset=utf-8"};
bool passThrough_{false};
void setContentType(const std::string_view &contentType)
{
contentTypeString_ =
std::string(contentType.data(), contentType.size());
}
void setStatusMessage(const std::string_view &message)
{
statusMessage_ = message;
}
};
using HttpResponseImplPtr = std::shared_ptr<HttpResponseImpl>;
inline void swap(HttpResponseImpl &one, HttpResponseImpl &two) noexcept
{
one.swap(two);
}
} // namespace drogon
+319
View File
@@ -0,0 +1,319 @@
/**
*
* @file HttpResponseParser.cc
* @author 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 <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <algorithm>
using namespace trantor;
using namespace drogon;
void HttpResponseParser::reset()
{
status_ = HttpResponseParseStatus::kExpectResponseLine;
responsePtr_.reset(new HttpResponseImpl);
parseResponseForHeadMethod_ = false;
leftBodyLength_ = 0;
currentChunkLength_ = 0;
}
HttpResponseParser::HttpResponseParser(const trantor::TcpConnectionPtr &connPtr)
: status_(HttpResponseParseStatus::kExpectResponseLine),
responsePtr_(new HttpResponseImpl),
conn_(connPtr)
{
}
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;
}
bool HttpResponseParser::parseResponseOnClose()
{
if (status_ == HttpResponseParseStatus::kExpectClose)
{
status_ = HttpResponseParseStatus::kGotAll;
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_ = static_cast<size_t>(std::stoull(len));
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 &&
[this]() -> bool {
std::string upgradeValue =
responsePtr_->getHeaderBy("upgrade");
std::transform(upgradeValue.begin(),
upgradeValue.end(),
upgradeValue.begin(),
[](unsigned char c) {
return tolower(c);
});
return upgradeValue == "websocket";
}()))
{
// The Websocket response may not have a
// content-length header.
status_ = HttpResponseParseStatus::kGotAll;
hasMore = false;
}
else
{
status_ = HttpResponseParseStatus::kExpectClose;
auto connPtr = conn_.lock();
connPtr->shutdown();
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;
responsePtr_->addHeader("content-length",
std::to_string(
responsePtr_->getBody().length()));
responsePtr_->removeHeaderBy("transfer-encoding");
break;
}
else
{
hasMore = false;
}
}
}
return ok;
}
+77
View File
@@ -0,0 +1,77 @@
/**
*
* @file HttpResponseParser.h
* @author 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,
};
explicit HttpResponseParser(const trantor::TcpConnectionPtr &connPtr);
// default copy-ctor, dtor and assignment are fine
// return false if any error
bool parseResponse(trantor::MsgBuffer *buf);
bool parseResponseOnClose();
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};
std::weak_ptr<trantor::TcpConnection> conn_;
};
} // namespace drogon
File diff suppressed because it is too large Load Diff
+166
View File
@@ -0,0 +1,166 @@
/**
*
* @file HttpServer.h
* @author 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/TcpServer.h>
#include <trantor/utils/NonCopyable.h>
#include <functional>
#include <string>
#include <vector>
#include "impl_forwards.h"
struct CallbackParamPack;
namespace drogon
{
struct ControllerBinderBase;
class HttpServer : trantor::NonCopyable
{
public:
HttpServer(trantor::EventLoop *loop,
const trantor::InetAddress &listenAddr,
std::string name);
~HttpServer();
void setIoLoops(const std::vector<trantor::EventLoop *> &ioLoops)
{
server_.setIoLoops(ioLoops);
}
void start();
void stop();
void enableSSL(trantor::TLSPolicyPtr policy)
{
server_.enableSSL(std::move(policy));
}
void reloadSSL()
{
server_.reloadSSL();
}
const trantor::InetAddress &address() const
{
return server_.address();
}
void setBeforeListenSockOptCallback(std::function<void(int)> cb)
{
beforeListenSetSockOptCallback_ = std::move(cb);
}
void setAfterAcceptSockOptCallback(std::function<void(int)> cb)
{
afterAcceptSetSockOptCallback_ = std::move(cb);
}
void setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb)
{
connectionCallback_ = std::move(cb);
}
private:
friend class HttpInternalForwardHelper;
static void onConnection(const trantor::TcpConnectionPtr &conn);
static void onMessage(const trantor::TcpConnectionPtr &,
trantor::MsgBuffer *);
static void onRequests(const trantor::TcpConnectionPtr &,
const std::vector<HttpRequestImplPtr> &,
const std::shared_ptr<HttpRequestParser> &);
struct HttpRequestParamPack
{
std::shared_ptr<ControllerBinderBase> binderPtr;
std::function<void(const HttpResponsePtr &)> callback;
};
struct WsRequestParamPack
{
std::shared_ptr<ControllerBinderBase> binderPtr;
std::function<void(const HttpResponsePtr &)> callback;
WebSocketConnectionImplPtr wsConnPtr;
};
// Http request handling steps
static void onHttpRequest(const HttpRequestImplPtr &,
std::function<void(const HttpResponsePtr &)> &&);
static void httpRequestRouting(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
static void httpRequestHandling(
const HttpRequestImplPtr &req,
std::shared_ptr<ControllerBinderBase> &&binderPtr,
std::function<void(const HttpResponsePtr &)> &&callback);
// Websocket request handling steps
static void onWebsocketRequest(
const HttpRequestImplPtr &,
std::function<void(const HttpResponsePtr &)> &&,
WebSocketConnectionImplPtr &&);
static void websocketRequestRouting(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
WebSocketConnectionImplPtr &&wsConnPtr);
static void websocketRequestHandling(
const HttpRequestImplPtr &req,
std::shared_ptr<ControllerBinderBase> &&binderPtr,
std::function<void(const HttpResponsePtr &)> &&callback,
WebSocketConnectionImplPtr &&wsConnPtr);
// Http/Websocket shared handling steps
template <typename Pack>
static void requestPostRouting(const HttpRequestImplPtr &req, Pack &&pack);
template <typename Pack>
static void requestPassMiddlewares(const HttpRequestImplPtr &req,
Pack &&pack);
template <typename Pack>
static void requestPreHandling(const HttpRequestImplPtr &req, Pack &&pack);
// Response buffering and sending
static void handleResponse(
const HttpResponsePtr &response,
const std::shared_ptr<CallbackParamPack> &paramPack,
bool *respReadyPtr);
static void sendResponse(const trantor::TcpConnectionPtr &,
const HttpResponsePtr &,
bool isHeadMethod);
static void sendResponses(
const trantor::TcpConnectionPtr &conn,
const std::vector<std::pair<HttpResponsePtr, bool>> &responses,
trantor::MsgBuffer &buffer);
trantor::TcpServer server_;
std::function<void(int)> beforeListenSetSockOptCallback_;
std::function<void(int)> afterAcceptSetSockOptCallback_;
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
};
class HttpInternalForwardHelper
{
public:
static void forward(const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
return HttpServer::onHttpRequest(req, std::move(callback));
}
};
} // namespace drogon
+743
View File
@@ -0,0 +1,743 @@
/**
*
* @file HttpUtils.cc
* @author 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 "HttpUtils.h"
#include <drogon/utils/Utilities.h>
#include <trantor/utils/Logger.h>
#include <map>
#include <unordered_map>
#include <mutex>
namespace drogon
{
static std::unordered_map<std::string, std::string> customMime;
// https://en.wikipedia.org/wiki/List_of_file_formats
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
// https://www.digipres.org/formats/mime-types/
// https://www.iana.org/assignments/media-types/media-types.xhtml
// content type -> list of corresponding mime types, the first being the default
// (more standard) one + the mime type to return in contentTypeToMime() when not
// empty (mainly to return the charset for text types)
static const std::unordered_map<
ContentType,
std::pair<std::vector<std::string_view>, std::string_view>>
mimeTypeDatabase_{
{CT_NONE, {{""}, ""}},
{CT_APPLICATION_OCTET_STREAM, {{"application/octet-stream"}, ""}},
{CT_APPLICATION_X_FORM, {{"application/x-www-form-urlencoded"}, ""}},
{CT_MULTIPART_FORM_DATA, {{"multipart/form-data"}, ""}},
{CT_APPLICATION_GZIP, {{"application/gzip"}, ""}},
{CT_APPLICATION_JSON,
{{"application/json"}, "application/json; charset=utf-8"}},
{CT_APPLICATION_FONT_WOFF, {{"application/font-woff"}, ""}},
{CT_APPLICATION_FONT_WOFF2, {{"application/font-woff2"}, ""}},
{CT_APPLICATION_JAVA_ARCHIVE,
{{"application/java-archive", "application/x-java-archive"}, ""}},
{CT_APPLICATION_MSWORD, {{"application/msword"}, ""}},
{CT_APPLICATION_MSWORDX,
{{"application/"
"vnd.openxmlformats-officedocument.wordprocessingml.document"},
""}},
{CT_APPLICATION_PDF, {{"application/pdf"}, ""}},
{CT_APPLICATION_VND_MS_FONTOBJ,
{{"application/vnd.ms-fontobject"}, ""}},
{CT_APPLICATION_VND_RAR, {{"application/vnd.rar"}, ""}},
{CT_APPLICATION_WASM, {{"application/wasm"}, ""}},
{CT_APPLICATION_X_BZIP, {{"application/x-bzip"}, ""}},
{CT_APPLICATION_X_BZIP2, {{"application/x-bzip2"}, ""}},
{CT_APPLICATION_X_7Z, {{"application/x-7z-compressed"}, ""}},
{CT_APPLICATION_X_HTTPD_PHP, {{"application/x-httpd-php"}, ""}},
{CT_APPLICATION_X_JAVASCRIPT,
{{"application/x-javascript"},
"application/x-javascript; charset=utf-8"}},
{CT_APPLICATION_X_FONT_OPENTYPE,
{{"application/x-font-opentype", "font/otf"}, ""}},
{CT_APPLICATION_X_FONT_TRUETYPE,
{{"application/x-font-truetype", "font/ttf"}, ""}},
{CT_APPLICATION_X_TAR, {{"application/x-tar"}, ""}},
{CT_APPLICATION_X_TGZ, {{"application/x-tgz"}, ""}},
{CT_APPLICATION_X_XZ, {{"application/x-xz", "application/x-lzma"}, ""}},
{CT_APPLICATION_XHTML,
{{"application/xhtml+xml", "application/xhtml"},
"application/xhtml+xml; charset=utf-8"}},
{CT_APPLICATION_XML,
{{"application/xml"}, "application/xml; charset=utf-8"}},
{CT_APPLICATION_ZIP, {{"application/zip"}, ""}},
{CT_AUDIO_AAC, {{"audio/aac", "audio/aacp"}, ""}},
{CT_AUDIO_AC3, {{"audio/ac3"}, ""}},
{CT_AUDIO_AIFF, {{"audio/aiff", "audio/x-aiff"}, ""}},
{CT_AUDIO_FLAC, {{"audio/flac"}, ""}},
{CT_AUDIO_MATROSKA, {{"audio/matroska", "audio/x-matroska"}, ""}},
{CT_AUDIO_MPEG, {{"audio/mpeg"}, ""}},
{CT_AUDIO_MPEG4, {{"audio/mp4", "audio/x-m4a"}, ""}},
{CT_AUDIO_OGG, {{"audio/ogg"}, ""}},
{CT_AUDIO_WAVE, {{"audio/wav", "audio/x-wav"}, ""}},
{CT_AUDIO_X_APE, {{"audio/x-ape"}, ""}},
{CT_AUDIO_X_MS_WMA, {{"audio/x-ms-wma"}, ""}},
{CT_AUDIO_X_TTA, {{"audio/x-tta"}, ""}},
{CT_AUDIO_X_WAVPACK, {{"audio/x-wavpack"}, ""}},
{CT_AUDIO_WEBM, {{"audio/webm"}, ""}},
{CT_IMAGE_APNG, {{"image/apng"}, ""}},
{CT_IMAGE_AVIF, {{"image/avif"}, ""}},
{CT_IMAGE_BMP, {{"image/bmp"}, ""}},
{CT_IMAGE_GIF, {{"image/gif"}, ""}},
{CT_IMAGE_ICNS, {{"image/icns"}, ""}},
{CT_IMAGE_JP2, {{"image/jp2", "image/jpx", "image/jpm"}, ""}},
{CT_IMAGE_JPG, {{"image/jpeg"}, ""}},
{CT_IMAGE_PNG, {{"image/png"}, ""}},
{CT_IMAGE_SVG_XML, {{"image/svg+xml"}, ""}},
{CT_IMAGE_TIFF, {{"image/tiff"}, ""}},
{CT_IMAGE_WEBP, {{"image/webp"}, ""}},
{CT_IMAGE_X_MNG, {{"image/x-mng"}, ""}},
{CT_IMAGE_X_TGA, {{"image/x-tga", "image/x-targa"}, ""}},
{CT_IMAGE_XICON, {{"image/vnd.microsoft.icon", "image/x-icon"}, ""}},
{CT_TEXT_CSS, {{"text/css"}, "text/css; charset=utf-8"}},
{CT_TEXT_CSV, {{"text/csv"}, "text/csv; charset=utf-8"}},
{CT_TEXT_HTML, {{"text/html"}, "text/html; charset=utf-8"}},
{CT_TEXT_JAVASCRIPT,
{{"text/javascript"}, "text/javascript; charset=utf-8"}},
{CT_TEXT_PLAIN, {{"text/plain"}, "text/plain; charset=utf-8"}},
{CT_TEXT_XML, {{"text/xml"}, "text/xml; charset=utf-8"}},
{CT_TEXT_XSL, {{"text/xsl"}, "text/xsl; charset=utf-8"}},
{CT_VIDEO_APG, {{"video/apg"}, ""}},
{CT_VIDEO_AV1, {{"video/av01", "video/av1"}, ""}},
{CT_VIDEO_QUICKTIME, {{"video/quicktime"}, ""}},
{CT_VIDEO_MPEG, {{"video/mpeg"}, ""}},
{CT_VIDEO_MPEG2TS, {{"video/mp2t"}, ""}},
{CT_VIDEO_MP4, {{"video/mp4"}, ""}},
{CT_VIDEO_OGG, {{"video/ogg"}, ""}},
{CT_VIDEO_WEBM, {{"video/webm"}, ""}},
{CT_VIDEO_X_M4V, {{"video/x-m4v"}, ""}},
{CT_VIDEO_MATROSKA, {{"video/matroska", "video/x-matroska"}, ""}},
{CT_VIDEO_X_MSVIDEO, {{"video/x-msvideo"}, ""}},
};
static const std::unordered_map<std::string_view,
std::pair<FileType, ContentType>>
fileTypeDatabase_{
{"", {FT_UNKNOWN, CT_CUSTOM}},
{"aac", {FT_AUDIO, CT_AUDIO_AAC}},
{"ac3", {FT_AUDIO, CT_AUDIO_AC3}},
{"aif", {FT_AUDIO, CT_AUDIO_AIFF}},
{"aifc", {FT_AUDIO, CT_AUDIO_AIFF}},
{"aiff", {FT_AUDIO, CT_AUDIO_AIFF}},
{"apg", {FT_AUDIO, CT_VIDEO_APG}},
{"ape", {FT_AUDIO, CT_AUDIO_X_APE}},
{"apng", {FT_IMAGE, CT_IMAGE_APNG}},
{"av1", {FT_MEDIA, CT_VIDEO_AV1}},
{"avi", {FT_MEDIA, CT_VIDEO_X_MSVIDEO}},
{"avif", {FT_IMAGE, CT_IMAGE_AVIF}},
{"bmp", {FT_IMAGE, CT_IMAGE_BMP}},
{"bz", {FT_ARCHIVE, CT_APPLICATION_X_BZIP}},
{"bz2", {FT_ARCHIVE, CT_APPLICATION_X_BZIP2}},
{"css", {FT_DOCUMENT, CT_TEXT_CSS}},
{"csv", {FT_DOCUMENT, CT_TEXT_CSV}},
{"doc", {FT_DOCUMENT, CT_APPLICATION_MSWORD}},
{"docx", {FT_DOCUMENT, CT_APPLICATION_MSWORDX}},
{"eot", {FT_DOCUMENT, CT_APPLICATION_VND_MS_FONTOBJ}},
{"flac", {FT_AUDIO, CT_AUDIO_FLAC}},
{"gif", {FT_MEDIA, CT_IMAGE_GIF}},
{"gz", {FT_ARCHIVE, CT_APPLICATION_GZIP}},
{"htm", {FT_DOCUMENT, CT_TEXT_HTML}},
{"html", {FT_DOCUMENT, CT_TEXT_HTML}},
{"icns", {FT_IMAGE, CT_IMAGE_ICNS}},
{"ico", {FT_IMAGE, CT_IMAGE_XICON}},
{"j2k", {FT_IMAGE, CT_IMAGE_JP2}},
{"jar", {FT_DOCUMENT, CT_APPLICATION_JAVA_ARCHIVE}},
{"j2c", {FT_IMAGE, CT_IMAGE_JP2}},
{"jp2", {FT_IMAGE, CT_IMAGE_JP2}},
{"jpeg", {FT_IMAGE, CT_IMAGE_JPG}},
{"jpc", {FT_IMAGE, CT_IMAGE_JP2}},
{"jpf", {FT_IMAGE, CT_IMAGE_JP2}},
{"jpg", {FT_IMAGE, CT_IMAGE_JPG}},
{"jpg2", {FT_IMAGE, CT_IMAGE_JP2}},
{"jpm", {FT_IMAGE, CT_IMAGE_JP2}},
{"jpx", {FT_IMAGE, CT_IMAGE_JP2}},
{"js", {FT_DOCUMENT, CT_TEXT_JAVASCRIPT}},
{"json", {FT_DOCUMENT, CT_APPLICATION_JSON}},
{"lzma", {FT_ARCHIVE, CT_APPLICATION_X_XZ}},
{"m1a", {FT_AUDIO, CT_AUDIO_MPEG}},
{"m1v", {FT_MEDIA, CT_VIDEO_MPEG}},
{"m2a", {FT_AUDIO, CT_AUDIO_MPEG}},
{"m2ts", {FT_MEDIA, CT_VIDEO_MPEG2TS}},
{"m2v", {FT_MEDIA, CT_VIDEO_MPEG}},
{"m4a", {FT_AUDIO, CT_AUDIO_MPEG4}},
{"m4v", {FT_MEDIA, CT_VIDEO_X_M4V}},
{"mjs", {FT_DOCUMENT, CT_TEXT_JAVASCRIPT}},
{"mka", {FT_AUDIO, CT_AUDIO_MATROSKA}},
{"mkv", {FT_MEDIA, CT_VIDEO_MATROSKA}},
{"mng", {FT_MEDIA, CT_IMAGE_X_MNG}},
{"mov", {FT_MEDIA, CT_VIDEO_QUICKTIME}},
{"mp1", {FT_AUDIO, CT_AUDIO_MPEG}},
{"mp2", {FT_AUDIO, CT_AUDIO_MPEG}},
{"mp3", {FT_AUDIO, CT_AUDIO_MPEG}},
{"mp4", {FT_MEDIA, CT_VIDEO_MP4}},
{"mpa", {FT_AUDIO, CT_AUDIO_MPEG}},
{"mpe", {FT_MEDIA, CT_VIDEO_MPEG}},
{"mpeg", {FT_MEDIA, CT_VIDEO_MPEG}},
{"mpg", {FT_MEDIA, CT_VIDEO_MPEG}},
{"mpv", {FT_MEDIA, CT_VIDEO_MPEG}},
{"oga", {FT_AUDIO, CT_AUDIO_OGG}},
{"ogg", {FT_AUDIO, CT_AUDIO_OGG}},
{"ogv", {FT_MEDIA, CT_VIDEO_OGG}},
{"otf", {FT_DOCUMENT, CT_APPLICATION_X_FONT_OPENTYPE}},
{"pdf", {FT_DOCUMENT, CT_APPLICATION_PDF}},
{"php", {FT_DOCUMENT, CT_APPLICATION_X_HTTPD_PHP}},
{"png", {FT_IMAGE, CT_IMAGE_PNG}},
{"rar", {FT_ARCHIVE, CT_APPLICATION_VND_RAR}},
{"svg", {FT_IMAGE, CT_IMAGE_SVG_XML}},
{"tar", {FT_ARCHIVE, CT_APPLICATION_X_TAR}},
{"targa", {FT_IMAGE, CT_IMAGE_X_TGA}},
{"tif", {FT_IMAGE, CT_IMAGE_TIFF}},
{"tiff", {FT_IMAGE, CT_IMAGE_TIFF}},
{"tga", {FT_IMAGE, CT_IMAGE_X_TGA}},
{"tgz", {FT_ARCHIVE, CT_APPLICATION_X_TGZ}},
{"ts", {FT_MEDIA, CT_VIDEO_MPEG2TS}},
{"tta", {FT_AUDIO, CT_AUDIO_X_TTA}},
{"ttf", {FT_DOCUMENT, CT_APPLICATION_X_FONT_TRUETYPE}},
{"txt", {FT_DOCUMENT, CT_TEXT_PLAIN}},
{"w64", {FT_AUDIO, CT_AUDIO_WAVE}},
{"wav", {FT_AUDIO, CT_AUDIO_WAVE}},
{"wave", {FT_AUDIO, CT_AUDIO_WAVE}},
{"wasm", {FT_DOCUMENT, CT_APPLICATION_WASM}},
{"weba", {FT_AUDIO, CT_AUDIO_WEBM}},
{"webm", {FT_MEDIA, CT_VIDEO_WEBM}},
{"webp", {FT_IMAGE, CT_IMAGE_WEBP}},
{"wma", {FT_AUDIO, CT_AUDIO_X_MS_WMA}},
{"woff", {FT_DOCUMENT, CT_APPLICATION_FONT_WOFF}},
{"woff2", {FT_DOCUMENT, CT_APPLICATION_FONT_WOFF2}},
{"wv", {FT_AUDIO, CT_AUDIO_X_WAVPACK}},
{"xht", {FT_DOCUMENT, CT_APPLICATION_XHTML}},
{"xhtml", {FT_DOCUMENT, CT_APPLICATION_XHTML}},
{"xml", {FT_DOCUMENT, CT_APPLICATION_XML}},
{"xsl", {FT_DOCUMENT, CT_TEXT_XSL}},
{"xz", {FT_ARCHIVE, CT_APPLICATION_X_XZ}},
{"zip", {FT_ARCHIVE, CT_APPLICATION_ZIP}},
{"7z", {FT_ARCHIVE, CT_APPLICATION_X_7Z}},
};
const std::string_view &statusCodeToString(int code)
{
switch (code)
{
case 100:
{
static std::string_view sv = "Continue";
return sv;
}
case 101:
{
static std::string_view sv = "Switching Protocols";
return sv;
}
case 102:
{
static std::string_view sv = "Processing";
return sv;
}
case 103:
{
static std::string_view sv = "Early Hints";
return sv;
}
case 200:
{
static std::string_view sv = "OK";
return sv;
}
case 201:
{
static std::string_view sv = "Created";
return sv;
}
case 202:
{
static std::string_view sv = "Accepted";
return sv;
}
case 203:
{
static std::string_view sv = "Non-Authoritative Information";
return sv;
}
case 204:
{
static std::string_view sv = "No Content";
return sv;
}
case 205:
{
static std::string_view sv = "Reset Content";
return sv;
}
case 206:
{
static std::string_view sv = "Partial Content";
return sv;
}
case 207:
{
static std::string_view sv = "Multi-Status";
return sv;
}
case 208:
{
static std::string_view sv = "Already Reported";
return sv;
}
case 226:
{
static std::string_view sv = "IM Used";
return sv;
}
case 300:
{
static std::string_view sv = "Multiple Choices";
return sv;
}
case 301:
{
static std::string_view sv = "Moved Permanently";
return sv;
}
case 302:
{
static std::string_view sv = "Found";
return sv;
}
case 303:
{
static std::string_view sv = "See Other";
return sv;
}
case 304:
{
static std::string_view sv = "Not Modified";
return sv;
}
case 305:
{
static std::string_view sv = "Use Proxy";
return sv;
}
case 306:
{
static std::string_view sv = "(Unused)";
return sv;
}
case 307:
{
static std::string_view sv = "Temporary Redirect";
return sv;
}
case 308:
{
static std::string_view sv = "Permanent Redirect";
return sv;
}
case 400:
{
static std::string_view sv = "Bad Request";
return sv;
}
case 401:
{
static std::string_view sv = "Unauthorized";
return sv;
}
case 402:
{
static std::string_view sv = "Payment Required";
return sv;
}
case 403:
{
static std::string_view sv = "Forbidden";
return sv;
}
case 404:
{
static std::string_view sv = "Not Found";
return sv;
}
case 405:
{
static std::string_view sv = "Method Not Allowed";
return sv;
}
case 406:
{
static std::string_view sv = "Not Acceptable";
return sv;
}
case 407:
{
static std::string_view sv = "Proxy Authentication Required";
return sv;
}
case 408:
{
static std::string_view sv = "Request Time-out";
return sv;
}
case 409:
{
static std::string_view sv = "Conflict";
return sv;
}
case 410:
{
static std::string_view sv = "Gone";
return sv;
}
case 411:
{
static std::string_view sv = "Length Required";
return sv;
}
case 412:
{
static std::string_view sv = "Precondition Failed";
return sv;
}
case 413:
{
static std::string_view sv = "Request Entity Too Large";
return sv;
}
case 414:
{
static std::string_view sv = "Request-URI Too Large";
return sv;
}
case 415:
{
static std::string_view sv = "Unsupported Media Type";
return sv;
}
case 416:
{
static std::string_view sv = "Requested Range Not Satisfiable";
return sv;
}
case 417:
{
static std::string_view sv = "Expectation Failed";
return sv;
}
case 418:
{
static std::string_view sv = "I'm a Teapot";
return sv;
}
case 421:
{
static std::string_view sv = "Misdirected Request";
return sv;
}
case 422:
{
static std::string_view sv = "Unprocessable Entity";
return sv;
}
case 423:
{
static std::string_view sv = "Locked";
return sv;
}
case 424:
{
static std::string_view sv = "Failed Dependency";
return sv;
}
case 425:
{
static std::string_view sv = "Too Early";
return sv;
}
case 426:
{
static std::string_view sv = "Upgrade Required";
return sv;
}
case 428:
{
static std::string_view sv = "Precondition Required";
return sv;
}
case 429:
{
static std::string_view sv = "Too Many Requests";
return sv;
}
case 431:
{
static std::string_view sv = "Request Header Fields Too Large";
return sv;
}
case 451:
{
static std::string_view sv = "Unavailable For Legal Reasons";
return sv;
}
case 500:
{
static std::string_view sv = "Internal Server Error";
return sv;
}
case 501:
{
static std::string_view sv = "Not Implemented";
return sv;
}
case 502:
{
static std::string_view sv = "Bad Gateway";
return sv;
}
case 503:
{
static std::string_view sv = "Service Unavailable";
return sv;
}
case 504:
{
static std::string_view sv = "Gateway Time-out";
return sv;
}
case 505:
{
static std::string_view sv = "HTTP Version Not Supported";
return sv;
}
case 506:
{
static std::string_view sv = "Variant Also Negotiates";
return sv;
}
case 507:
{
static std::string_view sv = "Insufficient Storage";
return sv;
}
case 508:
{
static std::string_view sv = "Loop Detected";
return sv;
}
case 510:
{
static std::string_view sv = "Not Extended";
return sv;
}
case 511:
{
static std::string_view sv = "Network Authentication Required";
return sv;
}
default:
if (code >= 100 && code < 200)
{
static std::string_view sv = "Informational";
return sv;
}
else if (code >= 200 && code < 300)
{
static std::string_view sv = "Successful";
return sv;
}
else if (code >= 300 && code < 400)
{
static std::string_view sv = "Redirection";
return sv;
}
else if (code >= 400 && code < 500)
{
static std::string_view sv = "Bad Request";
return sv;
}
else if (code >= 500 && code < 600)
{
static std::string_view sv = "Server Error";
return sv;
}
else
{
static std::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(),
[](unsigned char c) { return tolower(c); });
}
auto it = fileTypeDatabase_.find(extName);
return (it == fileTypeDatabase_.end()) ? CT_APPLICATION_OCTET_STREAM
: it->second.second;
}
ContentType parseContentType(const std::string_view &contentType)
{
// Generate map from database for faster query
static std::unordered_map<std::string_view, ContentType> contentTypeMap_;
// Thread safe initialization
static std::once_flag flag;
std::call_once(flag, []() {
for (const auto &e : mimeTypeDatabase_)
{
for (const auto &type : e.second.first)
contentTypeMap_[type] = e.first;
}
});
auto ext = contentType.find(';');
if (ext != std::string_view::npos)
return parseContentType(contentType.substr(0, ext));
if (contentType == "application/x-www-form-urlencoded")
return CT_APPLICATION_X_FORM;
if (contentType == "multipart/form-data")
return CT_MULTIPART_FORM_DATA;
auto it = contentTypeMap_.find(contentType);
return (it == contentTypeMap_.end()) ? CT_CUSTOM : it->second;
}
FileType parseFileType(const std::string_view &fileExtension)
{
std::string extName(fileExtension);
transform(extName.begin(),
extName.end(),
extName.begin(),
[](unsigned char c) { return tolower(c); });
auto it = fileTypeDatabase_.find(extName);
return (it == fileTypeDatabase_.end()) ? FT_CUSTOM : it->second.first;
}
FileType getFileType(ContentType contentType)
{
// Generate map from database for faster query
static std::unordered_map<ContentType, FileType> fileTypeMap_;
// Thread safe initialization
static std::once_flag flag;
std::call_once(flag, []() {
for (const auto &e : fileTypeDatabase_)
fileTypeMap_[e.second.second] = e.second.first;
fileTypeMap_[CT_NONE] = FT_UNKNOWN;
fileTypeMap_[CT_CUSTOM] = FT_CUSTOM;
});
auto it = fileTypeMap_.find(contentType);
return (it == fileTypeMap_.end()) ? FT_UNKNOWN : it->second;
}
const std::string_view &contentTypeToMime(ContentType contentType)
{
auto it = mimeTypeDatabase_.find(contentType);
return (it == mimeTypeDatabase_.end())
? mimeTypeDatabase_.at(CT_APPLICATION_OCTET_STREAM).first.front()
: (it->second.second.empty() ? it->second.first.front()
: it->second.second);
}
void registerCustomExtensionMime(const std::string &ext,
const std::string &mime)
{
if (ext.empty())
return;
auto &mimeStr = customMime[ext];
if (!mimeStr.empty())
{
LOG_WARN << ext << " has already been registered as type " << mime
<< ". Overwriting.";
}
mimeStr = mime;
}
const std::string_view fileNameToMime(const std::string &fileName)
{
ContentType internalContentType = getContentType(fileName);
if (internalContentType != CT_APPLICATION_OCTET_STREAM)
return contentTypeToMime(internalContentType);
std::string extName;
auto pos = fileName.rfind('.');
if (pos != std::string::npos)
{
extName = fileName.substr(pos + 1);
transform(extName.begin(),
extName.end(),
extName.begin(),
[](unsigned char c) { return tolower(c); });
}
auto it = customMime.find(extName);
if (it == customMime.end())
return "";
return it->second;
}
std::pair<ContentType, const std::string_view> fileNameToContentTypeAndMime(
const std::string &fileName)
{
ContentType internalContentType = getContentType(fileName);
if (internalContentType != CT_APPLICATION_OCTET_STREAM)
return {internalContentType, contentTypeToMime(internalContentType)};
std::string extName;
auto pos = fileName.rfind('.');
if (pos != std::string::npos)
{
extName = fileName.substr(pos + 1);
transform(extName.begin(),
extName.end(),
extName.begin(),
[](unsigned char c) { return tolower(c); });
}
auto it = customMime.find(extName);
if (it == customMime.end())
return {CT_NONE, ""};
return {CT_CUSTOM, it->second};
}
const std::vector<std::string_view> &getFileExtensions(ContentType contentType)
{
// Generate map from database for faster query
static std::unordered_map<ContentType, std::vector<std::string_view>>
extensionMap_;
static std::vector<std::string_view> notFound_;
// Thread safe initialization
static std::once_flag flag;
std::call_once(flag, []() {
for (const auto &e : fileTypeDatabase_)
if (!e.first.empty())
extensionMap_[e.second.second].push_back(e.first);
// Add deprecated
extensionMap_[CT_APPLICATION_X_JAVASCRIPT] =
extensionMap_[CT_TEXT_JAVASCRIPT];
extensionMap_[CT_TEXT_XML] = extensionMap_[CT_APPLICATION_XML];
});
auto it = extensionMap_.find(contentType);
if (it == extensionMap_.end())
return notFound_;
return it->second;
}
} // namespace drogon
+87
View File
@@ -0,0 +1,87 @@
/**
*
* @file HttpUtils.h
* @author 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/MsgBuffer.h>
#include <drogon/HttpTypes.h>
#include <string>
#include <string_view>
namespace drogon
{
const std::string_view &contentTypeToMime(ContentType contentType);
const std::string_view &statusCodeToString(int code);
ContentType getContentType(const std::string &fileName);
ContentType parseContentType(const std::string_view &contentType);
FileType parseFileType(const std::string_view &fileExtension);
FileType getFileType(ContentType contentType);
void registerCustomExtensionMime(const std::string &ext,
const std::string &mime);
const std::string_view fileNameToMime(const std::string &fileName);
std::pair<ContentType, const std::string_view> fileNameToContentTypeAndMime(
const std::string &filename);
inline std::string_view getFileExtension(const std::string &fileName)
{
auto pos = fileName.rfind('.');
if (pos == std::string::npos)
return "";
return std::string_view(&fileName[pos + 1], fileName.length() - pos - 1);
}
const std::vector<std::string_view> &getFileExtensions(ContentType contentType);
inline const std::vector<std::string_view> &getFileExtensions(
const std::string_view &contentType)
{
return getFileExtensions(parseContentType(contentType));
}
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
+47
View File
@@ -0,0 +1,47 @@
/**
*
* HttpViewData.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 <drogon/HttpViewData.h>
using namespace drogon;
std::string HttpViewData::htmlTranslate(const char *str, size_t length)
{
std::string ret;
ret.reserve(length + 64);
auto end = str + length;
while (str != end)
{
switch (*str)
{
case '"':
ret.append("&quot;", 6);
break;
case '&':
ret.append("&amp;", 5);
break;
case '<':
ret.append("&lt;", 4);
break;
case '>':
ret.append("&gt;", 4);
break;
default:
ret.push_back(*str);
break;
}
++str;
}
return ret;
}
+30
View File
@@ -0,0 +1,30 @@
/**
*
* IntranetIpFilter.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 "HttpResponseImpl.h"
#include <drogon/IntranetIpFilter.h>
using namespace drogon;
void IntranetIpFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if (req->peerAddr().isIntranetIp())
{
fccb();
return;
}
auto res = drogon::HttpResponse::newNotFoundResponse(req);
fcb(res);
}
+27
View File
@@ -0,0 +1,27 @@
#include "JsonConfigAdapter.h"
#include <fstream>
#include <mutex>
using namespace drogon;
Json::Value JsonConfigAdapter::getJson(const std::string &content) const
noexcept(false)
{
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());
Json::Value root;
if (!reader->parse(
content.c_str(), content.c_str() + content.size(), &root, &errs))
{
throw std::runtime_error(errs);
}
return root;
}
std::vector<std::string> JsonConfigAdapter::getExtensions() const
{
return {"json"};
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "ConfigAdapter.h"
namespace drogon
{
class JsonConfigAdapter : public ConfigAdapter
{
public:
JsonConfigAdapter() = default;
~JsonConfigAdapter() override = default;
Json::Value getJson(const std::string &content) const
noexcept(false) override;
std::vector<std::string> getExtensions() const override;
};
} // namespace drogon
+228
View File
@@ -0,0 +1,228 @@
/**
*
* @file ListenerManager.cc
* @author 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 "ListenerManager.h"
#include <drogon/config.h>
#include <fcntl.h>
#include <trantor/utils/Logger.h>
#include "HttpAppFrameworkImpl.h"
#include "HttpServer.h"
#ifndef _WIN32
#include <sys/file.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, 0666);
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,
bool useOldTLS,
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
{
if (useSSL && !utils::supportsTls())
LOG_ERROR << "Can't use SSL without OpenSSL found in your system";
listeners_.emplace_back(
ip, port, useSSL, certFile, keyFile, useOldTLS, sslConfCmds);
}
std::vector<trantor::InetAddress> ListenerManager::getListeners() const
{
std::vector<trantor::InetAddress> listeners;
for (auto &server : servers_)
{
listeners.emplace_back(server->address());
}
return listeners;
}
void ListenerManager::createListeners(
const std::string &globalCertFile,
const std::string &globalKeyFile,
const std::vector<std::pair<std::string, std::string>> &sslConfCmds,
const std::vector<trantor::EventLoop *> &ioLoops)
{
LOG_TRACE << "thread num=" << ioLoops.size();
#ifdef __linux__
for (size_t i = 0; i < ioLoops.size(); ++i)
{
for (auto const &listener : listeners_)
{
auto const &ip = listener.ip_;
bool isIpv6 = (ip.find(':') != std::string::npos);
InetAddress listenAddress(ip, listener.port_, isIpv6);
if (listenAddress.isUnspecified())
{
LOG_FATAL << "Failed to parse IP address '" << ip
<< "'. (Note: FQDN/domain names/hostnames are not "
"supported. Including 'localhost')";
abort();
}
if (i == 0 && !app().reusePort())
{
DrogonFileLocker lock;
// Check whether the port is in use.
TcpServer server(HttpAppFrameworkImpl::instance().getLoop(),
listenAddress,
"drogonPortTest",
true,
false);
}
std::shared_ptr<HttpServer> serverPtr =
std::make_shared<HttpServer>(ioLoops[i],
listenAddress,
"drogon");
if (beforeListenSetSockOptCallback_)
{
serverPtr->setBeforeListenSockOptCallback(
beforeListenSetSockOptCallback_);
}
if (afterAcceptSetSockOptCallback_)
{
serverPtr->setAfterAcceptSockOptCallback(
afterAcceptSetSockOptCallback_);
}
if (connectionCallback_)
{
serverPtr->setConnectionCallback(connectionCallback_);
}
if (listener.useSSL_ && utils::supportsTls())
{
auto cert = listener.certFile_;
auto key = listener.keyFile_;
if (cert.empty())
cert = globalCertFile;
if (key.empty())
key = globalKeyFile;
if (cert.empty() || key.empty())
{
std::cerr
<< "You can't use https without cert file or key file"
<< std::endl;
exit(1);
}
auto cmds = sslConfCmds;
std::copy(listener.sslConfCmds_.begin(),
listener.sslConfCmds_.end(),
std::back_inserter(cmds));
auto policy =
trantor::TLSPolicy::defaultServerPolicy(cert, key);
policy->setConfCmds(cmds).setUseOldTLS(listener.useOldTLS_);
serverPtr->enableSSL(std::move(policy));
}
servers_.push_back(serverPtr);
}
}
#else
if (!listeners_.empty())
{
listeningThread_ =
std::make_unique<EventLoopThread>("DrogonListeningLoop");
listeningThread_->run();
for (auto const &listener : listeners_)
{
auto ip = listener.ip_;
bool isIpv6 = (ip.find(':') != std::string::npos);
auto serverPtr = std::make_shared<HttpServer>(
listeningThread_->getLoop(),
InetAddress(ip, listener.port_, isIpv6),
"drogon");
if (listener.useSSL_ && utils::supportsTls())
{
auto cert = listener.certFile_;
auto key = listener.keyFile_;
if (cert.empty())
cert = globalCertFile;
if (key.empty())
key = globalKeyFile;
if (cert.empty() || key.empty())
{
std::cerr
<< "You can't use https without cert file or key file"
<< std::endl;
exit(1);
}
auto cmds = sslConfCmds;
auto policy =
trantor::TLSPolicy::defaultServerPolicy(cert, key);
policy->setConfCmds(cmds).setUseOldTLS(listener.useOldTLS_);
serverPtr->enableSSL(std::move(policy));
}
serverPtr->setIoLoops(ioLoops);
servers_.push_back(serverPtr);
}
}
#endif
}
void ListenerManager::startListening()
{
for (auto &server : servers_)
{
server->start();
}
}
void ListenerManager::stopListening()
{
for (auto &serverPtr : servers_)
{
serverPtr->stop();
}
if (listeningThread_)
{
auto loop = listeningThread_->getLoop();
assert(!loop->isInLoopThread());
loop->quit();
listeningThread_->wait();
}
}
void ListenerManager::reloadSSLFiles()
{
for (auto &server : servers_)
{
server->reloadSSL();
}
}
+113
View File
@@ -0,0 +1,113 @@
/**
*
* @file ListenerManager.h
* @author 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 <utility>
#include <vector>
#include "impl_forwards.h"
namespace trantor
{
class InetAddress;
}
namespace drogon
{
class ListenerManager : public trantor::NonCopyable
{
public:
~ListenerManager() = default;
void addListener(const std::string &ip,
uint16_t port,
bool useSSL = false,
const std::string &certFile = "",
const std::string &keyFile = "",
bool useOldTLS = false,
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds = {});
std::vector<trantor::InetAddress> getListeners() const;
void createListeners(
const std::string &globalCertFile,
const std::string &globalKeyFile,
const std::vector<std::pair<std::string, std::string>> &sslConfCmds,
const std::vector<trantor::EventLoop *> &ioLoops);
void startListening();
void stopListening();
void setBeforeListenSockOptCallback(std::function<void(int)> cb)
{
beforeListenSetSockOptCallback_ = std::move(cb);
}
void setAfterAcceptSockOptCallback(std::function<void(int)> cb)
{
afterAcceptSetSockOptCallback_ = std::move(cb);
}
void setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb)
{
connectionCallback_ = std::move(cb);
}
void reloadSSLFiles();
private:
struct ListenerInfo
{
ListenerInfo(
std::string ip,
uint16_t port,
bool useSSL,
std::string certFile,
std::string keyFile,
bool useOldTLS,
std::vector<std::pair<std::string, std::string>> sslConfCmds)
: ip_(std::move(ip)),
port_(port),
useSSL_(useSSL),
certFile_(std::move(certFile)),
keyFile_(std::move(keyFile)),
useOldTLS_(useOldTLS),
sslConfCmds_(std::move(sslConfCmds))
{
}
std::string ip_;
uint16_t port_;
bool useSSL_;
std::string certFile_;
std::string keyFile_;
bool useOldTLS_;
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
};
std::vector<ListenerInfo> listeners_;
std::vector<std::shared_ptr<HttpServer>> servers_;
// should have value when and only when on OS that one port can only be
// listened by one thread
std::unique_ptr<trantor::EventLoopThread> listeningThread_;
std::function<void(int)> beforeListenSetSockOptCallback_;
std::function<void(int)> afterAcceptSetSockOptCallback_;
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
};
} // namespace drogon
+30
View File
@@ -0,0 +1,30 @@
/**
*
* LocalHostFilter.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 "HttpResponseImpl.h"
#include <drogon/LocalHostFilter.h>
using namespace drogon;
void LocalHostFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if (req->peerAddr().isLoopbackIp())
{
fccb();
return;
}
auto res = drogon::HttpResponse::newNotFoundResponse(req);
fcb(res);
}
+185
View File
@@ -0,0 +1,185 @@
/**
*
* @file MiddlewaresFunction.cc
* @author 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 "MiddlewaresFunction.h"
#include "HttpRequestImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/HttpMiddleware.h>
#include <queue>
namespace drogon
{
namespace middlewares_function
{
static void doFilterChains(
const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr)
{
if (index < filters.size())
{
auto &filter = filters[index];
filter->doFilter(
req,
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
(*callbackPtr)(resp);
},
[index, req, callbackPtr, &filters]() mutable {
auto ioLoop = req->getLoop();
if (ioLoop && !ioLoop->isInLoopThread())
{
ioLoop->queueInLoop(
[&filters,
index,
req,
callbackPtr = std::move(callbackPtr)]() mutable {
doFilterChains(filters,
index + 1,
req,
std::move(callbackPtr));
});
}
else
{
doFilterChains(filters,
index + 1,
req,
std::move(callbackPtr));
}
});
}
else
{
(*callbackPtr)(nullptr);
}
}
void doFilters(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doFilterChains(filters, 0, req, std::move(callbackPtr));
}
/**
* @brief
* The middlewares are invoked according to the onion ring model.
*
* @param outerCallback The road back to the outer layer of the onion ring.
* @param innermostHandler The innermost handler at the core of the onion ring.
*
* When going through each middleware, the `innermostHandler` is passed down as
* is, while the `outerCallback` is passed to the user code. User code wraps the
* outerCallback along with other post processing codes into `userPostCb`, and
* passes it to the next middleware.
*
* When reaching the onion core, the `innermostHandler` is finally called. It's
* parameter is a function that wraps the original `outerCallback` and all
* `userPostCb`s.
*/
static void passMiddlewareChains(
const std::vector<std::shared_ptr<HttpMiddlewareBase>> &middlewares,
size_t index,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&outerCallback,
std::function<void(std::function<void(const HttpResponsePtr &)> &&)>
&&innermostHandler)
{
if (index < middlewares.size())
{
auto &middleware = middlewares[index];
middleware->invoke(
req,
[index,
req,
innermostHandler = std::move(innermostHandler),
&middlewares](std::function<void(const HttpResponsePtr &)>
&&userPostCb) mutable {
// call next middleware
auto ioLoop = req->getLoop();
if (ioLoop && !ioLoop->isInLoopThread())
{
ioLoop->queueInLoop(
[&middlewares,
index,
req,
innermostHandler = std::move(innermostHandler),
userPostCb = std::move(userPostCb)]() mutable {
passMiddlewareChains(middlewares,
index + 1,
req,
std::move(userPostCb),
std::move(innermostHandler)
);
});
}
else
{
passMiddlewareChains(middlewares,
index + 1,
req,
std::move(userPostCb),
std::move(innermostHandler));
}
},
std::move(outerCallback));
}
else
{
innermostHandler(std::move(outerCallback));
}
}
std::vector<std::shared_ptr<HttpMiddlewareBase>> createMiddlewares(
const std::vector<std::string> &middlewareNames)
{
std::vector<std::shared_ptr<HttpMiddlewareBase>> middlewares;
for (const auto &name : middlewareNames)
{
auto object_ = DrClassMap::getSingleInstance(name);
if (auto middleware =
std::dynamic_pointer_cast<HttpMiddlewareBase>(object_))
{
middlewares.push_back(middleware);
}
else
{
LOG_ERROR << "middleware " << name << " not found";
}
}
return middlewares;
}
void passMiddlewares(
const std::vector<std::shared_ptr<HttpMiddlewareBase>> &middlewares,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&outermostCallback,
std::function<void(std::function<void(const HttpResponsePtr &)> &&)>
&&innermostHandler)
{
passMiddlewareChains(middlewares,
0,
req,
std::move(outermostCallback),
std::move(innermostHandler));
}
} // namespace middlewares_function
} // namespace drogon
+44
View File
@@ -0,0 +1,44 @@
/**
*
* MiddlewaresFunction.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 <memory>
#include <string>
#include <vector>
namespace drogon
{
namespace middlewares_function
{
// We can not remove old filters api. GlobalFilter still needs it.
// GlobalFilter run filters in advice chains, which does not expose the outer
// response handler, so HttpMiddleware is not suitable for it.
void doFilters(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
std::vector<std::shared_ptr<HttpMiddlewareBase>> createMiddlewares(
const std::vector<std::string> &middlewareNames);
void passMiddlewares(
const std::vector<std::shared_ptr<HttpMiddlewareBase>> &middlewares,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&outermostCallback,
std::function<void(std::function<void(const HttpResponsePtr &)> &&)>
&&innermostHandler);
} // namespace middlewares_function
} // namespace drogon
+259
View File
@@ -0,0 +1,259 @@
/**
*
* @file MultiPart.cc
* @author 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 "HttpRequestImpl.h"
#include "HttpUtils.h"
#include "HttpAppFrameworkImpl.h"
#include "HttpFileImpl.h"
#include <drogon/MultiPart.h>
#include <drogon/utils/Utilities.h>
#include <drogon/config.h>
#include <algorithm>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <sys/stat.h>
#ifndef _WIN32
#include <unistd.h>
#endif
using namespace drogon;
const std::vector<HttpFile> &MultiPartParser::getFiles() const
{
return files_;
}
std::unordered_map<std::string, HttpFile> MultiPartParser::getFilesMap() const
{
std::unordered_map<std::string, HttpFile> result;
for (auto &file : files_)
{
result.emplace(file.getItemName(), file);
}
return result;
}
const SafeStringMap<std::string> &MultiPartParser::getParameters() const
{
return parameters_;
}
int MultiPartParser::parse(const HttpRequestPtr &req)
{
switch (req->method())
{
case Post:
case Put:
case Patch:
break;
default:
return -1;
}
const std::string &contentType =
static_cast<HttpRequestImpl *>(req.get())->getHeaderBy("content-type");
if (contentType.empty())
{
return -1;
}
std::string::size_type pos = contentType.find(';');
if (pos == std::string::npos)
return -1;
std::string type = contentType.substr(0, pos);
std::transform(type.begin(), type.end(), type.begin(), [](unsigned char c) {
return tolower(c);
});
if (type != "multipart/form-data")
return -1;
pos = contentType.find("boundary=");
if (pos == std::string::npos)
return -1;
auto pos2 = contentType.find(';', pos);
if (pos2 == std::string::npos)
pos2 = contentType.size();
return parse(req, contentType.data() + (pos + 9), pos2 - (pos + 9));
}
static std::pair<std::string_view, std::string_view> parseLine(
const char *begin,
const char *end)
{
auto p = begin;
while (p != end)
{
if (*p == ':')
{
if (p + 1 != end && *(p + 1) == ' ')
{
return std::make_pair(std::string_view(begin, p - begin),
std::string_view(p + 2, end - p - 2));
}
else
{
return std::make_pair(std::string_view(begin, p - begin),
std::string_view(p + 1, end - p - 1));
}
}
++p;
}
return std::make_pair(std::string_view(), std::string_view());
}
int MultiPartParser::parseEntity(const HttpRequestPtr &req,
const char *begin,
const char *end)
{
static const char entityName[] = "name=";
static const char fileName[] = "filename=";
static const char CRLF[] = "\r\n\r\n";
auto headEnd = std::search(begin, end, CRLF, CRLF + 4);
if (headEnd == end)
{
return -1;
}
headEnd += 2;
auto pos = begin;
std::shared_ptr<HttpFileImpl> filePtr = std::make_shared<HttpFileImpl>();
while (pos != headEnd)
{
auto lineEnd = std::search(pos, headEnd, CRLF, CRLF + 2);
auto keyAndValue = parseLine(pos, lineEnd);
if (keyAndValue.first.empty() || keyAndValue.second.empty())
{
return -1;
}
pos = lineEnd + 2;
std::string key{keyAndValue.first.data(), keyAndValue.first.size()};
std::transform(key.begin(),
key.end(),
key.begin(),
[](unsigned char c) { return tolower(c); });
if (key == "content-disposition")
{
auto value = keyAndValue.second;
auto valueEnd = value.data() + value.length();
auto namePos =
std::search(value.data(), valueEnd, entityName, entityName + 5);
if (namePos == valueEnd)
{
return -1;
}
namePos += 5;
const char *nameEnd;
if (*namePos == '"')
{
++namePos;
nameEnd = std::find(namePos, valueEnd, '"');
}
else
{
nameEnd = std::find(namePos, valueEnd, ';');
}
std::string name(namePos, nameEnd);
auto fileNamePos =
std::search(nameEnd, valueEnd, fileName, fileName + 9);
if (fileNamePos == valueEnd)
{
parameters_.emplace(name, std::string(headEnd + 2, end));
return 0;
}
else
{
fileNamePos += 9;
const char *fileNameEnd;
if (*fileNamePos == '"')
{
++fileNamePos;
fileNameEnd = std::find(fileNamePos, valueEnd, '"');
}
else
{
fileNameEnd = std::find(fileNamePos, valueEnd, ';');
}
std::string fName{fileNamePos, fileNameEnd};
filePtr->setRequest(req);
filePtr->setItemName(std::move(name));
filePtr->setFileName(std::move(fName));
filePtr->setFile(headEnd + 2,
static_cast<size_t>(end - headEnd - 2));
}
}
else if (key == "content-type")
{
auto value = keyAndValue.second;
auto semiColonPos =
std::find(value.data(), value.data() + value.length(), ';');
std::string_view contentType(value.data(),
semiColonPos - value.data());
filePtr->setContentType(parseContentType(contentType));
}
else if (key == "content-transfer-encoding")
{
auto value = keyAndValue.second;
auto semiColonPos =
std::find(value.data(), value.data() + value.length(), ';');
filePtr->setContentTransferEncoding(
std::string{value.data(), semiColonPos});
}
}
if (!filePtr->getFileName().empty())
{
files_.emplace_back(std::move(filePtr));
return 0;
}
else
{
return -1;
}
}
int MultiPartParser::parse(const HttpRequestPtr &req,
const char *boundaryData,
size_t boundaryLen)
{
std::string_view boundary{boundaryData, boundaryLen};
if (boundary.size() > 2 && boundary[0] == '\"')
boundary = boundary.substr(1, boundary.size() - 2);
std::string_view::size_type pos1, pos2;
pos1 = 0;
auto content = static_cast<HttpRequestImpl *>(req.get())->bodyView();
pos2 = content.find(boundary);
while (true)
{
pos1 = pos2;
if (pos1 == std::string_view::npos)
break;
pos1 += boundary.length();
if (content[pos1] == '\r' && content[pos1 + 1] == '\n')
pos1 += 2;
pos2 = content.find(boundary, pos1);
if (pos2 == std::string_view::npos)
break;
bool flag = false;
if (content[pos2 - 4] == '\r' && content[pos2 - 3] == '\n' &&
content[pos2 - 2] == '-' && content[pos2 - 1] == '-')
{
pos2 -= 4;
flag = true;
}
if (parseEntity(req, content.data() + pos1, content.data() + pos2) != 0)
return -1;
if (flag)
pos2 += 4;
}
return 0;
}
+356
View File
@@ -0,0 +1,356 @@
/**
*
* @file MultipartStreamParser.h
* @author Nitromelon
*
* Copyright 2024, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "MultipartStreamParser.h"
#include <cassert>
using namespace drogon;
static bool startsWith(const std::string_view &a, const std::string_view &b)
{
if (a.size() < b.size())
{
return false;
}
for (size_t i = 0; i < b.size(); i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}
static bool startsWithIgnoreCase(const std::string_view &a,
const std::string_view &b)
{
if (a.size() < b.size())
{
return false;
}
for (size_t i = 0; i < b.size(); i++)
{
if (::tolower(a[i]) != ::tolower(b[i]))
{
return false;
}
}
return true;
}
MultipartStreamParser::MultipartStreamParser(const std::string &contentType)
{
static const std::string_view multipart = "multipart/form-data";
static const std::string_view boundaryEq = "boundary=";
if (!startsWithIgnoreCase(contentType, multipart))
{
isValid_ = false;
return;
}
auto pos = contentType.find(boundaryEq, multipart.size());
if (pos == std::string::npos)
{
isValid_ = false;
return;
}
pos += boundaryEq.size();
size_t pos2;
if (contentType[pos] == '"')
{
++pos;
pos2 = contentType.find('"', pos);
}
else
{
pos2 = contentType.find(';', pos);
}
if (pos2 == std::string::npos)
pos2 = contentType.size();
boundary_ = contentType.substr(pos, pos2 - pos);
dashBoundaryCrlf_ = dash_ + boundary_ + crlf_;
crlfDashBoundary_ = crlf_ + dash_ + boundary_;
}
// TODO: same function in HttpRequestParser.cc
static std::pair<std::string_view, std::string_view> parseLine(
const char *begin,
const char *end)
{
auto p = begin;
while (p != end)
{
if (*p == ':')
{
if (p + 1 != end && *(p + 1) == ' ')
{
return std::make_pair(std::string_view(begin, p - begin),
std::string_view(p + 2, end - p - 2));
}
else
{
return std::make_pair(std::string_view(begin, p - begin),
std::string_view(p + 1, end - p - 1));
}
}
++p;
}
return std::make_pair(std::string_view(), std::string_view());
}
void drogon::MultipartStreamParser::parse(
const char *data,
size_t length,
const drogon::RequestStreamReader::MultipartHeaderCallback &headerCb,
const drogon::RequestStreamReader::StreamDataCallback &dataCb)
{
buffer_.append(data, length);
while (buffer_.size() > 0)
{
switch (status_)
{
case Status::kExpectFirstBoundary:
{
if (buffer_.size() < dashBoundaryCrlf_.size())
{
return;
}
std::string_view v = buffer_.view();
auto pos = v.find(dashBoundaryCrlf_);
// ignore everything before the first boundary
if (pos == std::string::npos)
{
buffer_.eraseFront(buffer_.size() -
dashBoundaryCrlf_.size());
return;
}
// found
buffer_.eraseFront(pos + dashBoundaryCrlf_.size());
status_ = Status::kExpectNewEntry;
continue;
}
case Status::kExpectNewEntry:
{
currentHeader_.name.clear();
currentHeader_.filename.clear();
currentHeader_.contentType.clear();
status_ = Status::kExpectHeader;
continue;
}
case Status::kExpectHeader:
{
std::string_view v = buffer_.view();
auto pos = v.find(crlf_);
if (pos == std::string::npos)
{
// same magic number in HttpRequestParser::parseRequest()
if (buffer_.size() > 60 * 1024)
{
isValid_ = false;
}
return; // header incomplete, wait for more data
}
// empty line
if (pos == 0)
{
buffer_.eraseFront(crlf_.size());
status_ = Status::kExpectBody;
headerCb(currentHeader_);
continue;
}
// found header line
auto [keyView, valueView] = parseLine(v.data(), v.data() + pos);
if (keyView.empty() || valueView.empty())
{
// Bad header
isValid_ = false;
return;
}
if (startsWithIgnoreCase(keyView, "content-type"))
{
currentHeader_.contentType = valueView;
}
else if (startsWithIgnoreCase(keyView, "content-disposition"))
{
static const std::string_view nameKey = "name=";
static const std::string_view fileNameKey = "filename=";
// Extract name
auto namePos = valueView.find(nameKey);
if (namePos == std::string::npos)
{
// name absent
isValid_ = false;
return;
}
namePos += nameKey.size();
size_t nameEnd;
if (valueView[namePos] == '"')
{
++namePos;
nameEnd = valueView.find('"', namePos);
}
else
{
nameEnd = valueView.find(';', namePos);
}
if (nameEnd == std::string::npos)
{
// name end not found
isValid_ = false;
return;
}
currentHeader_.name =
valueView.substr(namePos, nameEnd - namePos);
// Extract filename
auto fileNamePos = valueView.find(fileNameKey, nameEnd);
if (fileNamePos != std::string::npos)
{
fileNamePos += fileNameKey.size();
size_t fileNameEnd;
if (valueView[fileNamePos] == '"')
{
++fileNamePos;
fileNameEnd = valueView.find('"', fileNamePos);
}
else
{
fileNameEnd = valueView.find(';', fileNamePos);
}
currentHeader_.filename =
valueView.substr(fileNamePos,
fileNameEnd - fileNamePos);
}
}
// ignore other headers
buffer_.eraseFront(pos + crlf_.size());
continue;
}
case Status::kExpectBody:
{
if (buffer_.size() < crlfDashBoundary_.size())
{
return; // not enough data to check boundary
}
std::string_view v = buffer_.view();
auto pos = v.find(crlfDashBoundary_);
if (pos == std::string::npos)
{
// boundary not found, leave potential partial boundary
size_t len = v.size() - crlfDashBoundary_.size();
if (len > 0)
{
dataCb(v.data(), len);
buffer_.eraseFront(len);
}
return;
}
// found boundary
dataCb(v.data(), pos);
if (pos > 0)
{
dataCb(v.data() + pos, 0); // notify end of file
}
buffer_.eraseFront(pos + crlfDashBoundary_.size());
status_ = Status::kExpectEndOrNewEntry;
continue;
}
case Status::kExpectEndOrNewEntry:
{
std::string_view v = buffer_.view();
// Check new entry
if (v.size() < crlf_.size())
{
return;
}
if (startsWith(v, crlf_))
{
buffer_.eraseFront(crlf_.size());
status_ = Status::kExpectNewEntry;
continue;
}
// Check end
if (v.size() < dash_.size())
{
return;
}
if (startsWith(v, dash_))
{
isFinished_ = true;
buffer_.clear(); // ignore epilogue
return;
}
isValid_ = false;
return;
}
}
}
}
std::string_view MultipartStreamParser::Buffer::view() const
{
return {buffer_.data() + bufHead_, size()};
}
void MultipartStreamParser::Buffer::append(const char *data, size_t length)
{
size_t remainSize = size();
// Move existing data to the front
if (remainSize > 0 && bufHead_ > 0)
{
for (size_t i = 0; i < remainSize; i++)
{
buffer_[i] = buffer_[bufHead_ + i];
}
}
bufHead_ = 0;
bufTail_ = remainSize;
if (remainSize + length > buffer_.size())
{
buffer_.resize(remainSize + length);
}
for (size_t i = 0; i < length; ++i)
{
buffer_[bufTail_ + i] = data[i];
}
bufTail_ += length;
}
size_t MultipartStreamParser::Buffer::size() const
{
return bufTail_ - bufHead_;
}
void MultipartStreamParser::Buffer::eraseFront(size_t length)
{
assert(length <= size());
bufHead_ += length;
}
void MultipartStreamParser::Buffer::clear()
{
buffer_.clear();
bufHead_ = 0;
bufTail_ = 0;
}
+77
View File
@@ -0,0 +1,77 @@
/**
*
* @file MultipartStreamParser.h
* @author Nitromelon
*
* Copyright 2024, Nitromelon. All rights reserved.
* https://github.com/drogonframework/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/exports.h>
#include <drogon/RequestStream.h>
#include <string>
namespace drogon
{
class DROGON_EXPORT MultipartStreamParser
{
public:
MultipartStreamParser(const std::string &contentType);
void parse(const char *data,
size_t length,
const RequestStreamReader::MultipartHeaderCallback &headerCb,
const RequestStreamReader::StreamDataCallback &dataCb);
bool isFinished() const
{
return isFinished_;
}
bool isValid() const
{
return isValid_;
}
private:
const std::string dash_ = "--";
const std::string crlf_ = "\r\n";
std::string boundary_;
std::string dashBoundaryCrlf_;
std::string crlfDashBoundary_;
struct Buffer
{
public:
std::string_view view() const;
void append(const char *data, size_t length);
size_t size() const;
void eraseFront(size_t length);
void clear();
private:
std::string buffer_;
size_t bufHead_{0};
size_t bufTail_{0};
} buffer_;
enum class Status
{
kExpectFirstBoundary = 0,
kExpectNewEntry = 1,
kExpectHeader = 2,
kExpectBody = 3,
kExpectEndOrNewEntry = 4,
} status_{Status::kExpectFirstBoundary};
MultipartHeader currentHeader_;
bool isValid_{true};
bool isFinished_{false};
};
} // namespace drogon
+51
View File
@@ -0,0 +1,51 @@
// this file is generated by program automatically,don't modify it!
/**
*
* NotFound.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 <drogon/NotFound.h>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
using namespace drogon;
std::string NotFound::genText(const HttpViewData &NotFound_view_data)
{
std::stringstream NotFound_tmp_stream;
NotFound_tmp_stream << "<html>\n";
NotFound_tmp_stream << "<head><title>404 Not Found</title></head>\n";
NotFound_tmp_stream << "<body bgcolor=\"white\" text=\"black\">\n";
NotFound_tmp_stream << "<center><h1>404 Not Found</h1></center>\n";
NotFound_tmp_stream << "<hr><center>drogon/";
NotFound_tmp_stream << NotFound_view_data.get<std::string>("version");
NotFound_tmp_stream << "</center>\n";
NotFound_tmp_stream << "</body>\n";
NotFound_tmp_stream << "</html>\n";
NotFound_tmp_stream << "<!-- a padding to disable MSIE and Chrome friendly "
"error page -->\n";
NotFound_tmp_stream << "<!-- a padding to disable MSIE and Chrome friendly "
"error page -->\n";
NotFound_tmp_stream << "<!-- a padding to disable MSIE and Chrome friendly "
"error page -->\n";
NotFound_tmp_stream << "<!-- a padding to disable MSIE and Chrome friendly "
"error page -->\n";
NotFound_tmp_stream << "<!-- a padding to disable MSIE and Chrome friendly "
"error page -->\n";
NotFound_tmp_stream << "<!-- a padding to disable MSIE and Chrome friendly "
"error page -->\n";
return NotFound_tmp_stream.str();
}
+121
View File
@@ -0,0 +1,121 @@
/**
*
* PluginsManager.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 "PluginsManager.h"
#include <trantor/utils/Logger.h>
using namespace drogon;
PluginsManager::~PluginsManager()
{
// Shut down all plugins in reverse order of initialization.
for (auto iter = initializedPlugins_.rbegin();
iter != initializedPlugins_.rend();
iter++)
{
(*iter)->shutdown();
}
}
void PluginsManager::initializeAllPlugins(
const Json::Value &configs,
const std::function<void(PluginBase *)> &forEachCallback)
{
assert(configs.isArray());
std::vector<PluginBase *> plugins;
for (auto &config : configs)
{
auto name = config.get("name", "").asString();
if (name.empty())
continue;
createPlugin(name);
}
for (auto &config : configs)
{
auto name = config.get("name", "").asString();
if (name.empty())
continue;
auto pluginPtr = getPlugin(name);
if (!pluginPtr)
{
continue;
}
auto configuration = config["config"];
auto dependencies = config["dependencies"];
pluginPtr->setConfig(configuration);
assert(dependencies.isArray() || dependencies.isNull());
if (dependencies.isArray())
{
// Is not null and is an array
for (auto &depName : dependencies)
{
auto *dp = getPlugin(depName.asString());
if (dp)
{
pluginPtr->addDependency(dp);
}
else
{
LOG_FATAL << "Dependent plugin " << depName.asString()
<< " is not loaded";
abort();
}
}
}
pluginPtr->setInitializedCallback([this](PluginBase *p) {
LOG_TRACE << "Plugin " << p->className() << " initialized!";
initializedPlugins_.push_back(p);
});
plugins.push_back(pluginPtr);
}
// Initialize them, Depth first
for (auto plugin : plugins)
{
plugin->initialize();
forEachCallback(plugin);
}
}
void PluginsManager::createPlugin(const std::string &pluginName)
{
auto pluginPtr = std::dynamic_pointer_cast<PluginBase>(
DrClassMap::newSharedObject(pluginName));
if (!pluginPtr)
{
LOG_ERROR << "Plugin " << pluginName << " undefined!";
return;
}
pluginsMap_[pluginName] = pluginPtr;
}
PluginBase *PluginsManager::getPlugin(const std::string &pluginName)
{
auto iter = pluginsMap_.find(pluginName);
if (iter != pluginsMap_.end())
{
return iter->second.get();
}
return nullptr;
}
std::shared_ptr<PluginBase> PluginsManager::getSharedPlugin(
const std::string &pluginName)
{
auto iter = pluginsMap_.find(pluginName);
if (iter != pluginsMap_.end())
{
return iter->second;
}
return nullptr;
}
+42
View File
@@ -0,0 +1,42 @@
/**
*
* PluginsManager.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/plugins/Plugin.h>
#include <map>
namespace drogon
{
using PluginBasePtr = std::shared_ptr<PluginBase>;
class PluginsManager : trantor::NonCopyable
{
public:
void initializeAllPlugins(
const Json::Value &configs,
const std::function<void(PluginBase *)> &forEachCallback);
PluginBase *getPlugin(const std::string &pluginName);
std::shared_ptr<PluginBase> getSharedPlugin(const std::string &pluginName);
~PluginsManager();
private:
void createPlugin(const std::string &pluginName);
std::map<std::string, PluginBasePtr> pluginsMap_;
std::vector<PluginBase *> initializedPlugins_;
};
} // namespace drogon
+208
View File
@@ -0,0 +1,208 @@
#include <drogon/plugins/PromExporter.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/utils/monitoring/Counter.h>
#include <drogon/utils/monitoring/Gauge.h>
#include <drogon/utils/monitoring/Histogram.h>
#include <drogon/utils/monitoring/Collector.h>
using namespace drogon;
using namespace drogon::monitoring;
using namespace drogon::plugin;
void PromExporter::initAndStart(const Json::Value &config)
{
path_ = config.get("path", path_).asString();
LOG_TRACE << path_;
auto &app = drogon::app();
std::weak_ptr<PromExporter> weakPtr = shared_from_this();
app.registerHandler(
path_,
[weakPtr](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
auto resp = HttpResponse::newNotFoundResponse(req);
callback(resp);
return;
}
auto resp = HttpResponse::newHttpResponse();
resp->setBody(thisPtr->exportMetrics());
resp->setContentTypeCode(CT_TEXT_PLAIN);
resp->setExpiredTime(5);
callback(resp);
},
{Get, Options},
"PromExporter");
if (config.isMember("collectors"))
{
std::lock_guard<std::mutex> guard(mutex_);
auto &collectors = config["collectors"];
if (collectors.isArray())
{
for (auto const &collector : collectors)
{
if (collector.isObject())
{
auto name = collector["name"].asString();
auto type = collector["type"].asString();
auto help = collector["help"].asString();
auto labels = collector["labels"];
if (labels.isArray())
{
std::vector<std::string> labelNames;
for (auto const &label : labels)
{
if (label.isString())
{
labelNames.push_back(label.asString());
}
else
{
LOG_ERROR << "label name must be a string!";
}
}
if (type == "counter")
{
auto counterCollector =
std::make_shared<Collector<Counter>>(
name, help, labelNames);
collectors_.insert(
std::make_pair(name, counterCollector));
}
else if (type == "gauge")
{
auto gaugeCollector =
std::make_shared<Collector<Gauge>>(name,
help,
labelNames);
collectors_.insert(
std::make_pair(name, gaugeCollector));
}
else if (type == "histogram")
{
auto histogramCollector =
std::make_shared<Collector<Histogram>>(
name, help, labelNames);
collectors_.insert(
std::make_pair(name, histogramCollector));
}
else
{
LOG_ERROR << "Unknown collector type: " << type;
}
}
else
{
LOG_ERROR << "labels must be an array!";
}
}
else
{
LOG_ERROR << "collector must be an object!";
}
}
}
else
{
LOG_ERROR << "collectors must be an array!";
}
}
}
static std::string exportCollector(
const std::shared_ptr<CollectorBase> &collector)
{
auto sampleGroups = collector->collect();
std::string res;
res.append("# HELP ")
.append(collector->name())
.append(" ")
.append(collector->help())
.append("\n");
res.append("# TYPE ")
.append(collector->name())
.append(" ")
.append(collector->type())
.append("\n");
for (auto const &sampleGroup : sampleGroups)
{
auto const &metricPtr = sampleGroup.metric;
auto const &samples = sampleGroup.samples;
for (auto &sample : samples)
{
res.append(sample.name);
if (!sample.exLabels.empty() || !metricPtr->labels().empty())
{
res.append("{");
for (auto const &label : metricPtr->labels())
{
res.append(label.first)
.append("=\"")
.append(label.second)
.append("\",");
}
for (auto const &label : sample.exLabels)
{
res.append(label.first)
.append("=\"")
.append(label.second)
.append("\",");
}
res.pop_back();
res.append("}");
}
res.append(" ").append(std::to_string(sample.value));
if (sample.timestamp.microSecondsSinceEpoch() > 0)
{
res.append(" ")
.append(std::to_string(
sample.timestamp.microSecondsSinceEpoch() / 1000))
.append("\n");
}
else
{
res.append("\n");
}
}
}
return res;
}
std::string PromExporter::exportMetrics()
{
std::lock_guard<std::mutex> guard(mutex_);
std::string result;
for (auto const &collector : collectors_)
{
result.append(exportCollector(collector.second));
}
return result;
}
void PromExporter::registerCollector(
const std::shared_ptr<drogon::monitoring::CollectorBase> &collector)
{
std::lock_guard<std::mutex> guard(mutex_);
if (collectors_.find(collector->name()) != collectors_.end())
{
throw std::runtime_error("The collector named " + collector->name() +
" has been registered!");
}
collectors_.insert(std::make_pair(collector->name(), collector));
}
std::shared_ptr<drogon::monitoring::CollectorBase> PromExporter::getCollector(
const std::string &name) const noexcept(false)
{
std::lock_guard<std::mutex> guard(mutex_);
auto iter = collectors_.find(name);
if (iter != collectors_.end())
{
return iter->second;
}
else
{
throw std::runtime_error("Can't find the collector named " + name);
}
}
+180
View File
@@ -0,0 +1,180 @@
/**
*
* RangeParser.h
* He, Wanchen
*
* Copyright 2021, He,Wanchen. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "RangeParser.h"
#include <limits>
using namespace drogon;
static constexpr size_t MAX_SIZE = std::numeric_limits<size_t>::max();
static constexpr size_t MAX_TEN = MAX_SIZE / 10;
static constexpr size_t MAX_DIGIT = MAX_SIZE % 10;
// clang-format off
#define DR_SKIP_WHITESPACE(p) while (*p == ' ') { ++(p); }
#define DR_ISDIGIT(p) ('0' <= *(p) && *(p) <= '9')
#define DR_WOULD_OVERFLOW(base, digit) \
(static_cast<size_t>(base) > MAX_TEN || \
(static_cast<size_t>(base) >= MAX_TEN && \
static_cast<size_t>(digit) - '0' > MAX_DIGIT))
// clang-format on
/** Following formats are valid range header according to rfc7233`
* Range: <unit>=<start>-
* Range: <unit>=<start>-<end>
* Range: <unit>=<start>-<end>, <start>-<end>
* Range: <unit>=<start>-<end>, <start>-<end>, <start>-<end>
* Range: <unit>=-<suffix-length>
*/
FileRangeParseResult drogon::parseRangeHeader(const std::string &rangeStr,
size_t contentLength,
std::vector<FileRange> &ranges)
{
if (rangeStr.size() < 7 || rangeStr.compare(0, 6, "bytes=") != 0)
{
return InvalidRange;
}
const char *iter = rangeStr.c_str() + 6;
size_t totalSize = 0;
while (true)
{
size_t start = 0;
size_t end = 0;
// If this is a suffix range: <unit>=-<suffix-length>
bool isSuffix = false;
DR_SKIP_WHITESPACE(iter);
if (*iter == '-')
{
isSuffix = true;
++iter;
}
// Parse start
else
{
if (!DR_ISDIGIT(iter))
{
return InvalidRange;
}
while (DR_ISDIGIT(iter))
{
// integer out of range
if (DR_WOULD_OVERFLOW(start, *iter))
{
return NotSatisfiable;
}
start = start * 10 + (*iter++ - '0');
}
DR_SKIP_WHITESPACE(iter);
// should be separator now
if (*iter++ != '-')
{
return InvalidRange;
}
DR_SKIP_WHITESPACE(iter);
// If this is a prefix range <unit>=<range-start>-
if (*iter == ',' || *iter == '\0')
{
end = contentLength;
// Handle found
if (start < end)
{
if (totalSize > MAX_SIZE - (end - start))
{
return NotSatisfiable;
}
totalSize += end - start;
ranges.push_back({start, end});
}
if (*iter++ != ',')
{
break;
}
continue;
}
}
// Parse end
if (!DR_ISDIGIT(iter))
{
return InvalidRange;
}
while (DR_ISDIGIT(iter))
{
if (DR_WOULD_OVERFLOW(end, *iter))
{
return NotSatisfiable;
}
end = end * 10 + (*iter++ - '0');
}
DR_SKIP_WHITESPACE(iter);
if (*iter != ',' && *iter != '\0')
{
return InvalidRange;
}
if (isSuffix)
{
start = (end < contentLength) ? contentLength - end : 0;
end = contentLength - 1;
}
// [start, end)
if (end >= contentLength)
{
end = contentLength;
}
else
{
++end;
}
// handle found
if (start < end)
{
ranges.push_back({start, end});
if (totalSize > MAX_SIZE - (end - start))
{
return NotSatisfiable;
}
totalSize += end - start;
// We restrict the number to be under 100, to avoid malicious
// requests.
// Though rfc does not say anything about max number of ranges,
// it does mention that server can ignore range header freely.
if (ranges.size() > 100)
{
return InvalidRange;
}
}
if (*iter++ != ',')
{
break;
}
}
if (ranges.size() == 0 || totalSize > contentLength)
{
return NotSatisfiable;
}
return ranges.size() == 1 ? SinglePart : MultiPart;
}
#undef DR_SKIP_WHITESPACE
#undef DR_ISDIGIT
#undef DR_WOULD_OVERFLOW
+41
View File
@@ -0,0 +1,41 @@
/**
*
* RangeParser.h
* He, Wanchen
*
* Copyright 2021, He,Wanchen. All rights reserved.
* https://github.com/drogonframework/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 <vector>
#include <sys/types.h>
namespace drogon
{
// [start, end)
struct FileRange
{
size_t start;
size_t end;
};
enum FileRangeParseResult
{
InvalidRange = -1,
NotSatisfiable = 0,
SinglePart = 1,
MultiPart = 2
};
FileRangeParseResult parseRangeHeader(const std::string &rangeStr,
size_t contentLength,
std::vector<FileRange> &ranges);
} // namespace drogon
+24
View File
@@ -0,0 +1,24 @@
#include <drogon/RateLimiter.h>
#include "FixedWindowRateLimiter.h"
#include "SlidingWindowRateLimiter.h"
#include "TokenBucketRateLimiter.h"
using namespace drogon;
RateLimiterPtr RateLimiter::newRateLimiter(
RateLimiterType type,
size_t capacity,
std::chrono::duration<double> timeUnit)
{
switch (type)
{
case RateLimiterType::kFixedWindow:
return std::make_shared<FixedWindowRateLimiter>(capacity, timeUnit);
case RateLimiterType::kSlidingWindow:
return std::make_shared<SlidingWindowRateLimiter>(capacity,
timeUnit);
case RateLimiterType::kTokenBucket:
return std::make_shared<TokenBucketRateLimiter>(capacity, timeUnit);
}
return std::make_shared<TokenBucketRateLimiter>(capacity, timeUnit);
}
+226
View File
@@ -0,0 +1,226 @@
/**
*
* @file RealIpResolver.cc
* @author Nitromelon
*
* Copyright 2022, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/drogon.h>
#include <trantor/utils/Logger.h>
#include <drogon/plugins/RealIpResolver.h>
using namespace drogon;
using namespace drogon::plugin;
struct XForwardedForParser : public trantor::NonCopyable
{
explicit XForwardedForParser(std::string value)
: value_(std::move(value)), start_(value_.c_str()), len_(value_.size())
{
}
std::string getNext()
{
if (len_ == 0)
{
return {};
}
// Skip trailing separators
const char *cur;
for (cur = start_ + len_ - 1; cur > start_; --cur, --len_)
{
if (*cur != ' ' && *cur != ',')
{
break;
}
}
for (; cur > start_; --cur)
{
if (*cur == ' ' || *cur == ',')
{
++cur;
break;
}
}
std::string ip{cur, len_ - (cur - start_)};
len_ = cur == start_ ? 0 : cur - start_ - 1;
return ip;
}
private:
std::string value_;
const char *start_;
size_t len_;
};
static trantor::InetAddress parseAddress(const std::string &addr)
{
auto pos = addr.find(':');
uint16_t port = 0;
if (pos == std::string::npos)
{
return trantor::InetAddress(addr, 0);
}
try
{
port = std::stoi(addr.substr(pos + 1));
}
catch (const std::exception &ex)
{
(void)ex;
LOG_ERROR << "Error in ipv4 address: " + addr;
port = 0;
}
return trantor::InetAddress(addr.substr(0, pos), port);
}
void RealIpResolver::initAndStart(const Json::Value &config)
{
fromHeader_ = config.get("from_header", "x-forwarded-for").asString();
attributeKey_ = config.get("attribute_key", "real-ip").asString();
std::transform(fromHeader_.begin(),
fromHeader_.end(),
fromHeader_.begin(),
[](unsigned char c) { return tolower(c); });
if (fromHeader_ == "x-forwarded-for")
{
useXForwardedFor_ = true;
}
const Json::Value &trustIps = config["trust_ips"];
if (!trustIps.isNull() && !trustIps.isArray())
{
throw std::runtime_error("Invalid trusted_ips. Should be array.");
}
for (const auto &ipOrCidr : trustIps)
{
trustCIDRs_.emplace_back(ipOrCidr.asString());
}
drogon::app().registerPreRoutingAdvice([this](const HttpRequestPtr &req) {
const auto &headers = req->headers();
auto ipHeaderFind = headers.find(fromHeader_);
const trantor::InetAddress &peerAddr = req->getPeerAddr();
if (ipHeaderFind == headers.end() || !matchCidr(peerAddr, trustCIDRs_))
{
// Target header is empty, or
// direct peer is already a non-proxy
req->attributes()->insert(attributeKey_, peerAddr);
return;
}
const std::string &ipHeader = ipHeaderFind->second;
// Use a header field which contains a single ip
if (!useXForwardedFor_)
{
trantor::InetAddress addr = parseAddress(ipHeader);
if (addr.isUnspecified())
{
req->attributes()->insert(attributeKey_, peerAddr);
}
else
{
req->attributes()->insert(attributeKey_, addr);
}
return;
}
// Use x-forwarded-for header, which may contains multiple ip address,
// separated by comma
XForwardedForParser parser(ipHeader);
std::string ip;
while (!(ip = parser.getNext()).empty())
{
trantor::InetAddress addr = parseAddress(ip);
if (addr.isUnspecified() || matchCidr(addr, trustCIDRs_))
{
continue;
}
req->attributes()->insert(attributeKey_, addr);
return;
}
// No match, use peerAddr
req->attributes()->insert(attributeKey_, peerAddr);
});
}
void RealIpResolver::shutdown()
{
}
const trantor::InetAddress &RealIpResolver::GetRealAddr(
const HttpRequestPtr &req)
{
auto *plugin = app().getPlugin<drogon::plugin::RealIpResolver>();
if (!plugin)
{
return req->getPeerAddr();
}
return plugin->getRealAddr(req);
}
const trantor::InetAddress &RealIpResolver::getRealAddr(
const HttpRequestPtr &req) const
{
const std::shared_ptr<Attributes> &attributesPtr = req->getAttributes();
if (!attributesPtr->find(attributeKey_))
{
return req->getPeerAddr();
}
return attributesPtr->get<trantor::InetAddress>(attributeKey_);
}
bool RealIpResolver::matchCidr(const trantor::InetAddress &addr,
const CIDRs &trustCIDRs)
{
for (const auto &cidr : trustCIDRs)
{
if ((addr.ipNetEndian() & cidr.mask_) == cidr.addr_)
{
return true;
}
}
return false;
}
RealIpResolver::CIDR::CIDR(const std::string &ipOrCidr)
{
// Find CIDR slash
auto pos = ipOrCidr.find('/');
std::string ipv4;
if (pos != std::string::npos)
{
// parameter is a CIDR block
std::string prefixLen = ipOrCidr.substr(pos + 1);
ipv4 = ipOrCidr.substr(0, pos);
uint16_t prefix = std::stoi(prefixLen);
if (prefix > 32)
{
throw std::runtime_error("Bad CIDR block: " + ipOrCidr);
}
mask_ = htonl(0xffffffffu << (32 - prefix));
}
else
{
// parameter is an IP
ipv4 = ipOrCidr;
mask_ = 0xffffffffu;
}
trantor::InetAddress addr(ipv4, 0);
if (addr.isIpV6())
{
throw std::runtime_error("Ipv6 is not supported by RealIpResolver.");
}
if (addr.isUnspecified())
{
throw std::runtime_error("Bad ipv4 address: " + ipv4);
}
addr_ = addr.ipNetEndian() & mask_;
}
+87
View File
@@ -0,0 +1,87 @@
/**
*
* @file Redirector.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 <drogon/drogon.h>
#include <drogon/plugins/Redirector.h>
using namespace drogon;
using namespace drogon::plugin;
void Redirector::initAndStart(const Json::Value &config)
{
auto weakPtr = std::weak_ptr<Redirector>(shared_from_this());
drogon::app().registerSyncAdvice(
[weakPtr](const HttpRequestPtr &req) -> HttpResponsePtr {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
return HttpResponsePtr{};
}
std::string protocol, host;
bool pathChanged{false};
for (auto &handler : thisPtr->pathRewriteHandlers_)
{
pathChanged |= handler(req);
}
for (auto &handler : thisPtr->handlers_)
{
if (!handler(req, protocol, host, pathChanged))
{
return HttpResponse::newNotFoundResponse(req);
}
}
if (!protocol.empty() || !host.empty() || pathChanged)
{
std::string url;
if (protocol.empty())
{
if (!host.empty())
{
url = req->isOnSecureConnection() ? "https://"
: "http://";
url.append(host);
}
}
else
{
url = std::move(protocol);
if (!host.empty())
{
url.append(host);
}
else
{
url.append(req->getHeader("host"));
}
}
url.append(req->path());
auto &query = req->query();
if (!query.empty())
{
url.append("?").append(query);
}
return HttpResponse::newRedirectionResponse(url);
}
for (auto &handler : thisPtr->forwardHandlers_)
{
handler(req);
}
return HttpResponsePtr{};
});
}
void Redirector::shutdown()
{
LOG_TRACE << "Redirector plugin is shutdown!";
}
+80
View File
@@ -0,0 +1,80 @@
/**
*
* RedisClientManager.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/nosql/RedisClient.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/IOThreadStorage.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <string>
#include <memory>
namespace drogon
{
namespace nosql
{
class RedisClientManager : public trantor::NonCopyable
{
public:
void createRedisClients(const std::vector<trantor::EventLoop *> &ioLoops);
RedisClientPtr getRedisClient(const std::string &name)
{
assert(redisClientsMap_.find(name) != redisClientsMap_.end());
return redisClientsMap_[name];
}
RedisClientPtr getFastRedisClient(const std::string &name)
{
auto iter = redisFastClientsMap_.find(name);
assert(iter != redisFastClientsMap_.end());
return iter->second.getThreadData();
}
void createRedisClient(const std::string &name,
const std::string &host,
unsigned short port,
const std::string &username,
const std::string &password,
size_t connectionNum,
bool isFast,
double timeout,
unsigned int db);
// bool areAllRedisClientsAvailable() const noexcept;
~RedisClientManager();
private:
std::map<std::string, RedisClientPtr> redisClientsMap_;
std::map<std::string, IOThreadStorage<RedisClientPtr>> redisFastClientsMap_;
struct RedisInfo
{
std::string name_;
std::string addr_;
std::string username_;
std::string password_;
unsigned short port_;
bool isFast_;
size_t connectionNumber_;
double timeout_;
unsigned int db_;
};
std::vector<RedisInfo> redisInfos_;
};
} // namespace nosql
} // namespace drogon
@@ -0,0 +1,54 @@
/**
*
* RedisClientManagerSkipped.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 "RedisClientManager.h"
#include <drogon/config.h>
#include <drogon/utils/Utilities.h>
#include <algorithm>
#include <cstdlib>
using namespace drogon::nosql;
using namespace drogon;
void RedisClientManager::createRedisClients(
const std::vector<trantor::EventLoop *> & /*ioloops*/)
{
return;
}
void RedisClientManager::createRedisClient(const std::string & /*name*/,
const std::string & /*host*/,
unsigned short /*port*/,
const std::string & /*username*/,
const std::string & /*password*/,
size_t /*connectionNum*/,
bool /*isFast*/,
double /*timeout*/,
unsigned int /*db*/)
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
// bool RedisClientManager::areAllRedisClientsAvailable() const noexcept
// {
// LOG_FATAL << "Redis is supported by drogon, please install the "
// "hiredis library first.";
// abort();
// }
RedisClientManager::~RedisClientManager()
{
}
+33
View File
@@ -0,0 +1,33 @@
/**
*
* RedisClientSkipped.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 "drogon/nosql/RedisClient.h"
namespace drogon
{
namespace nosql
{
std::shared_ptr<RedisClient> RedisClient::newRedisClient(
const trantor::InetAddress & /*serverAddress*/,
size_t /*numberOfConnections*/,
const std::string & /*password*/,
const unsigned int /*db*/,
const std::string & /*username*/)
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
} // namespace nosql
} // namespace drogon
+72
View File
@@ -0,0 +1,72 @@
/**
*
* RedisClientSkipped.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 "drogon/nosql/RedisResult.h"
#include "trantor/utils/Logger.h"
namespace drogon
{
namespace nosql
{
std::string RedisResult::getStringForDisplaying() const noexcept
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
std::string RedisResult::getStringForDisplayingWithIndent(
size_t /*indent*/) const noexcept
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
std::string RedisResult::asString() const noexcept(false)
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
RedisResultType RedisResult::type() const noexcept
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
std::vector<RedisResult> RedisResult::asArray() const noexcept(false)
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
long long RedisResult::asInteger() const noexcept(false)
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
bool RedisResult::isNil() const noexcept
{
LOG_FATAL << "Redis is not supported by drogon, please install the "
"hiredis library first.";
abort();
}
} // namespace nosql
} // namespace drogon
+225
View File
@@ -0,0 +1,225 @@
/**
*
* @file RequestStream.cc
* @author Nitromelon
*
* Copyright 2024, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "MultipartStreamParser.h"
#include "HttpRequestImpl.h"
#include <drogon/RequestStream.h>
#include <variant>
namespace drogon
{
class RequestStreamImpl : public RequestStream
{
public:
RequestStreamImpl(const HttpRequestImplPtr &req) : weakReq_(req)
{
}
~RequestStreamImpl() override
{
if (isSet_.exchange(true))
{
return;
}
// Drop all data if no reader is set
if (auto req = weakReq_.lock())
{
setHandlerInLoop(req, RequestStreamReader::newNullReader());
}
}
void setStreamReader(RequestStreamReaderPtr reader) override
{
if (isSet_.exchange(true))
{
return;
}
if (auto req = weakReq_.lock())
{
setHandlerInLoop(req, std::move(reader));
}
}
void setHandlerInLoop(const HttpRequestImplPtr &req,
RequestStreamReaderPtr reader)
{
if (!req->isStreamMode())
{
return;
}
auto loop = req->getLoop();
if (loop->isInLoopThread())
{
req->setStreamReader(std::move(reader));
}
else
{
loop->queueInLoop([req, reader = std::move(reader)]() mutable {
req->setStreamReader(std::move(reader));
});
}
}
private:
std::weak_ptr<HttpRequestImpl> weakReq_;
std::atomic_bool isSet_{false};
};
namespace internal
{
RequestStreamPtr createRequestStream(const HttpRequestPtr &req)
{
auto reqImpl = std::static_pointer_cast<HttpRequestImpl>(req);
if (!reqImpl->isStreamMode())
{
return nullptr;
}
return std::make_shared<RequestStreamImpl>(
std::static_pointer_cast<HttpRequestImpl>(req));
}
} // namespace internal
/**
* A default implementation for convenience
*/
class DefaultStreamReader : public RequestStreamReader
{
public:
DefaultStreamReader(StreamDataCallback dataCb,
StreamFinishCallback finishCb)
: dataCb_(std::move(dataCb)), finishCb_(std::move(finishCb))
{
}
void onStreamData(const char *data, size_t length) override
{
dataCb_(data, length);
}
void onStreamFinish(std::exception_ptr ex) override
{
finishCb_(std::move(ex));
}
private:
StreamDataCallback dataCb_;
StreamFinishCallback finishCb_;
};
/**
* Drops all data
*/
class NullStreamReader : public RequestStreamReader
{
public:
void onStreamData(const char *, size_t length) override
{
}
void onStreamFinish(std::exception_ptr) override
{
}
};
/**
* Parse multipart data and return actual content
*/
class MultipartStreamReader : public RequestStreamReader
{
public:
MultipartStreamReader(const std::string &contentType,
MultipartHeaderCallback headerCb,
StreamDataCallback dataCb,
StreamFinishCallback finishCb)
: parser_(contentType),
headerCb_(std::move(headerCb)),
dataCb_(std::move(dataCb)),
finishCb_(std::move(finishCb))
{
}
void onStreamData(const char *data, size_t length) override
{
if (!parser_.isValid() || parser_.isFinished())
{
return;
}
parser_.parse(data, length, headerCb_, dataCb_);
if (!parser_.isValid())
{
// TODO: should we mix stream error and user error?
finishCb_(std::make_exception_ptr(
std::runtime_error("invalid multipart data")));
}
else if (parser_.isFinished())
{
finishCb_({});
}
}
void onStreamFinish(std::exception_ptr ex) override
{
if (!parser_.isValid() || parser_.isFinished())
{
return;
}
if (!ex)
{
finishCb_(std::make_exception_ptr(
std::runtime_error("incomplete multipart data")));
}
else
{
finishCb_(std::move(ex));
}
}
private:
MultipartStreamParser parser_;
MultipartHeaderCallback headerCb_;
StreamDataCallback dataCb_;
StreamFinishCallback finishCb_;
};
RequestStreamReaderPtr RequestStreamReader::newReader(
StreamDataCallback dataCb,
StreamFinishCallback finishCb)
{
return std::make_shared<DefaultStreamReader>(std::move(dataCb),
std::move(finishCb));
}
RequestStreamReaderPtr RequestStreamReader::newNullReader()
{
return std::make_shared<NullStreamReader>();
}
RequestStreamReaderPtr RequestStreamReader::newMultipartReader(
const HttpRequestPtr &req,
MultipartHeaderCallback headerCb,
StreamDataCallback dataCb,
StreamFinishCallback finishCb)
{
return std::make_shared<MultipartStreamReader>(req->getHeader(
"content-type"),
std::move(headerCb),
std::move(dataCb),
std::move(finishCb));
}
} // namespace drogon
+137
View File
@@ -0,0 +1,137 @@
/**
*
* drogon_plugin_SecureSSLRedirector.cc
*
*/
#include <drogon/drogon.h>
#include <drogon/plugins/SecureSSLRedirector.h>
#include <drogon/plugins/Redirector.h>
#include <cstddef>
#include <string>
using namespace drogon;
using namespace drogon::plugin;
void SecureSSLRedirector::initAndStart(const Json::Value &config)
{
if (config.isMember("ssl_redirect_exempt"))
{
if (config["ssl_redirect_exempt"].isArray())
{
const auto &exempts = config["ssl_redirect_exempt"];
size_t exemptsCount = exempts.size();
if (exemptsCount)
{
std::string regexString;
size_t len = 0;
for (const auto &exempt : exempts)
{
assert(exempt.isString());
len += exempt.size();
}
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
const auto last = --exempts.end();
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
regexString.append("(")
.append(exempt->asString())
.append(")|");
regexString.append("(").append(last->asString()).append(")");
exemptRegex_ = std::regex(regexString);
regexFlag_ = true;
}
}
else if (config["ssl_redirect_exempt"].isString())
{
exemptRegex_ = std::regex(config["ssl_redirect_exempt"].asString());
regexFlag_ = true;
}
else
{
LOG_ERROR
<< "ssl_redirect_exempt must be a string or string array!";
}
}
secureHost_ = config.get("secure_ssl_host", "").asString();
std::weak_ptr<SecureSSLRedirector> weakPtr = shared_from_this();
auto redirector = drogon::app().getPlugin<Redirector>();
if (!redirector)
{
LOG_ERROR << "Redirector plugin is not found!";
return;
}
redirector->registerRedirectHandler(
[weakPtr](const drogon::HttpRequestPtr &req,
std::string &protocol,
std::string &host,
bool &) -> bool {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
return false;
}
return thisPtr->redirectingAdvice(req, protocol, host);
});
}
void SecureSSLRedirector::shutdown()
{
/// Shutdown the plugin
}
bool SecureSSLRedirector::redirectingAdvice(const HttpRequestPtr &req,
std::string &protocol,
std::string &host) const
{
if (req->isOnSecureConnection() || protocol == "https://")
{
return true;
}
else if (regexFlag_)
{
std::smatch regexResult;
if (std::regex_match(req->path(), regexResult, exemptRegex_))
{
return true;
}
else
{
return redirectToSSL(req, protocol, host);
}
}
else
{
return redirectToSSL(req, protocol, host);
}
}
bool SecureSSLRedirector::redirectToSSL(const HttpRequestPtr &req,
std::string &protocol,
std::string &host) const
{
if (!secureHost_.empty())
{
host = secureHost_;
protocol = "https://";
return true;
}
else if (host.empty())
{
const auto &reqHost = req->getHeader("host");
if (!reqHost.empty())
{
protocol = "https://";
return true;
}
else
{
return false;
}
}
else
{
protocol = "https://";
return true;
}
}
+128
View File
@@ -0,0 +1,128 @@
/**
*
* @file 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"
using namespace drogon;
SessionManager::SessionManager(
trantor::EventLoop *loop,
size_t timeout,
const std::vector<AdviceStartSessionCallback> &startAdvices,
const std::vector<AdviceDestroySessionCallback> &destroyAdvices,
IdGeneratorCallback idGeneratorCallback)
: loop_(loop),
timeout_(timeout),
sessionStartAdvices_(startAdvices),
sessionDestroyAdvices_(destroyAdvices),
idGeneratorCallback_(idGeneratorCallback)
{
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,
[this](const std::string &key) {
for (auto &advice : sessionStartAdvices_)
{
advice(key);
}
},
[this](const std::string &key) {
for (auto &advice : sessionDestroyAdvices_)
{
advice(key);
}
}));
}
else if (timeout_ == 0)
{
sessionMapPtr_ = std::unique_ptr<CacheMap<std::string, SessionPtr>>(
new CacheMap<std::string, SessionPtr>(
loop_,
0,
0,
0,
[this](const std::string &key) {
for (auto &advice : sessionStartAdvices_)
{
advice(key);
}
},
[this](const std::string &key) {
for (auto &advice : sessionDestroyAdvices_)
{
advice(key);
}
}));
}
}
SessionPtr SessionManager::getSession(const std::string &sessionID,
bool needToSet)
{
assert(!sessionID.empty());
SessionPtr sessionPtr;
sessionMapPtr_->modify(
sessionID,
[&sessionPtr, &sessionID, needToSet](SessionPtr &sessionInCache) {
if (sessionInCache)
{
sessionPtr = sessionInCache;
}
else
{
sessionPtr =
std::shared_ptr<Session>(new Session(sessionID, needToSet));
sessionInCache = sessionPtr;
}
},
timeout_);
return sessionPtr;
}
void SessionManager::changeSessionId(const SessionPtr &sessionPtr)
{
auto oldId = sessionPtr->sessionId();
auto newId = idGeneratorCallback_();
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);
});
}
+58
View File
@@ -0,0 +1,58 @@
/**
*
* @file 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/drogon_callbacks.h>
#include <drogon/CacheMap.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <functional>
#include <memory>
#include <string>
#include <mutex>
#include <vector>
namespace drogon
{
class SessionManager : public trantor::NonCopyable
{
public:
using IdGeneratorCallback = std::function<std::string()>;
SessionManager(
trantor::EventLoop *loop,
size_t timeout,
const std::vector<AdviceStartSessionCallback> &startAdvices,
const std::vector<AdviceDestroySessionCallback> &destroyAdvices,
IdGeneratorCallback idGeneratorCallback);
~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_;
trantor::EventLoop *loop_;
size_t timeout_;
const std::vector<AdviceStartSessionCallback> &sessionStartAdvices_;
const std::vector<AdviceDestroySessionCallback> &sessionDestroyAdvices_;
IdGeneratorCallback idGeneratorCallback_;
};
} // namespace drogon
+289
View File
@@ -0,0 +1,289 @@
/**
*
* @file SharedLibManager.cc
* @author 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 "SharedLibManager.h"
#include <drogon/config.h>
#include <dirent.h>
#include <dlfcn.h>
#include <fstream>
#include <sys/types.h>
#include <trantor/utils/Logger.h>
#include <unistd.h>
static void forEachFileIn(
const std::string &path,
const std::function<void(const std::string &, const struct stat &)> &cb)
{
DIR *dp;
struct dirent *dirp;
struct stat st;
/* open dirent directory */
if ((dp = opendir(path.c_str())) == NULL)
{
// perror("opendir:");
LOG_ERROR << "can't open dir,path:" << path;
return;
}
/**
* read all files in this dir
**/
while ((dirp = readdir(dp)) != NULL)
{
/* ignore hidden files */
if (dirp->d_name[0] == '.')
continue;
/* get dirent status */
std::string filename = dirp->d_name;
std::string fullname = path;
fullname.append("/").append(filename);
if (stat(fullname.c_str(), &st) == -1)
{
perror("stat");
closedir(dp);
return;
}
/* if dirent is a directory, find files recursively */
if (S_ISDIR(st.st_mode))
{
forEachFileIn(fullname, cb);
}
else
{
cb(fullname, st);
}
}
closedir(dp);
return;
}
using namespace drogon;
SharedLibManager::SharedLibManager(const std::vector<std::string> &libPaths,
const std::string &outputPath)
: libPaths_(libPaths), outputPath_(outputPath)
{
workingThread_.run();
timeId_ =
workingThread_.getLoop()->runEvery(5.0, [this]() { managerLibs(); });
}
SharedLibManager::~SharedLibManager()
{
workingThread_.getLoop()->invalidateTimer(timeId_);
}
void SharedLibManager::managerLibs()
{
for (auto const &libPath : libPaths_)
{
forEachFileIn(
libPath,
[this, libPath](const std::string &filename,
const struct stat &st) {
auto pos = filename.rfind('.');
if (pos != std::string::npos)
{
auto exName = filename.substr(pos + 1);
if (exName == "csp")
{
// compile
auto lockFile = filename + ".lock";
std::ifstream fin(lockFile);
if (fin)
{
return;
}
void *oldHandle = nullptr;
if (dlMap_.find(filename) != dlMap_.end())
{
#if defined __linux__ || defined __HAIKU__
if (st.st_mtim.tv_sec >
dlMap_[filename].mTime.tv_sec)
#elif defined _WIN32
if (st.st_mtime > dlMap_[filename].mTime.tv_sec)
#else
if (st.st_mtimespec.tv_sec >
dlMap_[filename].mTime.tv_sec)
#endif
{
LOG_TRACE << "new csp file:" << filename;
oldHandle = dlMap_[filename].handle;
}
else
return;
}
{
std::ofstream fout(lockFile);
}
auto srcFile = filename.substr(0, pos);
if (!outputPath_.empty())
{
pos = srcFile.rfind("/");
if (pos != std::string::npos)
{
srcFile = srcFile.substr(pos + 1);
}
srcFile = outputPath_ + "/" + srcFile;
}
auto soFile = srcFile + ".so";
DLStat dlStat;
if (!shouldCompileLib(soFile, st))
{
LOG_TRACE << "Using already compiled library:"
<< soFile;
dlStat.handle = loadLib(soFile, oldHandle);
}
else
{
// generate source code and compile it.
std::string cmd = "drogon_ctl create view ";
if (!outputPath_.empty())
{
cmd.append(filename).append(" -o ").append(
outputPath_);
}
else
{
cmd.append(filename).append(" -o ").append(
libPath);
}
srcFile.append(".cc");
LOG_TRACE << cmd;
auto r = system(cmd.c_str());
// TODO: handle r
(void)(r);
dlStat.handle =
compileAndLoadLib(srcFile, oldHandle);
}
#if defined __linux__ || defined __HAIKU__
dlStat.mTime = st.st_mtim;
#elif defined _WIN32
dlStat.mTime.tv_sec = st.st_mtime;
#else
dlStat.mTime = st.st_mtimespec;
#endif
if (dlStat.handle)
{
dlMap_[filename] = dlStat;
}
else
{
dlStat.handle = dlMap_[filename].handle;
dlMap_[filename] = dlStat;
}
workingThread_.getLoop()->runAfter(3.5, [lockFile]() {
LOG_TRACE << "remove file " << lockFile;
if (unlink(lockFile.c_str()) == -1)
perror("");
});
}
}
});
}
}
void *SharedLibManager::compileAndLoadLib(const std::string &sourceFile,
void *oldHld)
{
LOG_TRACE << "src:" << sourceFile;
std::string cmd = COMPILER_COMMAND;
cmd.append(" ")
.append(sourceFile)
.append(" ")
.append(COMPILATION_FLAGS)
.append(" ")
.append(INCLUDING_DIRS);
if (std::string(COMPILER_ID).find("Clang") != std::string::npos)
cmd.append(" -shared -fPIC -undefined dynamic_lookup -o ");
else
cmd.append(" -shared -fPIC --no-gnu-unique -o ");
auto pos = sourceFile.rfind('.');
auto soFile = sourceFile.substr(0, pos);
soFile.append(".so");
cmd.append(soFile);
LOG_TRACE << cmd;
if (system(cmd.c_str()) == 0)
{
LOG_TRACE << "Compiled successfully:" << soFile;
return loadLib(soFile, oldHld);
}
else
{
LOG_DEBUG << "Could not compile library.";
return nullptr;
}
}
bool SharedLibManager::shouldCompileLib(const std::string &soFile,
const struct stat &sourceStat)
{
#if defined __linux__ || defined __HAIKU__
auto sourceModifiedTime = sourceStat.st_mtim.tv_sec;
#elif defined _WIN32
auto sourceModifiedTime = sourceStat.st_mtime;
#else
auto sourceModifiedTime = sourceStat.st_mtimespec.tv_sec;
#endif
struct stat soStat;
if (stat(soFile.c_str(), &soStat) == -1)
{
LOG_TRACE << "Cannot determine modification time for:" << soFile;
return true;
}
#if defined __linux__ || defined __HAIKU__
auto soModifiedTime = soStat.st_mtim.tv_sec;
#elif defined _WIN32
auto soModifiedTime = soStat.st_mtime;
#else
auto soModifiedTime = soStat.st_mtimespec.tv_sec;
#endif
return (sourceModifiedTime > soModifiedTime);
}
void *SharedLibManager::loadLib(const std::string &soFile, void *oldHld)
{
if (oldHld)
{
if (dlclose(oldHld) == 0)
{
LOG_TRACE << "Successfully closed dynamic library:" << oldHld;
}
else
{
LOG_TRACE << dlerror();
}
}
auto Handle = dlopen(soFile.c_str(), RTLD_LAZY);
if (!Handle)
{
LOG_ERROR << "load " << soFile << " error!";
LOG_ERROR << dlerror();
}
else
{
LOG_TRACE << "Successfully loaded library file " << soFile;
}
return Handle;
}
+51
View File
@@ -0,0 +1,51 @@
/**
*
* SharedLibManager.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/EventLoopThread.h>
#include <trantor/utils/NonCopyable.h>
#include <unordered_map>
#include <vector>
#include <sys/stat.h>
namespace drogon
{
class SharedLibManager : public trantor::NonCopyable
{
public:
SharedLibManager(const std::vector<std::string> &libPaths,
const std::string &outputPath);
~SharedLibManager();
private:
void managerLibs();
std::vector<std::string> libPaths_;
std::string outputPath_;
struct DLStat
{
void *handle{nullptr};
struct timespec mTime = {0, 0};
};
std::unordered_map<std::string, DLStat> dlMap_;
void *compileAndLoadLib(const std::string &sourceFile, void *oldHld);
void *loadLib(const std::string &soFile, void *oldHld);
bool shouldCompileLib(const std::string &soFile,
const struct stat &sourceStat);
trantor::TimerId timeId_;
trantor::EventLoopThread workingThread_;
};
} // namespace drogon
+227
View File
@@ -0,0 +1,227 @@
#include <drogon/plugins/SlashRemover.h>
#include <drogon/plugins/Redirector.h>
#include <drogon/HttpAppFramework.h>
#include "drogon/utils/FunctionTraits.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
#include <utility>
using namespace drogon;
using namespace drogon::plugin;
using std::string;
using std::string_view;
enum removeSlashMode : uint8_t
{
trailing = 1 << 0,
duplicate = 1 << 1,
both = trailing | duplicate,
};
/// Returns the index before the trailing slashes,
/// or 0 if only contains slashes
static inline size_t findTrailingSlashes(string_view url)
{
auto len = url.size();
// Must be at least 2 chars and end with a slash
if (len < 2 || url.back() != '/')
return string::npos;
size_t a = len - 1; // We already know the last char is '/',
// we will use pre-decrement to account for this
while (--a > 0 && url[a] == '/')
; // We know the first char is '/', so don't check for 0
return a;
}
static inline void removeTrailingSlashes(string &url,
size_t start,
string_view originalUrl)
{
url = originalUrl.substr(0, start + 1);
}
/// Returns the index of the 2nd duplicate slash
static inline size_t findDuplicateSlashes(string_view url)
{
size_t len = url.size();
if (len < 2)
return string::npos;
bool startedPair = true; // Always starts with a slash
for (size_t a = 1; a < len; ++a)
{
if (url[a] != '/') // Broken pair
{
startedPair = false;
continue;
}
if (startedPair) // Matching pair
return a;
startedPair = true;
}
return string::npos;
}
static inline void removeDuplicateSlashes(string &url, size_t start)
{
// +1 because we don't need to look at the same character again,
// which was found by `findDuplicateSlashes`, it saves one iteration
for (size_t b = (start--) + 1, len = url.size(); b < len; ++b)
{
const char c = url[b];
if (c != '/' || url[start] != '/')
{
++start;
url[start] = c;
}
}
url.resize(start + 1);
}
static inline std::pair<size_t, size_t> findExcessiveSlashes(string_view url)
{
size_t len = url.size();
if (len < 2) // Must have at least 2 characters to count as either trailing
// or duplicate slash
return {string::npos, string::npos};
// Trail finder
size_t trailIdx = len; // The pre-decrement will put it on last char
while (--trailIdx > 0 && url[trailIdx] == '/')
; // We know first char is '/', no need to check it
// Filled with '/'
if (trailIdx == 0)
return {
0, // Only keep first slash
string::npos, // No duplicate
};
// Look for a duplicate pair
size_t dupIdx = 1;
for (bool startedPair = true; dupIdx < trailIdx;
++dupIdx) // Always starts with a slash
{
if (url[dupIdx] != '/') // Broken pair
{
startedPair = false;
continue;
}
if (startedPair) // Matching pair
break;
startedPair = true;
}
// Found no duplicate
if (dupIdx == trailIdx)
return {
trailIdx != len - 1
? // If has gone past last char, then there is a trailing slash
trailIdx
: string::npos, // No trail
string::npos, // No duplicate
};
// Duplicate found
return {
trailIdx != len - 1
? // If has gone past last char, then there is a trailing slash
trailIdx
: string::npos, // No trail
dupIdx,
};
}
static inline void removeExcessiveSlashes(string &url,
std::pair<size_t, size_t> start,
string_view originalUrl)
{
if (start.first != string::npos)
removeTrailingSlashes(url, start.first, originalUrl);
else
url = originalUrl;
if (start.second != string::npos)
removeDuplicateSlashes(url, start.second);
}
static inline bool handleReq(const drogon::HttpRequestPtr &req,
uint8_t removeMode)
{
switch (removeMode)
{
case trailing:
{
auto find = findTrailingSlashes(req->path());
if (find == string::npos)
return false;
string newPath;
removeTrailingSlashes(newPath, find, req->path());
req->setPath(std::move(newPath));
break;
}
case duplicate:
{
auto find = findDuplicateSlashes(req->path());
if (find == string::npos)
return false;
string newPath = req->path();
removeDuplicateSlashes(newPath, find);
req->setPath(std::move(newPath));
break;
}
case both:
default:
{
auto find = findExcessiveSlashes(req->path());
if (find.first == string::npos && find.second == string::npos)
return false;
string newPath;
removeExcessiveSlashes(newPath, find, req->path());
req->setPath(std::move(newPath));
break;
}
}
return true;
}
void SlashRemover::initAndStart(const Json::Value &config)
{
trailingSlashes_ = config.get("remove_trailing_slashes", true).asBool();
duplicateSlashes_ = config.get("remove_duplicate_slashes", true).asBool();
redirect_ = config.get("redirect", true).asBool();
const uint8_t removeMode =
(trailingSlashes_ * trailing) | (duplicateSlashes_ * duplicate);
if (!removeMode)
return;
auto redirector = app().getPlugin<Redirector>();
if (!redirector)
{
LOG_ERROR << "Redirector plugin is not found!";
return;
}
auto func = [removeMode](const HttpRequestPtr &req) -> bool {
return handleReq(req, removeMode);
};
if (redirect_)
{
redirector->registerPathRewriteHandler(std::move(func));
}
else
{
redirector->registerForwardHandler(std::move(func));
}
}
void SlashRemover::shutdown()
{
LOG_TRACE << "SlashRemover plugin is shutdown!";
}
@@ -0,0 +1,59 @@
#include "SlidingWindowRateLimiter.h"
#include <assert.h>
using namespace drogon;
SlidingWindowRateLimiter::SlidingWindowRateLimiter(
size_t capacity,
std::chrono::duration<double> timeUnit)
: capacity_(capacity),
unitStartTime_(std::chrono::steady_clock::now()),
lastTime_(unitStartTime_),
timeUnit_(timeUnit)
{
}
// implementation of the sliding window algorithm
bool SlidingWindowRateLimiter::isAllowed()
{
auto now = std::chrono::steady_clock::now();
unitStartTime_ =
unitStartTime_ +
std::chrono::duration_cast<decltype(unitStartTime_)::duration>(
std::chrono::duration<double>(
static_cast<double>(
(uint64_t)(std::chrono::duration_cast<
std::chrono::duration<double>>(
now - unitStartTime_)
.count() /
timeUnit_.count())) *
timeUnit_.count()));
if (unitStartTime_ > lastTime_)
{
auto duration =
std::chrono::duration_cast<std::chrono::duration<double>>(
unitStartTime_ - lastTime_);
if (duration >= timeUnit_)
{
previousRequests_ = 0;
}
else
{
previousRequests_ = currentRequests_;
}
currentRequests_ = 0;
}
auto coef = std::chrono::duration_cast<std::chrono::duration<double>>(
now - unitStartTime_) /
timeUnit_;
assert(coef <= 1.0);
auto count = previousRequests_ * (1.0 - coef) + currentRequests_;
if (count < capacity_)
{
currentRequests_++;
lastTime_ = now;
return true;
}
return false;
}
@@ -0,0 +1,23 @@
#pragma once
#include <drogon/RateLimiter.h>
#include <chrono>
namespace drogon
{
class SlidingWindowRateLimiter : public RateLimiter
{
public:
SlidingWindowRateLimiter(size_t capacity,
std::chrono::duration<double> timeUnit);
bool isAllowed() override;
~SlidingWindowRateLimiter() noexcept override = default;
private:
size_t capacity_;
size_t currentRequests_{0};
size_t previousRequests_{0};
std::chrono::steady_clock::time_point unitStartTime_;
std::chrono::steady_clock::time_point lastTime_;
std::chrono::duration<double> timeUnit_;
};
} // namespace drogon
+87
View File
@@ -0,0 +1,87 @@
/**
* SpinLock.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 <emmintrin.h>
#include <thread>
#define LOCK_SPIN 2048
namespace drogon
{
class SpinLock
{
public:
inline SpinLock(std::atomic<bool> &flag) : flag_(flag)
{
static const int cpu = std::thread::hardware_concurrency();
int n, i;
while (1)
{
if (!flag_.load() &&
!flag_.exchange(true, std::memory_order_acquire))
{
return;
}
if (cpu > 1)
{
for (n = 1; n < LOCK_SPIN; n <<= 1)
{
for (i = 0; i < n; ++i)
{
//__asm__ __volatile__("rep; nop" ::: "memory"); //pause
_mm_pause();
}
if (!flag_.load() &&
!flag_.exchange(true, std::memory_order_acquire))
{
return;
}
}
}
std::this_thread::yield();
}
}
inline ~SpinLock()
{
flag_.store(false, std::memory_order_release);
}
private:
std::atomic<bool> &flag_;
};
class SimpleSpinLock
{
public:
inline SimpleSpinLock(std::atomic_flag &flag) : flag_(flag)
{
while (flag_.test_and_set(std::memory_order_acquire))
{
//__asm__ __volatile__("rep; nop" ::: "memory"); //pause
_mm_pause();
}
}
inline ~SimpleSpinLock()
{
flag_.clear(std::memory_order_release);
}
private:
std::atomic_flag &flag_;
};
} // namespace drogon
+589
View File
@@ -0,0 +1,589 @@
/**
*
* StaticFileRouter.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 "StaticFileRouter.h"
#include "HttpAppFrameworkImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "RangeParser.h"
#include <fstream>
#include <iostream>
#include <algorithm>
#include <memory>
#include <fcntl.h>
#ifndef _WIN32
#include <sys/file.h>
#elif !defined(__MINGW32__)
#define stat _wstati64
#define S_ISREG(m) (((m) & 0170000) == (0100000))
#define S_ISDIR(m) (((m) & 0170000) == (0040000))
#endif
#include <sys/stat.h>
#include <filesystem>
using namespace drogon;
void StaticFileRouter::init(const std::vector<trantor::EventLoop *> &ioLoops)
{
// Max timeout up to about 70 days;
staticFilesCacheMap_ = std::make_unique<
IOThreadStorage<std::unique_ptr<CacheMap<std::string, char>>>>();
staticFilesCacheMap_->init(
[&ioLoops](std::unique_ptr<CacheMap<std::string, char>> &mapPtr,
size_t i) {
assert(i == ioLoops[i]->index());
mapPtr = std::make_unique<CacheMap<std::string, char>>(ioLoops[i],
1.0f,
4,
50);
});
staticFilesCache_ = std::make_unique<
IOThreadStorage<std::unordered_map<std::string, HttpResponsePtr>>>();
ioLocationsPtr_ =
std::make_shared<IOThreadStorage<std::vector<Location>>>();
for (auto *loop : ioLoops)
{
loop->queueInLoop(
[ioLocationsPtr = ioLocationsPtr_, locations = locations_] {
**ioLocationsPtr = locations;
});
}
}
void StaticFileRouter::reset()
{
staticFilesCacheMap_.reset();
staticFilesCache_.reset();
ioLocationsPtr_.reset();
locations_.clear();
}
void StaticFileRouter::route(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
const std::string &path = req->path();
if (path.find("..") != std::string::npos)
{
auto directories = utils::splitString(path, "/");
int traversalDepth = 0;
for (const auto &dir : directories)
{
if (dir == "..")
{
traversalDepth--;
}
else if (dir != ".")
{
traversalDepth++;
}
if (traversalDepth < 0)
{
// Downloading files from the parent folder is forbidden.
callback(app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
}
}
auto lPath = path;
std::transform(lPath.begin(),
lPath.end(),
lPath.begin(),
[](unsigned char c) { return tolower(c); });
for (auto &location : **ioLocationsPtr_)
{
auto &URI = location.uriPrefix_;
if (location.realLocation_.empty())
{
if (!location.alias_.empty())
{
if (location.alias_[0] == '/')
{
location.realLocation_ = location.alias_;
}
else
{
location.realLocation_ =
HttpAppFrameworkImpl::instance().getDocumentRoot() +
location.alias_;
}
}
else
{
location.realLocation_ =
HttpAppFrameworkImpl::instance().getDocumentRoot() +
location.uriPrefix_;
}
if (location.realLocation_[location.realLocation_.length() - 1] !=
'/')
{
location.realLocation_.append(1, '/');
}
if (!location.isCaseSensitive_)
{
std::transform(URI.begin(),
URI.end(),
URI.begin(),
[](unsigned char c) { return tolower(c); });
}
}
auto &tmpPath = location.isCaseSensitive_ ? path : lPath;
if (tmpPath.length() >= URI.length() &&
std::equal(tmpPath.begin(),
tmpPath.begin() + URI.length(),
URI.begin()))
{
std::string_view restOfThePath{path.data() + URI.length(),
path.length() - URI.length()};
auto pos = restOfThePath.rfind('/');
if (pos != 0 && pos != std::string_view::npos &&
!location.isRecursive_)
{
callback(app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
std::string filePath =
location.realLocation_ +
std::string{restOfThePath.data(), restOfThePath.length()};
std::filesystem::path fsFilePath(utils::toNativePath(filePath));
std::error_code err;
if (!std::filesystem::exists(fsFilePath, err))
{
defaultHandler_(req, std::move(callback));
return;
}
if (std::filesystem::is_directory(fsFilePath, err))
{
// Check if path is eligible for an implicit index.html
if (implicitPageEnable_)
{
filePath = filePath + "/" + implicitPage_;
}
else
{
callback(app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
}
else
{
if (!location.allowAll_)
{
pos = restOfThePath.rfind('.');
if (pos == std::string_view::npos)
{
callback(
app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
std::string extension{restOfThePath.data() + pos + 1,
restOfThePath.length() - pos - 1};
std::transform(extension.begin(),
extension.end(),
extension.begin(),
[](unsigned char c) { return tolower(c); });
if (fileTypeSet_.find(extension) == fileTypeSet_.end())
{
callback(
app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
}
}
if (location.middlewares_.empty())
{
sendStaticFileResponse(filePath,
req,
std::move(callback),
std::string_view{
location.defaultContentType_});
}
else
{
middlewares_function::passMiddlewares(
location.middlewares_,
req,
std::move(callback),
[this,
req,
filePath = std::move(filePath),
contentType =
std::string_view{location.defaultContentType_}](
std::function<void(const HttpResponsePtr &)>
&&middlewarePostCb) mutable {
sendStaticFileResponse(filePath,
req,
std::move(middlewarePostCb),
contentType);
});
}
return;
}
}
std::string directoryPath =
HttpAppFrameworkImpl::instance().getDocumentRoot() + path;
std::filesystem::path fsDirectoryPath(utils::toNativePath(directoryPath));
std::error_code err;
if (std::filesystem::exists(fsDirectoryPath, err))
{
if (std::filesystem::is_directory(fsDirectoryPath, err))
{
// Check if path is eligible for an implicit index.html
if (implicitPageEnable_)
{
std::string filePath = directoryPath + "/" + implicitPage_;
sendStaticFileResponse(filePath, req, std::move(callback), "");
return;
}
else
{
callback(app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
}
else
{
// This is a normal page
auto pos = path.rfind('.');
if (pos == std::string::npos)
{
callback(app().getCustomErrorHandler()(k403Forbidden, req));
return;
}
std::string filetype = lPath.substr(pos + 1);
if (fileTypeSet_.find(filetype) != fileTypeSet_.end())
{
// LOG_INFO << "file query!" << path;
std::string filePath = directoryPath;
sendStaticFileResponse(filePath, req, std::move(callback), "");
return;
}
}
}
defaultHandler_(req, std::move(callback));
}
// Expand this struct as you need, nothing to worry about
struct FileStat
{
size_t fileSize_;
struct tm modifiedTime_;
std::string modifiedTimeStr_;
};
// A wrapper to call stat()
// std::filesystem::file_time_type::clock::to_time_t still not
// implemented by M$, even in c++20, so keep calls to stat()
static bool getFileStat(const std::string &filePath, FileStat &myStat)
{
#if defined(_WIN32) && !defined(__MINGW32__)
struct _stati64 fileStat;
#else // _WIN32
struct stat fileStat;
#endif // _WIN32
if (stat(utils::toNativePath(filePath).c_str(), &fileStat) == 0 &&
S_ISREG(fileStat.st_mode))
{
LOG_TRACE << "last modify time:" << fileStat.st_mtime;
#ifdef _WIN32
gmtime_s(&myStat.modifiedTime_, &fileStat.st_mtime);
#else
gmtime_r(&fileStat.st_mtime, &myStat.modifiedTime_);
#endif
std::string &timeStr = myStat.modifiedTimeStr_;
timeStr.resize(64);
size_t len = strftime((char *)timeStr.data(),
timeStr.size(),
"%a, %d %b %Y %H:%M:%S GMT",
&myStat.modifiedTime_);
timeStr.resize(len);
myStat.fileSize_ = fileStat.st_size;
return true;
}
return false;
}
void StaticFileRouter::sendStaticFileResponse(
const std::string &filePath,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string_view &defaultContentType)
{
if (req->method() != Get)
{
callback(app().getCustomErrorHandler()(k405MethodNotAllowed, req));
return;
}
FileStat fileStat;
bool fileExists = false;
const std::string &rangeStr = req->getHeaderBy("range");
if (enableRange_ && !rangeStr.empty())
{
if (!getFileStat(filePath, fileStat))
{
defaultHandler_(req, std::move(callback));
return;
}
fileExists = true;
// Check last modified time, rfc2616-14.25
// If-Modified-Since: Mon, 15 Oct 2018 06:26:33 GMT
// According to rfc 7233-3.1, preconditions must be evaluated before
const std::string &modiStr = req->getHeaderBy("if-modified-since");
if (enableLastModify_ && modiStr == fileStat.modifiedTimeStr_)
{
LOG_TRACE << "Not modified!";
std::shared_ptr<HttpResponseImpl> resp =
std::make_shared<HttpResponseImpl>();
resp->setStatusCode(k304NotModified);
resp->setContentTypeCode(CT_NONE);
callback(resp);
return;
}
// Check If-Range precondition
const std::string &ifRange = req->getHeaderBy("if-range");
if (ifRange.empty() || ifRange == fileStat.modifiedTimeStr_)
{
std::vector<FileRange> ranges;
switch (parseRangeHeader(rangeStr, fileStat.fileSize_, ranges))
{
// TODO: support only single range now
// Contributions are welcomed.
case FileRangeParseResult::SinglePart:
case FileRangeParseResult::MultiPart:
{
auto firstRange = ranges.front();
auto ct = fileNameToContentTypeAndMime(filePath);
auto resp =
HttpResponse::newFileResponse(filePath,
firstRange.start,
firstRange.end -
firstRange.start,
true,
"",
ct.first,
std::string(ct.second),
req);
if (!fileStat.modifiedTimeStr_.empty())
{
resp->addHeader("Last-Modified",
fileStat.modifiedTimeStr_);
resp->addHeader("Expires",
"Thu, 01 Jan 1970 00:00:00 GMT");
}
callback(resp);
return;
}
case FileRangeParseResult::NotSatisfiable:
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k416RequestedRangeNotSatisfiable);
char buf[64];
snprintf(buf,
sizeof(buf),
"bytes */%zu",
fileStat.fileSize_);
resp->addHeader("Content-Range", std::string(buf));
callback(resp);
return;
}
/** rfc7233 4.4.
* > Note: Because servers are free to ignore Range, many
* implementations will simply respond with the entire selected
* representation in a 200 (OK) response. That is partly
* because most clients are prepared to receive a 200 (OK) to
* complete the task (albeit less efficiently) and partly
* because clients might not stop making an invalid partial
* request until they have received a complete representation.
* Thus, clients cannot depend on receiving a 416 (Range Not
* Satisfiable) response even when it is most appropriate.
*/
default:
break;
}
}
}
// find cached response
HttpResponsePtr cachedResp;
auto &cacheMap = staticFilesCache_->getThreadData();
auto iter = cacheMap.find(filePath);
if (iter != cacheMap.end())
{
cachedResp = iter->second;
}
if (enableLastModify_)
{
if (cachedResp)
{
if (static_cast<HttpResponseImpl *>(cachedResp.get())
->getHeaderBy("last-modified") ==
req->getHeaderBy("if-modified-since"))
{
std::shared_ptr<HttpResponseImpl> resp =
std::make_shared<HttpResponseImpl>();
resp->setStatusCode(k304NotModified);
resp->setContentTypeCode(CT_NONE);
callback(resp);
return;
}
}
else
{
LOG_TRACE << "enabled LastModify";
if (!fileExists && !getFileStat(filePath, fileStat))
{
defaultHandler_(req, std::move(callback));
return;
}
fileExists = true;
const std::string &modiStr = req->getHeaderBy("if-modified-since");
if (modiStr == fileStat.modifiedTimeStr_)
{
LOG_TRACE << "not Modified!";
std::shared_ptr<HttpResponseImpl> resp =
std::make_shared<HttpResponseImpl>();
resp->setStatusCode(k304NotModified);
resp->setContentTypeCode(CT_NONE);
callback(resp);
return;
}
}
}
if (cachedResp)
{
LOG_TRACE << "Using file cache";
callback(cachedResp);
return;
}
// Check existence
if (!fileExists)
{
std::filesystem::path fsFilePath(utils::toNativePath(filePath));
std::error_code err;
if (!std::filesystem::exists(fsFilePath, err) ||
!std::filesystem::is_regular_file(fsFilePath, err))
{
defaultHandler_(req, std::move(callback));
return;
}
}
HttpResponsePtr resp;
auto &acceptEncoding = req->getHeaderBy("accept-encoding");
if (brStaticFlag_ && acceptEncoding.find("br") != std::string::npos)
{
// Find compressed file first.
auto brFileName = filePath + ".br";
std::filesystem::path fsBrFile(utils::toNativePath(brFileName));
std::error_code err;
if (std::filesystem::exists(fsBrFile, err) &&
std::filesystem::is_regular_file(fsBrFile, err))
{
auto ct = fileNameToContentTypeAndMime(filePath);
resp = HttpResponse::newFileResponse(
brFileName, "", ct.first, std::string(ct.second), req);
resp->addHeader("Content-Encoding", "br");
}
}
if (!resp && gzipStaticFlag_ &&
acceptEncoding.find("gzip") != std::string::npos)
{
// Find compressed file first.
auto gzipFileName = filePath + ".gz";
std::filesystem::path fsGzipFile(utils::toNativePath(gzipFileName));
std::error_code err;
if (std::filesystem::exists(fsGzipFile, err) &&
std::filesystem::is_regular_file(fsGzipFile, err))
{
auto ct = fileNameToContentTypeAndMime(filePath);
resp = HttpResponse::newFileResponse(
gzipFileName, "", ct.first, std::string(ct.second), req);
resp->addHeader("Content-Encoding", "gzip");
}
}
if (!resp)
{
auto ct = fileNameToContentTypeAndMime(filePath);
resp = HttpResponse::newFileResponse(
filePath, "", ct.first, std::string(ct.second), req);
}
if (resp->statusCode() != k404NotFound)
{
if (resp->getContentType() == CT_APPLICATION_OCTET_STREAM &&
!defaultContentType.empty())
{
resp->setContentTypeCodeAndCustomString(CT_CUSTOM,
defaultContentType);
}
if (!fileStat.modifiedTimeStr_.empty())
{
resp->addHeader("Last-Modified", fileStat.modifiedTimeStr_);
resp->addHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
}
if (enableRange_)
{
resp->addHeader("accept-range", "bytes");
}
if (!headers_.empty())
{
for (auto &header : headers_)
{
resp->addHeader(header.first, header.second);
}
}
// cache the response for 5 seconds by default
if (staticFilesCacheTime_ >= 0)
{
LOG_TRACE << "Save in cache for " << staticFilesCacheTime_
<< " seconds";
resp->setExpiredTime(staticFilesCacheTime_);
staticFilesCache_->getThreadData()[filePath] = resp;
staticFilesCacheMap_->getThreadData()->insert(
filePath, 0, staticFilesCacheTime_, [this, filePath]() {
LOG_TRACE << "Erase cache";
assert(staticFilesCache_->getThreadData().find(filePath) !=
staticFilesCache_->getThreadData().end());
staticFilesCache_->getThreadData().erase(filePath);
});
}
callback(resp);
return;
}
callback(resp);
}
void StaticFileRouter::setFileTypes(const std::vector<std::string> &types)
{
fileTypeSet_.clear();
for (auto const &type : types)
{
fileTypeSet_.insert(type);
}
}
void StaticFileRouter::defaultHandler(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
callback(HttpResponse::newNotFoundResponse(req));
}
+195
View File
@@ -0,0 +1,195 @@
/**
*
* StaticFileRouter.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 "MiddlewaresFunction.h"
#include <drogon/CacheMap.h>
#include <drogon/IOThreadStorage.h>
#include <functional>
#include <set>
#include <string>
#include <memory>
namespace drogon
{
class StaticFileRouter
{
public:
static StaticFileRouter &instance()
{
static StaticFileRouter inst;
return inst;
}
void route(const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void setFileTypes(const std::vector<std::string> &types);
void setStaticFilesCacheTime(int cacheTime)
{
staticFilesCacheTime_ = cacheTime;
}
int staticFilesCacheTime() const
{
return staticFilesCacheTime_;
}
void setGzipStatic(bool useGzipStatic)
{
gzipStaticFlag_ = useGzipStatic;
}
void setBrStatic(bool useBrStatic)
{
brStaticFlag_ = useBrStatic;
}
void init(const std::vector<trantor::EventLoop *> &ioLoops);
void reset();
void sendStaticFileResponse(
const std::string &filePath,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string_view &defaultContentType);
void addALocation(const std::string &uriPrefix,
const std::string &defaultContentType,
const std::string &alias,
bool isCaseSensitive,
bool allowAll,
bool isRecursive,
const std::vector<std::string> &middlewareNames)
{
locations_.emplace_back(uriPrefix,
defaultContentType,
alias,
isCaseSensitive,
allowAll,
isRecursive,
middlewareNames);
}
void setStaticFileHeaders(
const std::vector<std::pair<std::string, std::string>> &headers)
{
headers_ = headers;
}
void setImplicitPageEnable(bool useImplicitPage)
{
implicitPageEnable_ = useImplicitPage;
}
bool isImplicitPageEnabled() const
{
return implicitPageEnable_;
}
void setImplicitPage(const std::string &implicitPageFile)
{
implicitPage_ = implicitPageFile;
}
const std::string &getImplicitPage() const
{
return implicitPage_;
}
void setDefaultHandler(DefaultHandler &&handler)
{
defaultHandler_ = std::move(handler);
}
private:
static void defaultHandler(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
std::set<std::string> fileTypeSet_{"html",
"js",
"css",
"xml",
"xsl",
"txt",
"svg",
"ttf",
"otf",
"woff2",
"woff",
"eot",
"png",
"jpg",
"jpeg",
"gif",
"bmp",
"ico",
"icns"};
int staticFilesCacheTime_{5};
bool enableLastModify_{true};
bool enableRange_{true};
bool gzipStaticFlag_{true};
bool brStaticFlag_{true};
std::unique_ptr<
IOThreadStorage<std::unique_ptr<CacheMap<std::string, char>>>>
staticFilesCacheMap_;
std::unique_ptr<
IOThreadStorage<std::unordered_map<std::string, HttpResponsePtr>>>
staticFilesCache_;
std::vector<std::pair<std::string, std::string>> headers_;
bool implicitPageEnable_{true};
std::string implicitPage_{"index.html"};
DefaultHandler defaultHandler_ = StaticFileRouter::defaultHandler;
struct Location
{
std::string uriPrefix_;
std::string defaultContentType_;
std::string alias_;
std::string realLocation_;
bool isCaseSensitive_;
bool allowAll_;
bool isRecursive_;
std::vector<std::shared_ptr<drogon::HttpMiddlewareBase>> middlewares_;
Location(const std::string &uriPrefix,
const std::string &defaultContentType,
const std::string &alias,
bool isCaseSensitive,
bool allowAll,
bool isRecursive,
const std::vector<std::string> &middlewares)
: uriPrefix_(uriPrefix),
alias_(alias),
isCaseSensitive_(isCaseSensitive),
allowAll_(allowAll),
isRecursive_(isRecursive),
middlewares_(middlewares_function::createMiddlewares(middlewares))
{
if (!defaultContentType.empty())
{
defaultContentType_ =
std::string{"content-type: "} + defaultContentType + "\r\n";
}
}
};
std::shared_ptr<IOThreadStorage<std::vector<Location>>> ioLocationsPtr_;
std::vector<Location> locations_;
};
} // namespace drogon
+41
View File
@@ -0,0 +1,41 @@
/**
*
* @file TaskTimeoutFlag.cc
* @author 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 "TaskTimeoutFlag.h"
using namespace drogon;
TaskTimeoutFlag::TaskTimeoutFlag(trantor::EventLoop *loop,
const std::chrono::duration<double> &timeout,
std::function<void()> timeoutCallback)
: loop_(loop), timeout_(timeout), timeoutFunc_(timeoutCallback)
{
}
void TaskTimeoutFlag::runTimer()
{
std::weak_ptr<TaskTimeoutFlag> weakPtr = shared_from_this();
loop_->runAfter(timeout_, [weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (thisPtr->done())
return;
thisPtr->timeoutFunc_();
});
}
bool TaskTimeoutFlag::done()
{
return isDone_.exchange(true);
}
+41
View File
@@ -0,0 +1,41 @@
/**
*
* @file TaskTimeoutFlag.h
* @author 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/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <chrono>
#include <functional>
#include <atomic>
#include <memory>
namespace drogon
{
class TaskTimeoutFlag : public trantor::NonCopyable,
public std::enable_shared_from_this<TaskTimeoutFlag>
{
public:
TaskTimeoutFlag(trantor::EventLoop *loop,
const std::chrono::duration<double> &timeout,
std::function<void()> timeoutCallback);
bool done();
void runTimer();
private:
std::atomic<bool> isDone_{false};
trantor::EventLoop *loop_;
std::chrono::duration<double> timeout_;
std::function<void()> timeoutFunc_;
};
} // namespace drogon
@@ -0,0 +1,31 @@
#include "TokenBucketRateLimiter.h"
using namespace drogon;
TokenBucketRateLimiter::TokenBucketRateLimiter(
size_t capacity,
std::chrono::duration<double> timeUnit)
: capacity_(capacity),
lastTime_(std::chrono::steady_clock::now()),
timeUnit_(timeUnit),
tokens_((double)capacity_)
{
}
// implementation of the token bucket algorithm
bool TokenBucketRateLimiter::isAllowed()
{
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(
now - lastTime_);
tokens_ += capacity_ * (duration / timeUnit_);
if (tokens_ > capacity_)
tokens_ = (double)capacity_;
lastTime_ = now;
if (tokens_ > 1.0)
{
tokens_ -= 1.0;
return true;
}
return false;
}
@@ -0,0 +1,21 @@
#pragma once
#include <drogon/RateLimiter.h>
namespace drogon
{
class TokenBucketRateLimiter : public RateLimiter
{
public:
TokenBucketRateLimiter(size_t capacity,
std::chrono::duration<double> timeUnit);
bool isAllowed() override;
~TokenBucketRateLimiter() noexcept override = default;
private:
size_t capacity_;
std::chrono::steady_clock::time_point lastTime_;
std::chrono::duration<double> timeUnit_;
double tokens_;
};
} // namespace drogon
File diff suppressed because it is too large Load Diff
+501
View File
@@ -0,0 +1,501 @@
/**
*
* @file WebSocketClientImpl.cc
* @author 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 "WebSocketClientImpl.h"
#include "HttpResponseImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseParser.h"
#include "HttpUtils.h"
#include "WebSocketConnectionImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/utils/Utilities.h>
#include <drogon/config.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Utilities.h>
using namespace drogon;
using namespace trantor;
WebSocketClientImpl::~WebSocketClientImpl()
{
}
WebSocketConnectionPtr WebSocketClientImpl::getConnection()
{
return websockConnPtr_;
}
void WebSocketClientImpl::stop()
{
stop_ = true;
if (websockConnPtr_)
{
websockConnPtr_->shutdown();
websockConnPtr_.reset();
}
tcpClientPtr_.reset();
}
void WebSocketClientImpl::createTcpClient()
{
LOG_TRACE << "New TcpClient," << serverAddr_.toIpPort();
tcpClientPtr_ =
std::make_shared<trantor::TcpClient>(loop_, serverAddr_, "httpClient");
if (useSSL_)
{
auto policy = trantor::TLSPolicy::defaultClientPolicy();
policy->setUseOldTLS(useOldTLS_)
.setValidate(validateCert_)
.setHostname(domain_)
.setConfCmds(sslConfCmds_)
.setCertPath(clientCertPath_)
.setKeyPath(clientKeyPath_);
tcpClientPtr_->enableSSL(std::move(policy));
}
auto thisPtr = shared_from_this();
std::weak_ptr<WebSocketClientImpl> weakPtr = thisPtr;
tcpClientPtr_->setConnectionCallback(
[weakPtr](const trantor::TcpConnectionPtr &connPtr) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (connPtr->connected())
{
connPtr->setContext(
std::make_shared<HttpResponseParser>(connPtr));
// send request;
LOG_TRACE << "Connection established!";
thisPtr->sendReq(connPtr);
}
else
{
LOG_TRACE << "connection disconnect";
thisPtr->connectionClosedCallback_(thisPtr);
thisPtr->websockConnPtr_.reset();
if (!thisPtr->stop_)
{
thisPtr->loop_->runAfter(1.0, [thisPtr]() {
thisPtr->reconnect();
});
}
}
});
tcpClientPtr_->setConnectionErrorCallback([weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
// can't connect to server
LOG_TRACE << "error connecting to server";
thisPtr->requestCallback_(ReqResult::NetworkFailure, nullptr, thisPtr);
if (!thisPtr->stop_)
{
thisPtr->loop_->runAfter(1.0,
[thisPtr]() { thisPtr->reconnect(); });
}
});
tcpClientPtr_->setMessageCallback(
[weakPtr](const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
thisPtr->onRecvMessage(connPtr, msg);
}
});
tcpClientPtr_->connect();
}
void WebSocketClientImpl::connectToServerInLoop()
{
loop_->assertInLoopThread();
upgradeRequest_->addHeader("Connection", "Upgrade");
upgradeRequest_->addHeader("Upgrade", "websocket");
bool usePort = ((serverAddr_.toPort() != 80 && !useSSL_) ||
(serverAddr_.toPort() != 443 && useSSL_));
upgradeRequest_->addHeader(
"Host",
domain_.empty()
? (usePort ? serverAddr_.toIpPort() : serverAddr_.toIp())
: (usePort ? domain_ + ":" + std::to_string(serverAddr_.toPort())
: domain_));
upgradeRequest_->addHeader("Sec-WebSocket-Version", "13");
auto randStr = utils::genRandomString(16);
wsKey_ = utils::base64Encode((const unsigned char *)randStr.data(),
(unsigned int)randStr.length());
auto wsKey = wsKey_;
wsKey.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
unsigned char accKey[20];
static_assert(sizeof(accKey) == sizeof(trantor::utils::Hash160));
auto sha1 = trantor::utils::sha1(wsKey);
memcpy(accKey, &sha1, sizeof(sha1));
wsAccept_ = utils::base64Encode(accKey, 20);
upgradeRequest_->addHeader("Sec-WebSocket-Key", wsKey_);
// upgradeRequest_->addHeader("Sec-WebSocket-Version","13");
assert(!tcpClientPtr_);
bool hasIpv6Address = false;
if (serverAddr_.isIpV6())
{
auto ipaddr = serverAddr_.ip6NetEndian();
for (int i = 0; i < 4; ++i)
{
if (ipaddr[i] != 0)
{
hasIpv6Address = true;
break;
}
}
}
if (serverAddr_.ipNetEndian() == 0 && !hasIpv6Address && !domain_.empty() &&
serverAddr_.portNetEndian() != 0)
{
if (!resolver_)
{
resolver_ = trantor::Resolver::newResolver(loop_);
}
resolver_->resolve(
domain_,
[thisPtr = shared_from_this(),
hasIpv6Address](const trantor::InetAddress &addr) {
thisPtr->loop_->runInLoop([thisPtr, addr, hasIpv6Address]() {
auto port = thisPtr->serverAddr_.portNetEndian();
thisPtr->serverAddr_ = addr;
thisPtr->serverAddr_.setPortNetEndian(port);
LOG_TRACE << "dns:domain=" << thisPtr->domain_
<< ";ip=" << thisPtr->serverAddr_.toIp();
if ((thisPtr->serverAddr_.ipNetEndian() != 0 ||
hasIpv6Address) &&
thisPtr->serverAddr_.portNetEndian() != 0)
{
thisPtr->createTcpClient();
}
else
{
thisPtr->requestCallback_(ReqResult::BadServerAddress,
nullptr,
thisPtr);
return;
}
});
});
return;
}
if ((serverAddr_.ipNetEndian() != 0 || hasIpv6Address) &&
serverAddr_.portNetEndian() != 0)
{
createTcpClient();
}
else
{
requestCallback_(ReqResult::BadServerAddress,
nullptr,
shared_from_this());
return;
}
}
void WebSocketClientImpl::onRecvWsMessage(
const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msgBuffer)
{
if (websockConnPtr_)
{
websockConnPtr_->onNewMessage(connPtr, msgBuffer);
}
}
void WebSocketClientImpl::onRecvMessage(
const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msgBuffer)
{
if (upgraded_)
{
onRecvWsMessage(connPtr, msgBuffer);
return;
}
auto responseParser = connPtr->getContext<HttpResponseParser>();
// LOG_TRACE << "###:" << msg->readableBytes();
if (!responseParser->parseResponse(msgBuffer))
{
requestCallback_(ReqResult::BadResponse, nullptr, shared_from_this());
connPtr->shutdown();
websockConnPtr_.reset();
tcpClientPtr_.reset();
return;
}
if (responseParser->gotAll())
{
auto resp = responseParser->responseImpl();
responseParser->reset();
auto acceptStr = resp->getHeaderBy("sec-websocket-accept");
if (resp->statusCode() != k101SwitchingProtocols ||
acceptStr != wsAccept_)
{
requestCallback_(ReqResult::BadResponse,
nullptr,
shared_from_this());
connPtr->shutdown();
websockConnPtr_.reset();
tcpClientPtr_.reset();
return;
}
auto &type = resp->getHeaderBy("content-type");
if (type.find("application/json") != std::string::npos)
{
resp->parseJson();
}
auto &coding = resp->getHeaderBy("content-encoding");
if (coding == "gzip")
{
resp->gunzip();
}
#ifdef USE_BROTLI
else if (coding == "br")
{
resp->brDecompress();
}
#endif
upgraded_ = true;
websockConnPtr_ =
std::make_shared<WebSocketConnectionImpl>(connPtr, false);
websockConnPtr_->setPingMessage("", std::chrono::seconds{30});
auto thisPtr = shared_from_this();
std::weak_ptr<WebSocketClientImpl> weakPtr = thisPtr;
websockConnPtr_->setMessageCallback(
[weakPtr](std::string &&message,
const WebSocketConnectionImplPtr &,
const WebSocketMessageType &type) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
thisPtr->messageCallback_(std::move(message), thisPtr, type);
});
requestCallback_(ReqResult::Ok, resp, thisPtr);
if (msgBuffer->readableBytes() > 0)
{
onRecvWsMessage(connPtr, msgBuffer);
}
}
else
{
return;
}
}
void WebSocketClientImpl::reconnect()
{
tcpClientPtr_.reset();
websockConnPtr_.reset();
upgraded_ = false;
connectToServerInLoop();
}
WebSocketClientImpl::WebSocketClientImpl(trantor::EventLoop *loop,
const trantor::InetAddress &addr,
bool useSSL,
bool useOldTLS,
bool validateCert)
: loop_(loop),
serverAddr_(addr),
useSSL_(useSSL),
useOldTLS_(useOldTLS),
validateCert_(validateCert)
{
if (addr.isUnspecified())
LOG_ERROR << "Bad IP passed to WebSocket client";
}
WebSocketClientImpl::WebSocketClientImpl(trantor::EventLoop *loop,
const std::string &hostString,
bool useOldTLS,
bool validateCert)
: loop_(loop), useOldTLS_(useOldTLS), validateCert_(validateCert)
{
auto lowerHost = hostString;
std::transform(lowerHost.begin(),
lowerHost.end(),
lowerHost.begin(),
[](unsigned char c) { return tolower(c); });
if (lowerHost.find("wss://") != std::string::npos)
{
useSSL_ = true;
lowerHost = lowerHost.substr(6);
}
else if (lowerHost.find("ws://") != std::string::npos)
{
useSSL_ = false;
lowerHost = lowerHost.substr(5);
}
else
{
return;
}
auto pos = lowerHost.find(']');
if (lowerHost[0] == '[' && pos != std::string::npos)
{
// ipv6
domain_ = lowerHost.substr(1, pos - 1);
if (lowerHost[pos + 1] == ':')
{
auto portStr = lowerHost.substr(pos + 2);
pos = portStr.find('/');
if (pos != std::string::npos)
{
portStr = portStr.substr(0, pos);
}
auto port = atoi(portStr.c_str());
if (port > 0 && port < 65536)
{
serverAddr_ = InetAddress(domain_, port, true);
}
}
else
{
if (useSSL_)
{
serverAddr_ = InetAddress(domain_, 443, true);
}
else
{
serverAddr_ = InetAddress(domain_, 80, true);
}
}
}
else
{
auto pos = lowerHost.find(':');
if (pos != std::string::npos)
{
domain_ = lowerHost.substr(0, pos);
auto portStr = lowerHost.substr(pos + 1);
pos = portStr.find('/');
if (pos != std::string::npos)
{
portStr = portStr.substr(0, pos);
}
auto port = atoi(portStr.c_str());
if (port > 0 && port < 65536)
{
serverAddr_ = InetAddress(domain_, port);
}
}
else
{
domain_ = lowerHost;
pos = domain_.find('/');
if (pos != std::string::npos)
{
domain_ = domain_.substr(0, pos);
}
if (useSSL_)
{
serverAddr_ = InetAddress(domain_, 443);
}
else
{
serverAddr_ = InetAddress(domain_, 80);
}
}
}
LOG_TRACE << "userSSL=" << useSSL_ << " domain=" << domain_;
}
void WebSocketClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr)
{
trantor::MsgBuffer buffer;
assert(upgradeRequest_);
auto implPtr = static_cast<HttpRequestImpl *>(upgradeRequest_.get());
implPtr->appendToBuffer(&buffer);
LOG_TRACE << "Send request:"
<< std::string(buffer.peek(), buffer.readableBytes());
connPtr->send(std::move(buffer));
}
void WebSocketClientImpl::connectToServer(
const HttpRequestPtr &request,
const WebSocketRequestCallback &callback)
{
assert(callback);
if (loop_->isInLoopThread())
{
upgradeRequest_ = request;
requestCallback_ = callback;
connectToServerInLoop();
}
else
{
auto thisPtr = shared_from_this();
loop_->queueInLoop([request, callback, thisPtr] {
thisPtr->upgradeRequest_ = request;
thisPtr->requestCallback_ = callback;
thisPtr->connectToServerInLoop();
});
}
}
void WebSocketClientImpl::setCertPath(const std::string &cert,
const std::string &key)
{
clientCertPath_ = cert;
clientKeyPath_ = key;
}
void WebSocketClientImpl::addSSLConfigs(
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
{
for (const auto &cmd : sslConfCmds)
{
sslConfCmds_.push_back(cmd);
}
}
WebSocketClientPtr WebSocketClient::newWebSocketClient(const std::string &ip,
uint16_t port,
bool useSSL,
trantor::EventLoop *loop,
bool useOldTLS,
bool validateCert)
{
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
return std::make_shared<WebSocketClientImpl>(
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
trantor::InetAddress(ip, port, isIpv6),
useSSL,
useOldTLS,
validateCert);
}
WebSocketClientPtr WebSocketClient::newWebSocketClient(
const std::string &hostString,
trantor::EventLoop *loop,
bool useOldTLS,
bool validateCert)
{
return std::make_shared<WebSocketClientImpl>(
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
hostString,
useOldTLS,
validateCert);
}
+117
View File
@@ -0,0 +1,117 @@
/**
*
* @file WebSocketClientImpl.h
* @author 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 <drogon/WebSocketClient.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/TcpClient.h>
#include <trantor/utils/NonCopyable.h>
#include <memory>
#include <string>
namespace drogon
{
class WebSocketClientImpl
: public WebSocketClient,
public std::enable_shared_from_this<WebSocketClientImpl>
{
public:
WebSocketConnectionPtr getConnection() override;
void setMessageHandler(
const std::function<void(std::string &&message,
const WebSocketClientPtr &,
const WebSocketMessageType &)> &callback)
override
{
messageCallback_ = callback;
}
void setConnectionClosedHandler(
const std::function<void(const WebSocketClientPtr &)> &callback)
override
{
connectionClosedCallback_ = callback;
}
void connectToServer(const HttpRequestPtr &request,
const WebSocketRequestCallback &callback) override;
void setCertPath(const std::string &cert, const std::string &key) override;
void addSSLConfigs(const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) override;
trantor::EventLoop *getLoop() override
{
return loop_;
}
WebSocketClientImpl(trantor::EventLoop *loop,
const trantor::InetAddress &addr,
bool useSSL = false,
bool useOldTLS = false,
bool validateCert = true);
WebSocketClientImpl(trantor::EventLoop *loop,
const std::string &hostString,
bool useOldTLS = false,
bool validateCert = true);
void stop() override;
~WebSocketClientImpl() override;
private:
std::shared_ptr<trantor::TcpClient> tcpClientPtr_;
trantor::EventLoop *loop_;
trantor::InetAddress serverAddr_;
std::string domain_;
bool useSSL_{false};
bool useOldTLS_{false};
bool validateCert_{true};
bool upgraded_{false};
bool stop_{false};
std::string wsKey_;
std::string wsAccept_;
std::string clientCertPath_;
std::string clientKeyPath_;
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
HttpRequestPtr upgradeRequest_;
std::function<void(std::string &&,
const WebSocketClientPtr &,
const WebSocketMessageType &)>
messageCallback_ = [](std::string &&,
const WebSocketClientPtr &,
const WebSocketMessageType &) {};
std::function<void(const WebSocketClientPtr &)> connectionClosedCallback_ =
[](const WebSocketClientPtr &) {};
WebSocketRequestCallback requestCallback_;
WebSocketConnectionImplPtr websockConnPtr_;
void connectToServerInLoop();
void sendReq(const trantor::TcpConnectionPtr &connPtr);
void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
void onRecvWsMessage(const trantor::TcpConnectionPtr &,
trantor::MsgBuffer *);
void reconnect();
void createTcpClient();
std::shared_ptr<trantor::Resolver> resolver_;
};
} // namespace drogon
@@ -0,0 +1,497 @@
/**
*
* @file WebSocketConnectionImpl.cc
* @author 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 "WebSocketConnectionImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <json/value.h>
#include <json/writer.h>
#include <thread>
#include <limits>
using namespace drogon;
WebSocketConnectionImpl::WebSocketConnectionImpl(
const trantor::TcpConnectionPtr &conn,
bool isServer)
: tcpConnectionPtr_(conn),
localAddr_(conn->localAddr()),
peerAddr_(conn->peerAddr()),
isServer_(isServer),
usingMask_(false)
{
}
WebSocketConnectionImpl::~WebSocketConnectionImpl()
{
shutdown();
}
void WebSocketConnectionImpl::send(const char *msg,
uint64_t len,
const WebSocketMessageType type)
{
unsigned char opcode;
if (type == WebSocketMessageType::Text)
opcode = 1;
else if (type == WebSocketMessageType::Binary)
opcode = 2;
else if (type == WebSocketMessageType::Close)
{
assert(len <= 125);
opcode = 8;
}
else if (type == WebSocketMessageType::Ping)
{
assert(len <= 125);
opcode = 9;
}
else if (type == WebSocketMessageType::Pong)
{
assert(len <= 125);
opcode = 10;
}
else
{
opcode = 0;
assert(0);
}
sendWsData(msg, len, opcode);
}
void WebSocketConnectionImpl::sendWsData(const char *msg,
uint64_t len,
unsigned char opcode)
{
LOG_TRACE << "send " << len << " bytes";
// Format the frame
std::string bytesFormatted;
bytesFormatted.resize(len + 10);
bytesFormatted[0] = char(0x80 | (opcode & 0x0f));
int indexStartRawData = -1;
if (len <= 125)
{
bytesFormatted[1] = static_cast<char>(len);
indexStartRawData = 2;
}
else if (len <= 65535)
{
bytesFormatted[1] = 126;
bytesFormatted[2] = ((len >> 8) & 255);
bytesFormatted[3] = ((len) & 255);
LOG_TRACE << "bytes[2]=" << (size_t)bytesFormatted[2];
LOG_TRACE << "bytes[3]=" << (size_t)bytesFormatted[3];
indexStartRawData = 4;
}
else
{
bytesFormatted[1] = 127;
bytesFormatted[2] = ((len >> 56) & 255);
bytesFormatted[3] = ((len >> 48) & 255);
bytesFormatted[4] = ((len >> 40) & 255);
bytesFormatted[5] = ((len >> 32) & 255);
bytesFormatted[6] = ((len >> 24) & 255);
bytesFormatted[7] = ((len >> 16) & 255);
bytesFormatted[8] = ((len >> 8) & 255);
bytesFormatted[9] = ((len) & 255);
indexStartRawData = 10;
}
if (!isServer_)
{
int random;
// Use the cached randomness if no one else is also using it. Otherwise
// generate one from scratch.
if (!usingMask_.exchange(true, std::memory_order_acq_rel))
{
if (masks_.empty())
{
masks_.resize(16);
bool status =
utils::secureRandomBytes(masks_.data(),
masks_.size() * sizeof(uint32_t));
if (status == false)
{
LOG_ERROR << "Failed to generate random numbers for "
"WebSocket mask";
abort();
}
}
random = masks_.back();
masks_.pop_back();
usingMask_.store(false, std::memory_order_release);
}
else
{
bool status = utils::secureRandomBytes(&random, sizeof(random));
if (status == false)
{
LOG_ERROR
<< "Failed to generate random numbers for WebSocket mask";
abort();
}
}
bytesFormatted[1] = (bytesFormatted[1] | 0x80);
bytesFormatted.resize(indexStartRawData + 4 + len);
memcpy(&bytesFormatted[indexStartRawData], &random, sizeof(random));
for (size_t i = 0; i < len; ++i)
{
bytesFormatted[indexStartRawData + 4 + i] =
(msg[i] ^ bytesFormatted[indexStartRawData + (i % 4)]);
}
}
else
{
bytesFormatted.resize(indexStartRawData);
bytesFormatted.append(msg, len);
}
tcpConnectionPtr_->send(std::move(bytesFormatted));
}
void WebSocketConnectionImpl::send(const std::string_view msg,
const WebSocketMessageType type)
{
send(msg.data(), msg.length(), type);
}
void WebSocketConnectionImpl::sendJson(const Json::Value &json,
const WebSocketMessageType type)
{
static std::once_flag once;
static Json::StreamWriterBuilder builder;
std::call_once(once, []() {
builder["commentStyle"] = "None";
builder["indentation"] = "";
if (!app().isUnicodeEscapingUsedInJson())
{
builder["emitUTF8"] = true;
}
auto &precision = app().getFloatPrecisionInJson();
if (precision.first != 0)
{
builder["precision"] = precision.first;
builder["precisionType"] = precision.second;
}
});
auto msg = writeString(builder, json);
send(msg.data(), msg.length(), type);
}
const trantor::InetAddress &WebSocketConnectionImpl::localAddr() const
{
return localAddr_;
}
const trantor::InetAddress &WebSocketConnectionImpl::peerAddr() const
{
return peerAddr_;
}
bool WebSocketConnectionImpl::connected() const
{
return tcpConnectionPtr_->connected();
}
bool WebSocketConnectionImpl::disconnected() const
{
return tcpConnectionPtr_->disconnected();
}
void WebSocketConnectionImpl::WebSocketConnectionImpl::shutdown(
const CloseCode code,
const std::string &reason)
{
tcpConnectionPtr_->getLoop()->invalidateTimer(pingTimerId_);
if (!tcpConnectionPtr_->connected())
return;
std::string message;
message.resize(reason.length() + 2);
auto c = htons(static_cast<unsigned short>(code));
memcpy(&message[0], &c, 2);
if (!reason.empty())
memcpy(&message[2], reason.data(), reason.length());
send(message, WebSocketMessageType::Close);
tcpConnectionPtr_->shutdown();
}
void WebSocketConnectionImpl::WebSocketConnectionImpl::forceClose()
{
tcpConnectionPtr_->forceClose();
}
void WebSocketConnectionImpl::setPingMessage(
const std::string &message,
const std::chrono::duration<double> &interval)
{
auto loop = tcpConnectionPtr_->getLoop();
if (loop->isInLoopThread())
{
setPingMessageInLoop(std::string{message}, interval);
}
else
{
loop->queueInLoop(
[msg = message, interval, thisPtr = shared_from_this()]() mutable {
thisPtr->setPingMessageInLoop(std::move(msg), interval);
});
}
}
void WebSocketConnectionImpl::disablePing()
{
auto loop = tcpConnectionPtr_->getLoop();
if (loop->isInLoopThread())
{
disablePingInLoop();
}
else
{
loop->queueInLoop(
[thisPtr = shared_from_this()]() { thisPtr->disablePingInLoop(); });
}
}
bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
{
// According to the rfc6455
gotAll_ = false;
while (buffer->readableBytes() >= 2)
{
unsigned char opcode = (*buffer)[0] & 0x0f;
bool isControlFrame = false;
switch (opcode)
{
case 0:
LOG_TRACE << "continuation frame";
break;
case 1:
type_ = WebSocketMessageType::Text;
break;
case 2:
type_ = WebSocketMessageType::Binary;
break;
case 8:
type_ = WebSocketMessageType::Close;
isControlFrame = true;
break;
case 9:
type_ = WebSocketMessageType::Ping;
isControlFrame = true;
break;
case 10:
type_ = WebSocketMessageType::Pong;
isControlFrame = true;
break;
default:
LOG_ERROR << "Unknown frame type";
return false;
break;
}
bool isFin = (((*buffer)[0] & 0x80) == 0x80);
if (!isFin && isControlFrame)
{
// rfc6455-5.5
LOG_ERROR << "Bad frame: all control frames MUST NOT be fragmented";
return false;
}
auto secondByte = (*buffer)[1];
size_t length = secondByte & 127;
int isMasked = (secondByte & 0x80);
if (isMasked != 0)
{
LOG_TRACE << "data encoded!";
}
else
LOG_TRACE << "plain data";
size_t indexFirstMask = 2;
if (length == 126)
{
indexFirstMask = 4;
}
else if (length == 127)
{
indexFirstMask = 10;
}
if (indexFirstMask > 2)
{
if (buffer->readableBytes() < indexFirstMask)
{
// Not enough data yet, wait for more.
return true;
}
if (isControlFrame)
{
// rfc6455-5.5
LOG_ERROR << "Bad frame: all control frames MUST have a "
"payload length "
"of 125 bytes or less";
return false;
}
if (indexFirstMask == 4)
{
length = (unsigned char)(*buffer)[2];
length = (length << 8) + (unsigned char)(*buffer)[3];
}
else if (indexFirstMask == 10)
{
length = 0;
for (int i = 2; i <= 9; ++i)
{
if (length > ((std::numeric_limits<size_t>::max)() >> 8))
{
LOG_ERROR
<< "Payload length too large to handle safely";
return false;
}
length = (length << 8) + (unsigned char)(*buffer)[i];
}
}
else
{
LOG_ERROR << "Websock parsing failed!";
return false;
}
}
if (isMasked != 0)
{
// The message is sent by the client, check the length
if (length > HttpAppFrameworkImpl::instance()
.getClientMaxWebSocketMessageSize())
{
LOG_ERROR << "The size of the WebSocket message is too large!";
buffer->retrieveAll();
return false;
}
if (buffer->readableBytes() >= (indexFirstMask + 4 + length))
{
auto masks = buffer->peek() + indexFirstMask;
auto indexFirstDataByte = indexFirstMask + 4;
auto rawData = buffer->peek() + indexFirstDataByte;
auto oldLen = message_.length();
message_.resize(oldLen + length);
for (size_t i = 0; i < length; ++i)
{
message_[oldLen + i] = (rawData[i] ^ masks[i % 4]);
}
buffer->retrieve(indexFirstMask + 4 + length);
if (isFin)
{
gotAll_ = true;
return true;
}
}
else
{
// Not enough data yet, wait for more.
return true;
}
}
else
{
if (buffer->readableBytes() >= (indexFirstMask + length))
{
auto rawData = buffer->peek() + indexFirstMask;
message_.append(rawData, length);
buffer->retrieve(indexFirstMask + length);
if (isFin)
{
gotAll_ = true;
return true;
}
}
else
{
// Not enough data yet, wait for more.
return true;
}
}
}
return true;
}
void WebSocketConnectionImpl::onNewMessage(
const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *buffer)
{
auto self = shared_from_this();
while (buffer->readableBytes() > 0)
{
auto success = parser_.parse(buffer);
if (success)
{
std::string message;
WebSocketMessageType type;
if (parser_.gotAll(message, type))
{
if (type == WebSocketMessageType::Ping)
{
// ping
send(message, WebSocketMessageType::Pong);
}
else if (type == WebSocketMessageType::Close)
{
// close
connPtr->shutdown();
}
else if (type == WebSocketMessageType::Unknown)
{
return;
}
// LOG_TRACE << "new message received: " << message
// << "\n(type=" << (int)type << ")";
messageCallback_(std::move(message), self, type);
}
else
{
return;
}
}
else
{
// Websock error!
connPtr->shutdown();
return;
}
}
return;
}
void WebSocketConnectionImpl::disablePingInLoop()
{
if (pingTimerId_ != trantor::InvalidTimerId)
{
tcpConnectionPtr_->getLoop()->invalidateTimer(pingTimerId_);
}
}
void WebSocketConnectionImpl::setPingMessageInLoop(
std::string &&message,
const std::chrono::duration<double> &interval)
{
std::weak_ptr<WebSocketConnectionImpl> weakPtr = shared_from_this();
disablePingInLoop();
pingTimerId_ = tcpConnectionPtr_->getLoop()->runEvery(
interval.count(), [weakPtr, message = std::move(message)]() {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
thisPtr->send(message, WebSocketMessageType::Ping);
}
});
}
@@ -0,0 +1,134 @@
/**
*
* @file WebSocketConnectionImpl.h
* @author 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 <drogon/WebSocketConnection.h>
#include <json/value.h>
#include <string_view>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/TcpConnection.h>
namespace drogon
{
class WebSocketConnectionImpl;
using WebSocketConnectionImplPtr = std::shared_ptr<WebSocketConnectionImpl>;
class WebSocketMessageParser
{
public:
bool parse(trantor::MsgBuffer *buffer);
bool gotAll(std::string &message, WebSocketMessageType &type)
{
assert(message.empty());
if (!gotAll_)
return false;
message.swap(message_);
type = type_;
return true;
}
private:
std::string message_;
WebSocketMessageType type_;
bool gotAll_{false};
};
class WebSocketConnectionImpl final
: public WebSocketConnection,
public std::enable_shared_from_this<WebSocketConnectionImpl>,
public trantor::NonCopyable
{
public:
explicit WebSocketConnectionImpl(const trantor::TcpConnectionPtr &conn,
bool isServer = true);
~WebSocketConnectionImpl() override;
void send(
const char *msg,
uint64_t len,
const WebSocketMessageType type = WebSocketMessageType::Text) override;
void send(
std::string_view msg,
const WebSocketMessageType type = WebSocketMessageType::Text) override;
void sendJson(
const Json::Value &json,
const WebSocketMessageType type = WebSocketMessageType::Text) override;
const trantor::InetAddress &localAddr() const override;
const trantor::InetAddress &peerAddr() const override;
bool connected() const override;
bool disconnected() const override;
void shutdown(const CloseCode code = CloseCode::kNormalClosure,
const std::string &reason = "") override; // close write
void forceClose() override; // close
void setPingMessage(const std::string &message,
const std::chrono::duration<double> &interval) override;
void disablePing() override;
void setMessageCallback(
const std::function<void(std::string &&,
const WebSocketConnectionImplPtr &,
const WebSocketMessageType &)> &callback)
{
messageCallback_ = callback;
}
void setCloseCallback(
const std::function<void(const WebSocketConnectionImplPtr &)> &callback)
{
closeCallback_ = callback;
}
void onNewMessage(const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *buffer);
void onClose()
{
if (pingTimerId_ != trantor::InvalidTimerId)
tcpConnectionPtr_->getLoop()->invalidateTimer(pingTimerId_);
closeCallback_(shared_from_this());
}
private:
trantor::TcpConnectionPtr tcpConnectionPtr_;
trantor::InetAddress localAddr_;
trantor::InetAddress peerAddr_;
bool isServer_{true};
WebSocketMessageParser parser_;
trantor::TimerId pingTimerId_{trantor::InvalidTimerId};
std::vector<uint32_t> masks_;
std::atomic<bool> usingMask_;
std::function<void(std::string &&,
const WebSocketConnectionImplPtr &,
const WebSocketMessageType &)>
messageCallback_ = [](std::string &&,
const WebSocketConnectionImplPtr &,
const WebSocketMessageType &) {};
std::function<void(const WebSocketConnectionImplPtr &)> closeCallback_ =
[](const WebSocketConnectionImplPtr &) {};
void sendWsData(const char *msg, uint64_t len, unsigned char opcode);
void disablePingInLoop();
void setPingMessageInLoop(std::string &&message,
const std::chrono::duration<double> &interval);
};
} // namespace drogon
+118
View File
@@ -0,0 +1,118 @@
#include "YamlConfigAdapter.h"
#ifdef HAS_YAML_CPP
#include <yaml-cpp/yaml.h>
#endif
using namespace drogon;
#ifdef HAS_YAML_CPP
namespace YAML
{
static bool yaml2json(const Node &node, Json::Value &jsonValue)
{
if (node.IsNull())
{
return false;
}
else if (node.IsScalar())
{
if (node.Tag() != "!")
{
try
{
jsonValue = node.as<Json::Value::Int64>();
return true;
}
catch (const YAML::BadConversion &e)
{
}
try
{
jsonValue = node.as<double>();
return true;
}
catch (const YAML::BadConversion &e)
{
}
try
{
jsonValue = node.as<bool>();
return true;
}
catch (const YAML::BadConversion &e)
{
}
}
Json::Value v(node.Scalar());
jsonValue.swapPayload(v);
return true;
}
else if (node.IsSequence())
{
for (std::size_t i = 0; i < node.size(); i++)
{
Json::Value v;
if (yaml2json(node[i], v))
{
jsonValue.append(v);
}
else
{
return false;
}
}
return true;
}
else if (node.IsMap())
{
for (YAML::const_iterator it = node.begin(); it != node.end(); ++it)
{
Json::Value v;
if (yaml2json(it->second, v))
{
jsonValue[it->first.Scalar()] = v;
}
else
{
return false;
}
}
return true;
}
return false;
}
template <>
struct convert<Json::Value>
{
static bool decode(const Node &node, Json::Value &rhs)
{
return yaml2json(node, rhs);
};
};
} // namespace YAML
#endif
Json::Value YamlConfigAdapter::getJson(const std::string &content) const
noexcept(false)
{
#if HAS_YAML_CPP
// parse yaml file
YAML::Node config = YAML::Load(content);
if (!config.IsNull())
{
return config.as<Json::Value>();
}
else
return Json::Value();
#else
throw std::runtime_error("please install yaml-cpp library");
#endif
}
std::vector<std::string> YamlConfigAdapter::getExtensions() const
{
return {"yaml", "yml"};
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "ConfigAdapter.h"
namespace drogon
{
class YamlConfigAdapter : public ConfigAdapter
{
public:
YamlConfigAdapter() = default;
~YamlConfigAdapter() override = default;
Json::Value getJson(const std::string &content) const
noexcept(false) override;
std::vector<std::string> getExtensions() const override;
};
} // namespace drogon
+268
View File
@@ -0,0 +1,268 @@
#include <drogon/drogon_test.h>
#include <set>
#include <future>
#include <condition_variable>
namespace drogon
{
namespace test
{
std::mutex ThreadSafeStream::mtx_;
namespace internal
{
std::mutex mtxRegister;
std::mutex mtxTestStats;
bool testHasPrinted = false;
std::set<Case *> registeredTests;
std::promise<void> allTestRan;
std::atomic<size_t> numAssertions;
std::atomic<size_t> numCorrectAssertions;
size_t numTestCases;
std::atomic<size_t> numFailedTestCases;
bool printSuccessfulTests;
void registerCase(Case *test)
{
std::unique_lock<std::mutex> l(mtxRegister);
registeredTests.insert(test);
}
void unregisterCase(Case *test)
{
std::unique_lock<std::mutex> l(mtxRegister);
registeredTests.erase(test);
if (registeredTests.empty())
allTestRan.set_value();
}
static std::string leftpad(const std::string &str, size_t len)
{
if (len <= str.size())
return str;
return std::string(len - str.size(), ' ') + str;
}
std::string prettifyString(const std::string_view sv, size_t maxLength)
{
if (sv.size() <= maxLength)
return "\"" + escapeString(sv) + "\"";
const std::string msg = "...\" (truncated)";
return "\"" + escapeString(sv.substr(0, maxLength)) + msg;
}
} // namespace internal
static void printHelp(std::string_view argv0)
{
print() << "A Drogon Test application:\n\n"
<< "Usage: " << argv0 << " [options]\n"
<< "options:\n"
<< " -r Run a specific test\n"
<< " -s Print successful tests\n"
<< " -l List available tests\n"
<< " -h | --help Print this help message\n";
}
void printTestStats()
{
std::unique_lock<std::mutex> lk(internal::mtxTestStats);
if (internal::testHasPrinted)
return;
const size_t successAssertions = internal::numCorrectAssertions;
const size_t totalAssertions = internal::numAssertions;
const size_t successTests =
internal::numTestCases - internal::numFailedTestCases;
const size_t totalTests = internal::numTestCases;
float ratio;
if (totalAssertions != 0)
ratio = (float)successTests / totalTests;
else
ratio = 1;
const size_t barSize = 80;
auto greenBar = size_t(barSize * ratio);
auto redBar = size_t(barSize * (1 - ratio));
if (greenBar + redBar != barSize)
{
float fraction = (ratio * barSize) - (size_t)(ratio * barSize);
if (fraction >= 0.5f)
greenBar++;
else
redBar++;
}
if (successAssertions != totalAssertions && redBar == 0)
{
redBar = 1;
greenBar--;
}
print() << "\n\x1B[0;31m" << std::string(redBar, '=') << "\x1B[0;32m"
<< std::string(greenBar, '=') << "\x1B[0m\n";
if (successAssertions == totalAssertions)
{
print() << "\x1B[1;32m All tests passed\x1B[0m (" << totalAssertions
<< " assertions in " << totalTests << " tests cases).\n";
}
else
{
std::string totalAssertsionStr = std::to_string(totalAssertions);
std::string successAssertionsStr = std::to_string(successAssertions);
std::string failedAssertsionStr =
std::to_string(totalAssertions - successAssertions);
std::string totalTestsStr = std::to_string(totalTests);
std::string successTestsStr = std::to_string(successTests);
std::string failedTestsStr = std::to_string(totalTests - successTests);
const size_t totalLen =
(std::max)(totalAssertsionStr.size(), totalTestsStr.size());
const size_t successLen =
(std::max)(successAssertionsStr.size(), successTestsStr.size());
const size_t failedLen =
(std::max)(failedAssertsionStr.size(), failedTestsStr.size());
using internal::leftpad;
print() << "assertions: " << leftpad(totalAssertsionStr, totalLen)
<< " | \x1B[0;32m" << leftpad(successAssertionsStr, successLen)
<< " passed\x1B[0m | \x1B[0;31m"
<< leftpad(failedAssertsionStr, failedLen) << " failed\x1B[0m\n"
<< "test cases: " << leftpad(totalTestsStr, totalLen)
<< " | \x1B[0;32m" << leftpad(successTestsStr, successLen)
<< " passed\x1B[0m | \x1B[0;31m"
<< leftpad(failedTestsStr, failedLen) << " failed\x1B[0m\n";
}
internal::testHasPrinted = true;
}
int run(int argc, char **argv)
{
internal::numCorrectAssertions = 0;
internal::numAssertions = 0;
internal::numFailedTestCases = 0;
internal::numTestCases = 0;
internal::printSuccessfulTests = false;
std::string targetTest;
bool listTests = false;
for (int i = 1; i < argc; i++)
{
const std::string param = argv[i];
if (param == "-r")
{
if (!targetTest.empty())
{
printErr() << "Only one test can be specified to run\n";
exit(1);
}
else if (i + 1 >= argc)
{
printErr() << "Missing test name after -r.\n";
exit(1);
}
targetTest = argv[i + 1];
i++;
}
else if (param == "-h" || param == "--help")
{
printHelp(argv[0]);
exit(0);
}
else if (param == "-s")
{
internal::printSuccessfulTests = true;
}
else if (param == "-l")
{
listTests = true;
}
else
{
printErr() << "Unknown parameter: " << param << "\n";
printHelp(argv[0]);
exit(1);
}
}
auto classNames = DrClassMap::getAllClassName();
if (listTests)
{
print() << "Available Tests:\n";
for (const auto &name : classNames)
{
if (name.find(DROGON_TESTCASE_PREIX_STR_) == 0)
{
auto test =
std::unique_ptr<DrObjectBase>(DrClassMap::newObject(name));
auto ptr = dynamic_cast<TestCase *>(test.get());
if (ptr == nullptr)
continue;
print() << " " << ptr->name() << "\n";
}
}
exit(0);
}
std::vector<std::shared_ptr<TestCase>> testCases;
// NOTE: Registering a dummy case prevents the test-end signal to be
// emitted too early as there's always an case that hasn't finish
std::shared_ptr<Case> dummyCase = std::make_shared<Case>("__dummy_dummy_");
for (const auto &name : classNames)
{
if (name.find(DROGON_TESTCASE_PREIX_STR_) == 0)
{
auto obj =
std::shared_ptr<DrObjectBase>(DrClassMap::newObject(name));
auto test = std::dynamic_pointer_cast<TestCase>(obj);
if (test == nullptr)
{
LOG_WARN << "Class " << name
<< " seems to be a test case. But type information "
"disagrees.";
continue;
}
if (targetTest.empty() || test->name() == targetTest)
{
internal::numTestCases++;
test->doTest_(std::make_shared<Case>(test->name()));
testCases.emplace_back(std::move(test));
}
}
}
dummyCase = {};
if (targetTest != "" && internal::numTestCases == 0)
{
printErr() << "Cannot find test named " << targetTest << "\n";
exit(1);
}
std::unique_lock<std::mutex> l(internal::mtxRegister);
if (internal::registeredTests.empty() == false)
{
auto fut = internal::allTestRan.get_future();
l.unlock();
fut.get();
assert(internal::registeredTests.empty());
}
testCases.clear();
printTestStats();
return internal::numCorrectAssertions != internal::numAssertions;
}
ThreadSafeStream print()
{
return ThreadSafeStream(std::cout);
}
ThreadSafeStream printErr()
{
return ThreadSafeStream(std::cerr);
}
} // namespace test
} // namespace drogon
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <memory>
#include <functional>
namespace drogon
{
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
class Cookie;
class Session;
using SessionPtr = std::shared_ptr<Session>;
class UploadFile;
class WebSocketControllerBase;
using WebSocketControllerBasePtr = std::shared_ptr<WebSocketControllerBase>;
class HttpFilterBase;
using HttpFilterBasePtr = std::shared_ptr<HttpFilterBase>;
class HttpMiddlewareBase;
using HttpMiddlewareBasePtr = std::shared_ptr<HttpMiddlewareBase>;
class HttpSimpleControllerBase;
using HttpSimpleControllerBasePtr = std::shared_ptr<HttpSimpleControllerBase>;
class HttpRequestImpl;
using HttpRequestImplPtr = std::shared_ptr<HttpRequestImpl>;
class HttpResponseImpl;
using HttpResponseImplPtr = std::shared_ptr<HttpResponseImpl>;
class WebSocketConnectionImpl;
using WebSocketConnectionImplPtr = std::shared_ptr<WebSocketConnectionImpl>;
class HttpRequestParser;
class PluginsManager;
class ListenerManager;
class SharedLibManager;
class SessionManager;
class HttpServer;
namespace orm
{
class DbClient;
using DbClientPtr = std::shared_ptr<DbClient>;
class DbClientManager;
} // namespace orm
namespace nosql
{
class RedisClient;
using RedisClientPtr = std::shared_ptr<RedisClient>;
class RedisClientManager;
} // namespace nosql
} // namespace drogon
namespace trantor
{
class EventLoop;
class TcpConnection;
using TcpConnectionPtr = std::shared_ptr<TcpConnection>;
class Resolver;
class AsyncFileLogger;
} // namespace trantor
namespace drogon
{
using HttpAsyncCallback =
std::function<void(const HttpRequestImplPtr &,
std::function<void(const HttpResponsePtr &)> &&)>;
using WebSocketNewAsyncCallback =
std::function<void(const HttpRequestImplPtr &,
std::function<void(const HttpResponsePtr &)> &&,
const WebSocketConnectionImplPtr &)>;
} // namespace drogon