复现已有算法
This commit is contained in:
+210
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 ¤tBucket = 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
@@ -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();
|
||||
}
|
||||
+1375
File diff suppressed because it is too large
Load Diff
@@ -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 ®isterWebSocketController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints) override;
|
||||
HttpAppFramework ®isterWebSocketControllerRegex(
|
||||
const std::string ®Exp,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints) override;
|
||||
HttpAppFramework ®isterHttpSimpleController(
|
||||
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 ®isterBeginningAdvice(
|
||||
const std::function<void()> &advice) override
|
||||
{
|
||||
beginningAdvices_.emplace_back(advice);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework ®isterNewConnectionAdvice(
|
||||
const std::function<bool(const trantor::InetAddress &,
|
||||
const trantor::InetAddress &)> &advice)
|
||||
override;
|
||||
|
||||
HttpAppFramework ®isterHttpResponseCreationAdvice(
|
||||
const std::function<void(const HttpResponsePtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterSyncAdvice(
|
||||
const std::function<HttpResponsePtr(const HttpRequestPtr &)> &advice)
|
||||
override;
|
||||
|
||||
HttpAppFramework ®isterPreRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPostRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreHandlingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPostRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreHandlingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPostHandlingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
const HttpResponsePtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreSendingAdvice(
|
||||
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 ®isterSessionStartAdvice(
|
||||
const AdviceStartSessionCallback &advice) override
|
||||
{
|
||||
sessionStartAdvices_.emplace_back(advice);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework ®isterSessionDestroyAdvice(
|
||||
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 ®isterCustomExtensionMime(
|
||||
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 ®Exp,
|
||||
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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 ¶msVector = 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
|
||||
@@ -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
|
||||
@@ -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 ®Exp,
|
||||
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 ®Exp,
|
||||
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 ¶Place : 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);
|
||||
}
|
||||
@@ -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 ®Exp,
|
||||
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 ®Exp,
|
||||
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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
+1116
File diff suppressed because it is too large
Load Diff
+750
@@ -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> &¶ms) 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> ¶meters() 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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
+1331
File diff suppressed because it is too large
Load Diff
+166
@@ -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> ¶mPack,
|
||||
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
@@ -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
@@ -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
@@ -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(""", 6);
|
||||
break;
|
||||
case '&':
|
||||
ret.append("&", 5);
|
||||
break;
|
||||
case '<':
|
||||
ret.append("<", 4);
|
||||
break;
|
||||
case '>':
|
||||
ret.append(">", 4);
|
||||
break;
|
||||
default:
|
||||
ret.push_back(*str);
|
||||
break;
|
||||
}
|
||||
++str;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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!";
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
+1326
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user