复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+130
View File
@@ -0,0 +1,130 @@
/**
*
* Attribute.h
* armstrong@sweelia.com
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Logger.h>
#include <map>
#include <memory>
#include <any>
namespace drogon
{
/**
* @brief This class represents the attributes stored in the HTTP request.
* One can add/get any type of data to/from an Attributes object.
*/
class Attributes
{
public:
/**
* @brief Get the data identified by the key parameter.
* @note if the data is not found, a default value is returned.
* For example:
* @code
auto &userName = attributesPtr->get<std::string>("user name");
@endcode
*/
template <typename T>
const T &get(const std::string &key) const
{
static const T nullVal = T();
auto it = attributesMap_.find(key);
if (it != attributesMap_.end())
{
if (typeid(T) == it->second.type())
{
return *(std::any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
return nullVal;
}
/**
* @brief Get the 'any' object identified by the given key
*/
std::any &operator[](const std::string &key)
{
return attributesMap_[key];
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
attributesPtr->insert("user name", userNameString);
@endcode
*/
void insert(const std::string &key, const std::any &obj)
{
attributesMap_[key] = obj;
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
attributesPtr->insert("user name", userNameString);
@endcode
*/
void insert(const std::string &key, std::any &&obj)
{
attributesMap_[key] = std::move(obj);
}
/**
* @brief Erase the data identified by the given key.
*/
void erase(const std::string &key)
{
attributesMap_.erase(key);
}
/**
* @brief Return true if the data identified by the key exists.
*/
bool find(const std::string &key)
{
if (attributesMap_.find(key) == attributesMap_.end())
{
return false;
}
return true;
}
/**
* @brief Clear all attributes.
*/
void clear()
{
attributesMap_.clear();
}
/**
* @brief Constructor, usually called by the framework
*/
Attributes() = default;
private:
using AttributesMap = std::map<std::string, std::any>;
AttributesMap attributesMap_;
};
using AttributesPtr = std::shared_ptr<Attributes>;
} // namespace drogon
+562
View File
@@ -0,0 +1,562 @@
/**
*
* @file CacheMap.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/EventLoop.h>
#include <trantor/utils/Logger.h>
#include <atomic>
#include <deque>
#include <map>
#include <mutex>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <future>
#include <assert.h>
#define WHEELS_NUM 4
#define BUCKET_NUM_PER_WHEEL 200
#define TICK_INTERVAL 1.0
namespace drogon
{
/**
* @brief A utility class for CacheMap
*/
class CallbackEntry
{
public:
CallbackEntry(std::function<void()> cb) : cb_(std::move(cb))
{
}
~CallbackEntry()
{
cb_();
}
private:
std::function<void()> cb_;
};
using CallbackEntryPtr = std::shared_ptr<CallbackEntry>;
using WeakCallbackEntryPtr = std::weak_ptr<CallbackEntry>;
using CallbackBucket = std::unordered_set<CallbackEntryPtr>;
using CallbackBucketQueue = std::deque<CallbackBucket>;
/**
* @brief Cache Map
*
* @tparam T1 The keyword type.
* @tparam T2 The value type.
* @note
* Four wheels with 200 buckets per wheel means the cache map can work with a
* timeout up to 200^4 seconds (about 50 years).
*/
template <typename T1, typename T2>
class CacheMap
{
public:
/// constructor
/**
* @param loop
* eventloop pointer
* @param tickInterval
* second
* @param wheelsNum
* number of wheels
* @param bucketsNumPerWheel
* buckets number per wheel
* @param fnOnInsert
* function to execute on insertion
* @param fnOnErase
* function to execute on erase
* @details The max delay of the CacheMap is about
* tickInterval*(bucketsNumPerWheel^wheelsNum) seconds.
*/
CacheMap(trantor::EventLoop *loop,
float tickInterval = TICK_INTERVAL,
size_t wheelsNum = WHEELS_NUM,
size_t bucketsNumPerWheel = BUCKET_NUM_PER_WHEEL,
std::function<void(const T1 &)> fnOnInsert = nullptr,
std::function<void(const T1 &)> fnOnErase = nullptr)
: loop_(loop),
tickInterval_(tickInterval),
wheelsNumber_(wheelsNum),
bucketsNumPerWheel_(bucketsNumPerWheel),
ctrlBlockPtr_(std::make_shared<ControlBlock>()),
fnOnInsert_(fnOnInsert),
fnOnErase_(fnOnErase)
{
wheels_.resize(wheelsNumber_);
for (size_t i = 0; i < wheelsNumber_; ++i)
{
wheels_[i].resize(bucketsNumPerWheel_);
}
if (tickInterval_ > 0 && wheelsNumber_ > 0 && bucketsNumPerWheel_ > 0)
{
timerId_ = loop_->runEvery(
tickInterval_, [this, ctrlBlockPtr = ctrlBlockPtr_]() {
std::lock_guard<std::mutex> lock(ctrlBlockPtr->mtx);
if (ctrlBlockPtr->destructed)
return;
size_t t = ++ticksCounter_;
size_t pow = 1;
for (size_t i = 0; i < wheelsNumber_; ++i)
{
if ((t % pow) == 0)
{
CallbackBucket tmp;
{
std::lock_guard<std::mutex> lock(bucketMutex_);
// use tmp val to make this critical area as
// short as possible.
wheels_[i].front().swap(tmp);
wheels_[i].pop_front();
wheels_[i].push_back(CallbackBucket());
}
}
pow = pow * bucketsNumPerWheel_;
}
});
loop_->runOnQuit([ctrlBlockPtr = ctrlBlockPtr_] {
std::lock_guard<std::mutex> lock(ctrlBlockPtr->mtx);
ctrlBlockPtr->loopEnded = true;
});
}
else
{
noWheels_ = true;
}
};
~CacheMap()
{
std::lock_guard<std::mutex> lock(ctrlBlockPtr_->mtx);
ctrlBlockPtr_->destructed = true;
map_.clear();
if (!ctrlBlockPtr_->loopEnded)
{
loop_->invalidateTimer(timerId_);
}
for (auto iter = wheels_.rbegin(); iter != wheels_.rend(); ++iter)
{
iter->clear();
}
LOG_TRACE << "CacheMap destruct!";
}
struct MapValue
{
MapValue(const T2 &value,
size_t timeout,
std::function<void()> &&callback)
: value_(value),
timeout_(timeout),
timeoutCallback_(std::move(callback))
{
}
MapValue(T2 &&value, size_t timeout, std::function<void()> &&callback)
: value_(std::move(value)),
timeout_(timeout),
timeoutCallback_(std::move(callback))
{
}
MapValue(T2 &&value, size_t timeout)
: value_(std::move(value)), timeout_(timeout)
{
}
MapValue(const T2 &value, size_t timeout)
: value_(value), timeout_(timeout)
{
}
MapValue(T2 &&value) : value_(std::move(value))
{
}
MapValue(const T2 &value) : value_(value)
{
}
MapValue() = default;
T2 value_;
size_t timeout_{0};
std::function<void()> timeoutCallback_;
WeakCallbackEntryPtr weakEntryPtr_;
};
/**
* @brief Insert a key-value pair into the cache.
*
* @param key The key
* @param value The value
* @param timeout The timeout in seconds, if timeout > 0, the value will be
* erased within the 'timeout' seconds after the last access. If the timeout
* is zero, the value exists until being removed explicitly.
* @param timeoutCallback is called when the timeout expires.
*/
void insert(const T1 &key,
T2 &&value,
size_t timeout = 0,
std::function<void()> timeoutCallback = std::function<void()>())
{
if (timeout > 0)
{
MapValue v{std::move(value), timeout, std::move(timeoutCallback)};
std::lock_guard<std::mutex> lock(mtx_);
map_.insert(std::make_pair(key, std::move(v)));
eraseAfter(timeout, key);
}
else
{
MapValue v{std::move(value)};
std::lock_guard<std::mutex> lock(mtx_);
map_.insert(std::make_pair(key, std::move(v)));
}
if (fnOnInsert_)
fnOnInsert_(key);
}
/**
* @brief Insert a key-value pair into the cache.
*
* @param key The key
* @param value The value
* @param timeout The timeout in seconds, if timeout > 0, the value will be
* erased within the 'timeout' seconds after the last access. If the timeout
* is zero, the value exists until being removed explicitly.
* @param timeoutCallback is called when the timeout expires.
*/
void insert(const T1 &key,
const T2 &value,
size_t timeout = 0,
std::function<void()> timeoutCallback = std::function<void()>())
{
if (timeout > 0)
{
MapValue v{value, timeout, std::move(timeoutCallback)};
std::lock_guard<std::mutex> lock(mtx_);
map_.insert(std::make_pair(key, std::move(v)));
eraseAfter(timeout, key);
}
else
{
MapValue v{value};
std::lock_guard<std::mutex> lock(mtx_);
map_.insert(std::make_pair(key, std::move(v)));
}
if (fnOnInsert_)
fnOnInsert_(key);
}
/**
* @brief Return the value of the keyword.
*
* @param key
* @return T2
* @note This function returns a copy of the data in the cache. If the data
* is not found, a default T2 type value is returned and nothing is inserted
* into the cache.
*/
T2 operator[](const T1 &key)
{
size_t timeout = 0;
std::lock_guard<std::mutex> lock(mtx_);
auto iter = map_.find(key);
if (iter != map_.end())
{
timeout = iter->second.timeout_;
if (timeout > 0)
eraseAfter(timeout, key);
return iter->second.value_;
}
return T2();
}
/**
* @brief Modify or visit the data identified by the key parameter.
*
* @tparam Callable the type of the handler.
* @param key
* @param handler A callable that can modify or visit the data. The
* signature of the handler should be equivalent to 'void(T2&)' or
* 'void(const T2&)'
* @param timeout In seconds.
*
* @note This function is multiple-thread safe. if the data identified by
* the key doesn't exist, a new one is created and passed to the handler and
* stored in the cache with the timeout parameter. The changing of the data
* is protected by the mutex of the cache.
*
*/
template <typename Callable>
void modify(const T1 &key, Callable &&handler, size_t timeout = 0)
{
{
std::lock_guard<std::mutex> lock(mtx_);
auto iter = map_.find(key);
if (iter != map_.end())
{
timeout = iter->second.timeout_;
handler(iter->second.value_);
if (timeout > 0)
eraseAfter(timeout, key);
return;
}
MapValue v{T2(), timeout};
handler(v.value_);
map_.insert(std::make_pair(key, std::move(v)));
if (timeout > 0)
{
eraseAfter(timeout, key);
}
}
if (fnOnInsert_)
fnOnInsert_(key);
}
/// Check if the value of the keyword exists
bool find(const T1 &key)
{
size_t timeout = 0;
bool flag = false;
std::lock_guard<std::mutex> lock(mtx_);
auto iter = map_.find(key);
if (iter != map_.end())
{
timeout = iter->second.timeout_;
flag = true;
}
if (timeout > 0)
eraseAfter(timeout, key);
return flag;
}
/// Atomically find and get the value of a keyword
/**
* Return true when the value is found, and the value
* is assigned to the value argument.
*/
bool findAndFetch(const T1 &key, T2 &value)
{
size_t timeout = 0;
bool flag = false;
std::lock_guard<std::mutex> lock(mtx_);
auto iter = map_.find(key);
if (iter != map_.end())
{
timeout = iter->second.timeout_;
flag = true;
value = iter->second.value_;
}
if (timeout > 0)
eraseAfter(timeout, key);
return flag;
}
/// Erase the value of the keyword.
/**
* @param key the keyword.
* @note This function does not cause the timeout callback to be executed.
*/
void erase(const T1 &key)
{
// in this case,we don't evoke the timeout callback;
{
std::lock_guard<std::mutex> lock(mtx_);
map_.erase(key);
}
if (fnOnErase_)
fnOnErase_(key);
}
/**
* @brief Get the event loop object
*
* @return trantor::EventLoop*
*/
trantor::EventLoop *getLoop()
{
return loop_;
}
/**
* @brief run the task function after a period of time.
*
* @param delay in seconds
* @param task
* @note This timer is a low-precision timer whose accuracy depends on the
* tickInterval parameter of the cache. The advantage of the timer is its
* low cost.
*/
void runAfter(size_t delay, std::function<void()> &&task)
{
std::lock_guard<std::mutex> lock(bucketMutex_);
insertEntry(delay, std::make_shared<CallbackEntry>(std::move(task)));
}
void runAfter(size_t delay, const std::function<void()> &task)
{
std::lock_guard<std::mutex> lock(bucketMutex_);
insertEntry(delay, std::make_shared<CallbackEntry>(task));
}
private:
/**
* @brief ControlBlock in a internal structure that deals with synchronizing
* CacheMap destructing, event loop destructing and updating the CacheMap.
* It is possible that the EventLoop destructed before the CacheMap (ex:
* both CacheMap and the EventLoop being globals, the order of destruction
* is not defined), thus we shouldn't invalidate the time. Or CacheMap
* destructed before the event loop but the timer is still active. Thus we
* should avoid updating the CacheMap.
*/
struct ControlBlock
{
ControlBlock() : destructed(false), loopEnded(false)
{
}
bool destructed;
bool loopEnded;
std::mutex mtx;
};
std::unordered_map<T1, MapValue> map_;
std::vector<CallbackBucketQueue> wheels_;
std::atomic<size_t> ticksCounter_{0};
std::mutex mtx_;
std::mutex bucketMutex_;
trantor::TimerId timerId_;
trantor::EventLoop *loop_;
float tickInterval_;
size_t wheelsNumber_;
size_t bucketsNumPerWheel_;
std::shared_ptr<ControlBlock> ctrlBlockPtr_;
std::function<void(const T1 &)> fnOnInsert_;
std::function<void(const T1 &)> fnOnErase_;
bool noWheels_{false};
void insertEntry(size_t delay, CallbackEntryPtr entryPtr)
{
// protected by bucketMutex;
if (delay <= 0)
return;
delay = static_cast<size_t>(delay / tickInterval_ + 1);
size_t t = ticksCounter_;
for (size_t i = 0; i < wheelsNumber_; ++i)
{
if (delay <= bucketsNumPerWheel_)
{
wheels_[i][delay - 1].insert(entryPtr);
break;
}
if (i < (wheelsNumber_ - 1))
{
entryPtr = std::make_shared<CallbackEntry>(
[this, delay, i, t, entryPtr]() {
if (delay > 0)
{
std::lock_guard<std::mutex> lock(bucketMutex_);
wheels_[i][(delay + (t % bucketsNumPerWheel_) - 1) %
bucketsNumPerWheel_]
.insert(entryPtr);
}
});
}
else
{
// delay is too long to put entry at valid position in wheels;
wheels_[i][bucketsNumPerWheel_ - 1].insert(entryPtr);
}
delay =
(delay + (t % bucketsNumPerWheel_) - 1) / bucketsNumPerWheel_;
t = t / bucketsNumPerWheel_;
}
}
void eraseAfter(size_t delay, const T1 &key)
{
if (noWheels_)
return;
assert(map_.find(key) != map_.end());
CallbackEntryPtr entryPtr;
if (map_.find(key) != map_.end())
{
entryPtr = map_[key].weakEntryPtr_.lock();
}
if (entryPtr)
{
std::lock_guard<std::mutex> lock(bucketMutex_);
insertEntry(delay, entryPtr);
}
else
{
std::function<void()> cb = [this, key]() {
bool erased{false};
std::function<void()> timeoutCallback;
{
std::lock_guard<std::mutex> lock(mtx_);
auto iter = map_.find(key);
if (iter != map_.end())
{
auto &value = iter->second;
auto entryPtr = value.weakEntryPtr_.lock();
// entryPtr is used to avoid race conditions
if (value.timeout_ > 0 && !entryPtr)
{
erased = true;
timeoutCallback = std::move(value.timeoutCallback_);
map_.erase(key);
}
}
}
if (erased && fnOnErase_)
fnOnErase_(key);
if (erased && timeoutCallback)
timeoutCallback();
};
entryPtr = std::make_shared<CallbackEntry>(std::move(cb));
map_[key].weakEntryPtr_ = entryPtr;
{
std::lock_guard<std::mutex> lock(bucketMutex_);
insertEntry(delay, entryPtr);
}
}
}
};
} // namespace drogon
+429
View File
@@ -0,0 +1,429 @@
/**
*
* @file Cookie.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/exports.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <drogon/utils/Utilities.h>
#include <cctype>
#include <string>
#include <limits>
#include <optional>
#include <string_view>
namespace drogon
{
/**
* @brief this class represents a cookie entity.
*/
class DROGON_EXPORT Cookie
{
public:
/// Constructor
/**
* @param key key of the cookie
* @param value value of the cookie
*/
Cookie(std::string key, std::string value)
: key_(std::move(key)), value_(std::move(value))
{
}
Cookie() = default;
enum class SameSite
{
kNull,
kLax,
kStrict,
kNone
};
/**
* @brief Set the Expires Date
*
* @param date The expiration date
*/
void setExpiresDate(const trantor::Date &date)
{
expiresDate_ = date;
}
/**
* @brief Set if the cookie is HTTP only.
*/
void setHttpOnly(bool only)
{
httpOnly_ = only;
}
/**
* @brief Set if the cookie is secure.
*/
void setSecure(bool secure)
{
secure_ = secure;
}
/**
* @brief Set the domain of the cookie.
*/
void setDomain(const std::string &domain)
{
domain_ = domain;
}
/**
* @brief Set the domain of the cookie.
*/
void setDomain(std::string &&domain)
{
domain_ = std::move(domain);
}
/**
* @brief Set the path of the cookie.
*/
void setPath(const std::string &path)
{
path_ = path;
}
/**
* @brief Set the path of the cookie.
*/
void setPath(std::string &&path)
{
path_ = std::move(path);
}
/**
* @brief Set the key of the cookie.
*/
void setKey(const std::string &key)
{
key_ = key;
}
/**
* @brief Set the key of the cookie.
*/
void setKey(std::string &&key)
{
key_ = std::move(key);
}
/**
* @brief Set the value of the cookie.
*/
void setValue(const std::string &value)
{
value_ = value;
}
/**
* @brief Set the value of the cookie.
*/
void setValue(std::string &&value)
{
value_ = std::move(value);
}
/**
* @brief Set the max-age of the cookie.
*/
void setMaxAge(int value)
{
maxAge_ = value;
}
/**
* @brief Set the same site of the cookie.
*/
void setSameSite(SameSite sameSite)
{
sameSite_ = sameSite;
}
/**
* @brief Set the partitioned status of the cookie
*/
void setPartitioned(bool partitioned)
{
partitioned_ = partitioned;
if (partitioned)
{
setSecure(true);
}
}
/**
* @brief Get the string value of the cookie
*/
std::string cookieString() const;
/**
* @brief Get the string value of the cookie
*/
std::string getCookieString() const
{
return cookieString();
}
/**
* @brief Get the expiration date of the cookie
*/
const trantor::Date &expiresDate() const
{
return expiresDate_;
}
/**
* @brief Get the expiration date of the cookie
*/
const trantor::Date &getExpiresDate() const
{
return expiresDate_;
}
/**
* @brief Get the domain of the cookie
*/
const std::string &domain() const
{
return domain_;
}
/**
* @brief Get the domain of the cookie
*/
const std::string &getDomain() const
{
return domain_;
}
/**
* @brief Get the path of the cookie
*/
const std::string &path() const
{
return path_;
}
/**
* @brief Get the path of the cookie
*/
const std::string &getPath() const
{
return path_;
}
/**
* @brief Get the keyword of the cookie
*/
const std::string &key() const
{
return key_;
}
/**
* @brief Get the keyword of the cookie
*/
const std::string &getKey() const
{
return key_;
}
/**
* @brief Get the value of the cookie
*/
const std::string &value() const
{
return value_;
}
/**
* @brief Get the value of the cookie
*/
const std::string &getValue() const
{
return value_;
}
/**
* @brief Check if the cookie is empty
*
* @return true means the cookie is not empty
* @return false means the cookie is empty
*/
operator bool() const
{
return (!key_.empty()) && (!value_.empty());
}
/**
* @brief Check if the cookie is HTTP only
*
* @return true means the cookie is HTTP only
* @return false means the cookie is not HTTP only
*/
bool isHttpOnly() const
{
return httpOnly_;
}
/**
* @brief Check if the cookie is secure.
*
* @return true means the cookie is secure.
* @return false means the cookie is not secure.
*/
bool isSecure() const
{
return secure_;
}
/**
* @brief Check if the cookie is partitioned.
*
* @return true means the cookie is partitioned.
* @return false means the cookie is not partitioned.
*/
bool isPartitioned() const
{
return partitioned_;
}
/**
* @brief Get the max-age of the cookie
*/
std::optional<int> maxAge() const
{
return maxAge_;
}
/**
* @brief Get the max-age of the cookie
*/
std::optional<int> getMaxAge() const
{
return maxAge_;
}
/**
* @brief Get the same site of the cookie
*/
SameSite sameSite() const
{
return sameSite_;
}
/**
* @brief Get the same site of the cookie
*/
SameSite getSameSite() const
{
return sameSite_;
}
/**
* @brief Compare two strings ignoring the their cases
*
* @param str1 string to check its value
* @param str2 string to check against, written in lower case
*
* @note the function is optimized to check for cookie's samesite value
* where we check if the value equals to a specific value we already know in
* str2. so the function doesn't apply tolower to the second argument
* str2 as it's always in lower case.
*
* @return true if both strings are equal ignoring case
*/
static bool stricmp(const std::string_view str1,
const std::string_view str2)
{
auto str1Len{str1.length()};
auto str2Len{str2.length()};
if (str1Len != str2Len)
return false;
for (size_t idx{0}; idx < str1Len; ++idx)
{
auto lowerChar{tolower(str1[idx])};
if (lowerChar != str2[idx])
{
return false;
}
}
return true;
}
/**
* @brief Converts a string value to its associated enum class SameSite
* value
*/
static SameSite convertString2SameSite(std::string_view sameSite)
{
if (stricmp(sameSite, "lax"))
return Cookie::SameSite::kLax;
if (stricmp(sameSite, "strict"))
return Cookie::SameSite::kStrict;
if (stricmp(sameSite, "none"))
return Cookie::SameSite::kNone;
if (!stricmp(sameSite, "null"))
{
LOG_WARN
<< "'" << sameSite
<< "' is not a valid SameSite policy. 'Null', 'Lax', 'Strict' "
"or "
"'None' are proper values. Return value is SameSite::kNull.";
}
return Cookie::SameSite::kNull;
}
/**
* @brief Converts an enum class SameSite value to its associated string
* value
*/
static std::string_view convertSameSite2String(SameSite sameSite)
{
switch (sameSite)
{
case SameSite::kLax:
return "Lax";
case SameSite::kStrict:
return "Strict";
case SameSite::kNone:
return "None";
case SameSite::kNull:
return "Null";
default:
return "UNDEFINED";
}
}
private:
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
bool httpOnly_{true};
bool secure_{false};
bool partitioned_{false};
std::string domain_;
std::string path_;
std::string key_;
std::string value_;
std::optional<int> maxAge_;
SameSite sameSite_{SameSite::kNull};
};
} // namespace drogon
+144
View File
@@ -0,0 +1,144 @@
/**
*
* @file DrClassMap.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/exports.h>
#include <trantor/utils/Logger.h>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <vector>
#include <type_traits>
#include <cstdlib>
#ifndef _MSC_VER
#include <cxxabi.h>
#endif
#include <stdio.h>
namespace drogon
{
class DrObjectBase;
using DrAllocFunc = std::function<DrObjectBase *()>;
using DrSharedAllocFunc = std::function<std::shared_ptr<DrObjectBase>()>;
/**
* @brief A map class which can create DrObjects from names.
*/
class DROGON_EXPORT DrClassMap
{
public:
/**
* @brief Register a class into the map
*
* @param className The name of the class
* @param func The function which can create a new instance of the class.
*/
static void registerClass(const std::string &className,
const DrAllocFunc &func,
const DrSharedAllocFunc &sharedFunc = nullptr);
/**
* @brief Create a new instance of the class named by className
*
* @param className The name of the class
* @return DrObjectBase* The pointer to the newly created instance.
*/
static DrObjectBase *newObject(const std::string &className);
/**
* @brief Get the shared_ptr instance of the class named by className
*/
static std::shared_ptr<DrObjectBase> newSharedObject(
const std::string &className);
/**
* @brief Get the singleton object of the class named by className
*
* @param className The name of the class
* @return const std::shared_ptr<DrObjectBase>& The smart pointer to the
* instance.
*/
static const std::shared_ptr<DrObjectBase> &getSingleInstance(
const std::string &className);
/**
* @brief Get the singleton T type object
*
* @tparam T The type of the class
* @return std::shared_ptr<T> The smart pointer to the instance.
* @note The T must be a subclass of the DrObjectBase class.
*/
template <typename T>
static std::shared_ptr<T> getSingleInstance()
{
static_assert(std::is_base_of<DrObjectBase, T>::value,
"T must be a sub-class of DrObjectBase");
static auto const singleton =
std::dynamic_pointer_cast<T>(getSingleInstance(T::classTypeName()));
assert(singleton);
return singleton;
}
/**
* @brief Set a singleton object into the map.
*
* @param ins The smart pointer to the instance.
*/
static void setSingleInstance(const std::shared_ptr<DrObjectBase> &ins);
/**
* @brief Get all names of classes registered in the map.
*
* @return std::vector<std::string> the vector of class names.
*/
static std::vector<std::string> getAllClassName();
/**
* @brief demangle the type name which is returned by typeid(T).name().
*
* @param mangled_name The type name which is returned by typeid(T).name().
* @return std::string The human readable type name.
*/
static std::string demangle(const char *mangled_name)
{
#ifndef _MSC_VER
std::size_t len = 0;
int status = 0;
std::unique_ptr<char, decltype(&std::free)> ptr(
__cxxabiv1::__cxa_demangle(mangled_name, nullptr, &len, &status),
&std::free);
if (status == 0)
{
return std::string(ptr.get());
}
LOG_ERROR << "Demangle error!";
return "";
#else
auto pos = strstr(mangled_name, " ");
if (pos == nullptr)
return std::string{mangled_name};
else
return std::string{pos + 1};
#endif
}
protected:
static std::unordered_map<std::string,
std::pair<DrAllocFunc, DrSharedAllocFunc>> &
getMap();
};
} // namespace drogon
+151
View File
@@ -0,0 +1,151 @@
/**
*
* @file DrObject.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/exports.h>
#include <drogon/DrClassMap.h>
#include <string>
#include <type_traits>
#ifdef _MSC_VER
#pragma warning(disable : 4250)
#endif
namespace drogon
{
/**
* @brief The base class for all drogon reflection classes.
*
*/
class DROGON_EXPORT DrObjectBase
{
public:
/**
* @brief Get the class name
*
* @return const std::string& the class name
*/
virtual const std::string &className() const
{
static const std::string name{"DrObjectBase"};
return name;
}
/**
* @brief Return true if the class name is 'class_name'
*/
virtual bool isClass(const std::string &class_name) const
{
return (className() == class_name);
}
virtual ~DrObjectBase()
{
}
};
template <typename T>
struct isAutoCreationClass
{
template <class C>
static constexpr auto check(C *) -> std::enable_if_t<
std::is_same_v<decltype(C::isAutoCreation), const bool>,
bool>
{
return C::isAutoCreation;
}
template <typename>
static constexpr bool check(...)
{
return false;
}
static constexpr bool value = check<T>(nullptr);
};
/**
* a class template to
* implement the reflection function of creating the class object by class name
*/
template <typename T>
class DrObject : public virtual DrObjectBase
{
public:
const std::string &className() const override
{
return alloc_.className();
}
static const std::string &classTypeName()
{
return alloc_.className();
}
bool isClass(const std::string &class_name) const override
{
return (className() == class_name);
}
protected:
// protect constructor to make this class only inheritable
DrObject() = default;
~DrObject() override = default;
private:
class DrAllocator
{
public:
DrAllocator()
{
registerClass<T>();
}
const std::string &className() const
{
static std::string className =
DrClassMap::demangle(typeid(T).name());
return className;
}
template <typename D>
void registerClass()
{
if constexpr (std::is_default_constructible<D>::value)
{
DrClassMap::registerClass(
className(),
[]() -> DrObjectBase * { return new T; },
[]() -> std::shared_ptr<DrObjectBase> {
return std::make_shared<T>();
});
}
else if constexpr (isAutoCreationClass<D>::value)
{
static_assert(std::is_default_constructible<D>::value,
"Class is not default constructable!");
}
}
};
// use static val to register allocator function for class T;
static DrAllocator alloc_;
};
template <typename T>
typename DrObject<T>::DrAllocator DrObject<T>::alloc_;
} // namespace drogon
+31
View File
@@ -0,0 +1,31 @@
/**
*
* DrTemplate.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/DrObject.h>
#include <drogon/DrTemplateBase.h>
namespace drogon
{
template <typename T>
class DrTemplate : public DrObject<T>, public DrTemplateBase
{
protected:
DrTemplate()
{
}
};
} // namespace drogon
+58
View File
@@ -0,0 +1,58 @@
/**
*
* @file DrTemplateBase.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/exports.h>
#include <drogon/DrObject.h>
#include <drogon/HttpViewData.h>
#include <memory>
#include <string>
namespace drogon
{
using DrTemplateData = HttpViewData;
/// The templating engine class
/**
* This class can generate a text string from the template file and template
* data.
* For more details on the template file, see the wiki site (the 'View' section)
*/
class DROGON_EXPORT DrTemplateBase : public virtual DrObjectBase
{
public:
/// Create an object of the implementation class
/**
* @param templateName represents the name of the template file. A template
* file is a description file with a special format. Its extension is
* usually .csp. The user should preprocess the template file with the
* drogon_ctl tool to create c++ source files.
*/
static std::shared_ptr<DrTemplateBase> newTemplate(
const std::string &templateName);
/// Generate the text string
/**
* @param data represents data rendered in the string in a format
* according to the template file.
*/
virtual std::string genText(
const DrTemplateData &data = DrTemplateData()) = 0;
virtual ~DrTemplateBase(){};
DrTemplateBase(){};
};
} // namespace drogon
File diff suppressed because it is too large Load Diff
+485
View File
@@ -0,0 +1,485 @@
/**
*
* @file HttpBinder.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
*
*/
/// The classes in the file are internal tool classes. Do not include this
/// file directly and use any of these classes directly.
#pragma once
#include <drogon/exports.h>
#include <drogon/DrClassMap.h>
#include <drogon/DrObject.h>
#include <drogon/utils/FunctionTraits.h>
#include <drogon/utils/Utilities.h>
#include <drogon/HttpRequest.h>
#include <deque>
#include <memory>
#include <sstream>
#include <string>
namespace drogon
{
namespace internal
{
// we only accept value type or const lreference type or right reference type as
// the handle method parameters type
template <typename T>
struct BinderArgTypeTraits
{
static const bool isValid = true;
};
template <typename T>
struct BinderArgTypeTraits<T *>
{
static const bool isValid = false;
};
template <typename T>
struct BinderArgTypeTraits<T &>
{
static const bool isValid = false;
};
template <typename T>
struct BinderArgTypeTraits<T &&>
{
static const bool isValid = true;
};
template <typename T>
struct BinderArgTypeTraits<const T &&>
{
static const bool isValid = false;
};
template <typename T>
struct BinderArgTypeTraits<const T &>
{
static const bool isValid = true;
};
template <typename T>
T getHandlerArgumentValue(std::string &&p)
{
if constexpr (internal::CanConstructFromString<T>::value)
{
return T(std::move(p));
}
else if constexpr (internal::CanConvertFromStringStream<T>::value)
{
T value{T()};
if (!p.empty())
{
std::stringstream ss(std::move(p));
ss >> value;
}
return value;
}
else if constexpr (internal::CanConvertFromString<T>::value)
{
T value;
value = p;
return value;
}
else
{
LOG_ERROR << "Can't convert string to type " << typeid(T).name();
return T();
}
}
template <>
inline std::string getHandlerArgumentValue<std::string>(std::string &&p)
{
return std::move(p);
}
template <>
inline int getHandlerArgumentValue<int>(std::string &&p)
{
return std::stoi(p);
}
template <>
inline long getHandlerArgumentValue<long>(std::string &&p)
{
return std::stol(p);
}
template <>
inline long long getHandlerArgumentValue<long long>(std::string &&p)
{
return std::stoll(p);
}
template <>
inline unsigned long getHandlerArgumentValue<unsigned long>(std::string &&p)
{
return std::stoul(p);
}
template <>
inline unsigned long long getHandlerArgumentValue<unsigned long long>(
std::string &&p)
{
return std::stoull(p);
}
template <>
inline float getHandlerArgumentValue<float>(std::string &&p)
{
return std::stof(p);
}
template <>
inline double getHandlerArgumentValue<double>(std::string &&p)
{
return std::stod(p);
}
template <>
inline long double getHandlerArgumentValue<long double>(std::string &&p)
{
return std::stold(p);
}
class HttpBinderBase
{
public:
virtual void handleHttpRequest(
std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) = 0;
virtual size_t paramCount() = 0;
virtual const std::string &handlerName() const = 0;
virtual bool isStreamHandler() = 0;
virtual ~HttpBinderBase()
{
}
};
template <typename T>
T &getControllerObj()
{
// Initialization of function-local statics is guaranteed to occur only once
// even when
// called from multiple threads, and may be more efficient than the
// equivalent code using std::call_once.
static T obj;
return obj;
}
DROGON_EXPORT void handleException(
const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
using HttpBinderBasePtr = std::shared_ptr<HttpBinderBase>;
template <typename FUNCTION>
class HttpBinder : public HttpBinderBase
{
public:
using traits = FunctionTraits<FUNCTION>;
using FunctionType = FUNCTION;
void handleHttpRequest(
std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override
{
if (!pathArguments.empty())
{
std::vector<std::string> args;
args.reserve(pathArguments.size());
for (auto &arg : pathArguments)
{
args.emplace_back(arg);
}
req->setRoutingParameters(std::move(args));
}
run(pathArguments, req, std::move(callback));
}
size_t paramCount() override
{
return traits::arity;
}
bool isStreamHandler() override
{
return traits::isStreamHandler;
}
HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
{
static_assert(traits::isHTTPFunction,
"Your API handler function interface is wrong!");
handlerName_ = DrClassMap::demangle(typeid(FUNCTION).name());
}
void test()
{
std::cout << "argument_count=" << argument_count << " "
<< traits::isHTTPFunction << std::endl;
}
const std::string &handlerName() const override
{
return handlerName_;
}
template <bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass>
void createHandlerInstance()
{
if constexpr (isClassFunction)
{
if constexpr (isDrObjectClass)
{
auto objPtr = DrClassMap::getSingleInstance<
typename traits::class_type>();
LOG_TRACE << "create handler class object: " << objPtr.get();
}
else
{
auto &obj = getControllerObj<typename traits::class_type>();
LOG_TRACE << "create handler class object: " << &obj;
}
}
}
private:
FUNCTION func_;
template <std::size_t Index>
using nth_argument_type = typename traits::template argument<Index>;
static const size_t argument_count = traits::arity;
std::string handlerName_;
template <typename... Values,
std::size_t Boundary = argument_count,
bool isStreamHandler = traits::isStreamHandler,
bool isCoroutine = traits::isCoroutine>
void run(std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&...values)
{
if constexpr (sizeof...(Values) < Boundary)
{ // Call this function recursively until parameter's count equals to
// the count of target function parameters
static_assert(
BinderArgTypeTraits<
nth_argument_type<sizeof...(Values)>>::isValid,
"your handler argument type must be value type or const left "
"reference type or right reference type");
using ValueType = std::remove_cv_t<
std::remove_reference_t<nth_argument_type<sizeof...(Values)>>>;
if (!pathArguments.empty())
{
std::string v{std::move(pathArguments.front())};
pathArguments.pop_front();
try
{
if (!v.empty())
{
auto value =
getHandlerArgumentValue<ValueType>(std::move(v));
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
std::move(value));
return;
}
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
}
else
{
try
{
auto value = req->as<ValueType>();
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
std::move(value));
return;
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
ValueType());
}
else if constexpr (sizeof...(Values) == Boundary)
{
if constexpr (!isCoroutine)
{
try
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
if constexpr (isStreamHandler)
{
callFunction(req,
createRequestStream(req),
cb,
std::move(values)...);
}
else
{
callFunction(req, cb, std::move(values)...);
}
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
#ifdef __cpp_impl_coroutine
else
{
static_assert(!isStreamHandler);
[this](HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
Values &&...values) -> AsyncTask {
try
{
if constexpr (std::is_same_v<
AsyncTask,
typename traits::return_type>)
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
callFunction(req, cb, std::move(values)...);
}
else if constexpr (std::is_same_v<
Task<>,
typename traits::return_type>)
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
co_await callFunction(req,
cb,
std::move(values)...);
}
else if constexpr (std::is_same_v<
Task<HttpResponsePtr>,
typename traits::return_type>)
{
auto resp =
co_await callFunction(req,
std::move(values)...);
callback(std::move(resp));
}
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR
<< "Exception not derived from std::exception";
}
co_return;
}(req, std::move(callback), std::move(values)...);
}
#endif
}
}
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass,
bool isNormal = std::is_same_v<typename traits::first_param_type,
HttpRequestPtr>>
typename traits::return_type callFunction(const HttpRequestPtr &req,
Values &&...values)
{
if constexpr (isNormal)
{
if constexpr (isClassFunction)
{
if constexpr (!isDrObjectClass)
{
static auto &obj =
getControllerObj<typename traits::class_type>();
return (obj.*func_)(req, std::move(values)...);
}
else
{
static auto objPtr = DrClassMap::getSingleInstance<
typename traits::class_type>();
return (*objPtr.*func_)(req, std::move(values)...);
}
}
else
{
return func_(req, std::move(values)...);
}
}
else
{
if constexpr (isClassFunction)
{
if constexpr (!isDrObjectClass)
{
static auto &obj =
getControllerObj<typename traits::class_type>();
return (obj.*func_)((*req), std::move(values)...);
}
else
{
static auto objPtr = DrClassMap::getSingleInstance<
typename traits::class_type>();
return (*objPtr.*func_)((*req), std::move(values)...);
}
}
else
{
return func_((*req), std::move(values)...);
}
}
}
};
} // namespace internal
} // namespace drogon
+402
View File
@@ -0,0 +1,402 @@
/**
*
* @file HttpClient.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/exports.h>
#include <drogon/HttpTypes.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpResponse.h>
#include <drogon/HttpRequest.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <cstddef>
#include <functional>
#include <memory>
#include <future>
#include "drogon/HttpBinder.h"
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
class HttpClient;
using HttpClientPtr = std::shared_ptr<HttpClient>;
#ifdef __cpp_impl_coroutine
namespace internal
{
struct HttpRespAwaiter : public CallbackAwaiter<HttpResponsePtr>
{
HttpRespAwaiter(HttpClient *client, HttpRequestPtr req, double timeout)
: client_(client), req_(std::move(req)), timeout_(timeout)
{
}
void await_suspend(std::coroutine_handle<> handle);
private:
HttpClient *client_;
HttpRequestPtr req_;
double timeout_;
};
} // namespace internal
#endif
/// Asynchronous http client
/**
* HttpClient implementation object uses the HttpAppFramework's event loop by
* default, so you should call app().run() to make the client work.
* Each HttpClient object establishes a persistent connection with the server.
* If the connection is broken, the client attempts to reconnect
* when calling the sendRequest method.
*
* Using the static method newHttpClient(...) to get shared_ptr of the object
* implementing the class, the shared_ptr is retained in the framework until all
* response callbacks are invoked without fear of accidental deconstruction.
*
*/
class DROGON_EXPORT HttpClient : public trantor::NonCopyable
{
public:
/**
* @brief Send a request asynchronously to the server
*
* @param req The request sent to the server.
* @param callback The callback is called when the response is received from
* the server.
* @param timeout In seconds. If the response is not received within the
* timeout, the callback is called with `ReqResult::Timeout` and an empty
* response. The zero value by default disables the timeout.
*
* @note
* The request object is altered(some headers are added to it) before it is
* sent, so calling this method with a same request object in different
* thread is dangerous.
* Please be careful when using timeout on an non-idempotent request.
*/
virtual void sendRequest(const HttpRequestPtr &req,
const HttpReqCallback &callback,
double timeout = 0) = 0;
/**
* @brief Send a request asynchronously to the server
*
* @param req The request sent to the server.
* @param callback The callback is called when the response is received from
* the server.
* @param timeout In seconds. If the response is not received within
* the timeout, the callback is called with `ReqResult::Timeout` and an
* empty response. The zero value by default disables the timeout.
*
* @note
* The request object is altered(some headers are added to it) before it is
* sent, so calling this method with a same request object in different
* thread is dangerous.
* Please be careful when using timeout on an non-idempotent request.
*/
virtual void sendRequest(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout = 0) = 0;
/**
* @brief Send a request synchronously to the server and return the
* response.
*
* @param req
* @param timeout In seconds. If the response is not received within the
* timeout, the `ReqResult::Timeout` and an empty response is returned. The
* zero value by default disables the timeout.
*
* @return std::pair<ReqResult, HttpResponsePtr>
* @note Never call this function in the event loop thread of the
* client (partially in the callback function of the asynchronous
* sendRequest method), otherwise the thread will be blocked forever.
* Please be careful when using timeout on an non-idempotent request.
*/
std::pair<ReqResult, HttpResponsePtr> sendRequest(const HttpRequestPtr &req,
double timeout = 0)
{
assert(!getLoop()->isInLoopThread() &&
"Deadlock detected! Calling a sync API from the same loop as "
"the HTTP client processes on will deadlock the event loop");
std::promise<std::pair<ReqResult, HttpResponsePtr>> prom;
auto f = prom.get_future();
sendRequest(
req,
[&prom](ReqResult r, const HttpResponsePtr &resp) {
prom.set_value({r, resp});
},
timeout);
return f.get();
}
#ifdef __cpp_impl_coroutine
/**
* @brief Send a request via coroutines to the server and return an
* awaiter what could be `co_await`-ed to retrieve the response
* (HttpResponsePtr)
*
* @param req
* @param timeout In seconds. If the response is not received within the
* timeout, A `drogon::HttpException` with `ReqResult::Timeout` is thrown.
* The zero value by default disables the timeout.
*
* @return internal::HttpRespAwaiter. Await on it to get the response
*/
internal::HttpRespAwaiter sendRequestCoro(HttpRequestPtr req,
double timeout = 0)
{
return internal::HttpRespAwaiter(this, std::move(req), timeout);
}
#endif
/// Set socket options(before connecting)
/**
* @brief Set the callback which is called before connecting to the
* server. The callback is used to set socket options on the socket fd.
*
* @code
auto client = HttpClient::newHttpClient("http://www.baidu.com");
client->setSockOptCallback([](int fd) {});
auto req = HttpRequest::newHttpRequest();
client->sendRequest(req, [](ReqResult result, const HttpResponsePtr&
response) {});
@endcode
*/
virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
/**
* @brief Return the number of unsent http requests in the current http
* client cache buffer
*/
virtual std::size_t requestsBufferSize() = 0;
/// Set the pipelining depth, which is the number of requests that are not
/// responding.
/**
* If this method is not called, the default depth value is 0 which means
* the pipelining is disabled. For details about pipelining, see
* rfc2616-8.1.2.2
*/
virtual void setPipeliningDepth(size_t depth) = 0;
/// Enable cookies for the client
/**
* @param flag if the parameter is true, all requests sent by the client
* carry the cookies set by the server side. Cookies are disabled by
* default.
*/
virtual void enableCookies(bool flag = true) = 0;
/// Add a cookie to the client
/**
* @note
* These methods are independent of the enableCookies() method. Whether the
* enableCookies() is called with true or false, the cookies added by these
* methods will be sent to the server.
*/
virtual void addCookie(const std::string &key,
const std::string &value) = 0;
/// Add a cookie to the client
/**
* @note
* These methods are independent of the enableCookies() method. Whether the
* enableCookies() is called with true or false, the cookies added by these
* methods will be sent to the server.
*/
virtual void addCookie(const Cookie &cookie) = 0;
/**
* @brief Set the user_agent header, the default value is 'DrogonClient' if
* this method is not used.
*
* @param userAgent The user_agent value, if it is empty, the user_agent
* header is not sent to the server.
*/
virtual void setUserAgent(const std::string &userAgent) = 0;
/**
* @brief Create a new HTTP client which use ip and port to connect to
* server
*
* @param ip The ip address of the HTTP server
* @param port The port of the HTTP server
* @param useSSL if the parameter is set to true, the client connects to the
* server using HTTPS.
* @param loop If the loop parameter is set to nullptr, the client uses the
* HttpAppFramework's event loop, otherwise it runs in the loop identified
* by the parameter.
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* enabled for HTTPS.
* @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking.
* @return HttpClientPtr The smart pointer to the new client object.
* @note: The ip parameter support for both ipv4 and ipv6 address
*/
static HttpClientPtr newHttpClient(const std::string &ip,
uint16_t port,
bool useSSL = false,
trantor::EventLoop *loop = nullptr,
bool useOldTLS = false,
bool validateCert = true);
/// Get the event loop of the client;
virtual trantor::EventLoop *getLoop() = 0;
/// Get the number of bytes sent or received
virtual size_t bytesSent() const = 0;
virtual size_t bytesReceived() const = 0;
virtual std::string host() const = 0;
std::string getHost() const
{
return host();
}
virtual uint16_t port() const = 0;
uint16_t getPort() const
{
return port();
}
virtual bool secure() const = 0;
bool onDefaultPort() const
{
if (secure())
return port() == 443;
return port() == 80;
}
/**
* @brief Set the client certificate used by the HTTP connection
*
* @param cert Path to the certificate
* @param key Path to the certificate's private key
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
*/
virtual void setCertPath(const std::string &cert,
const std::string &key) = 0;
/**
* @brief Supplies command style options for `SSL_CONF_cmd`
*
* @param sslConfCmds options for SSL_CONF_cmd
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
* @code
addSSLConfigs({{"-dhparam", "/path/to/dhparam"}, {"-strict", ""}});
* @endcode
*/
virtual void addSSLConfigs(
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0;
/// Create a Http client using the hostString to connect to server
/**
*
* @param hostString this parameter must be prefixed by 'http://' or
* 'https://'.
*
* Examples for hostString:
* @code
https://www.baidu.com
http://www.baidu.com
https://127.0.0.1:8080/
http://127.0.0.1
http://[::1]:8080/ //IPv6 address must be enclosed in [], rfc2732
@endcode
*
* @param loop If the loop parameter is set to nullptr, the client uses the
* HttpAppFramework's event loop, otherwise it runs in the loop identified
* by the parameter.
*
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* enabled for HTTPS.
* @note
*
* @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking.
*
* @note Don't add path and parameters in hostString, the request path and
* parameters should be set in HttpRequestPtr when calling the sendRequest()
* method.
*
*/
static HttpClientPtr newHttpClient(const std::string &hostString,
trantor::EventLoop *loop = nullptr,
bool useOldTLS = false,
bool validateCert = true);
virtual ~HttpClient()
{
}
protected:
HttpClient() = default;
};
#ifdef __cpp_impl_coroutine
class HttpException : public std::exception
{
public:
HttpException() = delete;
explicit HttpException(ReqResult res)
: resultCode_(res), message_(to_string_view(res))
{
}
const char *what() const noexcept override
{
return message_.data();
}
ReqResult code() const
{
return resultCode_;
}
private:
ReqResult resultCode_;
std::string_view message_;
};
inline void internal::HttpRespAwaiter::await_suspend(
std::coroutine_handle<> handle)
{
assert(client_ != nullptr);
assert(req_ != nullptr);
client_->sendRequest(
req_,
[handle, this](ReqResult result, const HttpResponsePtr &resp) {
if (result == ReqResult::Ok)
setValue(resp);
else
setException(std::make_exception_ptr(HttpException(result)));
handle.resume();
},
timeout_);
}
#endif
} // namespace drogon
+150
View File
@@ -0,0 +1,150 @@
/**
*
* HttpController.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/DrObject.h>
#include <drogon/utils/HttpConstraint.h>
#include <drogon/HttpAppFramework.h>
#include <iostream>
#include <string>
#include <trantor/utils/Logger.h>
#include <vector>
/// For more details on the class, see the wiki site (the 'HttpController'
/// section)
#define METHOD_LIST_BEGIN \
static void initPathRouting() \
{
#define METHOD_ADD(method, pattern, ...) \
registerMethod(&method, pattern, {__VA_ARGS__}, true, #method)
#define ADD_METHOD_TO(method, path_pattern, ...) \
registerMethod(&method, path_pattern, {__VA_ARGS__}, false, #method)
#define ADD_METHOD_VIA_REGEX(method, regex, ...) \
registerMethodViaRegex(&method, regex, {__VA_ARGS__}, #method)
#define METHOD_LIST_END \
return; \
}
namespace drogon
{
/**
* @brief The base class for HTTP controllers.
*
*/
class HttpControllerBase
{
};
/**
* @brief The reflection base class template for HTTP controllers
*
* @tparam T the type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have nondefault constructors.
*/
template <typename T, bool AutoCreation = true>
class HttpController : public DrObject<T>, public HttpControllerBase
{
public:
static constexpr bool isAutoCreation = AutoCreation;
protected:
template <typename FUNCTION>
static void registerMethod(
FUNCTION &&function,
const std::string &pattern,
const std::vector<internal::HttpConstraint> &constraints = {},
bool classNameInPath = true,
const std::string &handlerName = "")
{
if (classNameInPath)
{
std::string path = "/";
path.append(HttpController<T, AutoCreation>::classTypeName());
LOG_TRACE << "classname:"
<< HttpController<T, AutoCreation>::classTypeName();
// transform(path.begin(), path.end(), path.begin(), [](unsigned
// char c){ return tolower(c); });
std::string::size_type pos;
while ((pos = path.find("::")) != std::string::npos)
{
path.replace(pos, 2, "/");
}
if (pattern.empty() || pattern[0] == '/')
app().registerHandler(path + pattern,
std::forward<FUNCTION>(function),
constraints,
handlerName);
else
app().registerHandler(path + "/" + pattern,
std::forward<FUNCTION>(function),
constraints,
handlerName);
}
else
{
std::string path = pattern;
if (path.empty() || path[0] != '/')
{
path = "/" + path;
}
app().registerHandler(path,
std::forward<FUNCTION>(function),
constraints,
handlerName);
}
}
template <typename FUNCTION>
static void registerMethodViaRegex(
FUNCTION &&function,
const std::string &regExp,
const std::vector<internal::HttpConstraint> &constraints =
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "")
{
app().registerHandlerViaRegex(regExp,
std::forward<FUNCTION>(function),
constraints,
handlerName);
}
private:
class methodRegistrator
{
public:
methodRegistrator()
{
if (AutoCreation)
T::initPathRouting();
}
};
// use static value to register controller method in framework before
// main();
static methodRegistrator registrator_;
virtual void *touch()
{
return &registrator_;
}
};
template <typename T, bool AutoCreation>
typename HttpController<T, AutoCreation>::methodRegistrator
HttpController<T, AutoCreation>::registrator_;
} // namespace drogon
+135
View File
@@ -0,0 +1,135 @@
/**
*
* @file HttpFilter.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/DrObject.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/HttpMiddleware.h>
#include <memory>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
/**
* @brief The abstract base class for filters
* For more details on the class, see the wiki site (the 'Filter' section)
*/
class DROGON_EXPORT HttpFilterBase : public virtual DrObjectBase,
public HttpMiddlewareBase
{
public:
/// This virtual function should be overridden in subclasses.
/**
* This method is an asynchronous interface, user should return the result
* via 'FilterCallback' or 'FilterChainCallback'.
* @param req is the request object processed by the filter
* @param fcb if this is called, the response object is send to the client
* by the callback, and doFilter methods of next filters and the handler
* registered on the path are not called anymore.
* @param fccb if this callback is called, the next filter's doFilter method
* or the handler registered on the path is called.
*/
virtual void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) = 0;
~HttpFilterBase() override = default;
private:
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) final
{
auto mcbPtr = std::make_shared<MiddlewareCallback>(std::move(mcb));
doFilter(
req,
[mcbPtr](const HttpResponsePtr &resp) {
(*mcbPtr)(resp);
}, // fcb, intercept the response
[nextCb = std::move(nextCb), mcbPtr]() mutable {
nextCb([mcbPtr = std::move(mcbPtr)](
const HttpResponsePtr &resp) { (*mcbPtr)(resp); });
} // fccb, call the next middleware
);
}
};
/**
* @brief The reflection base class template for filters
*
* @tparam T The type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have non-default constructors.
*/
template <typename T, bool AutoCreation = true>
class HttpFilter : public DrObject<T>, public HttpFilterBase
{
public:
static constexpr bool isAutoCreation{AutoCreation};
~HttpFilter() override = default;
};
#ifdef __cpp_impl_coroutine
template <typename T, bool AutoCreation = true>
class HttpCoroFilter : public DrObject<T>, public HttpFilterBase
{
public:
static constexpr bool isAutoCreation{AutoCreation};
~HttpCoroFilter() override = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) final
{
drogon::async_run([this,
req,
fcb = std::move(fcb),
fccb = std::move(fccb)]() mutable -> drogon::Task<> {
HttpResponsePtr resp;
try
{
resp = co_await doFilter(req);
}
catch (const std::exception &ex)
{
internal::handleException(ex, req, std::move(fcb));
co_return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
co_return;
}
if (resp)
{
fcb(resp);
}
else
{
fccb();
}
});
}
virtual Task<HttpResponsePtr> doFilter(const HttpRequestPtr &req) = 0;
};
#endif
} // namespace drogon
+151
View File
@@ -0,0 +1,151 @@
/**
*
* @file HttpMiddleware.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/DrObject.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <memory>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
/**
* @brief The abstract base class for middleware
*/
class DROGON_EXPORT HttpMiddlewareBase : public virtual DrObjectBase
{
public:
/**
* This virtual function should be overridden in subclasses.
*
* Example:
* @code
* void invoke(const HttpRequestPtr &req,
* MiddlewareNextCallback &&nextCb,
* MiddlewareCallback &&mcb) override
* {
* if (req->path() == "/some/path") {
* // intercept directly
* mcb(HttpResponse::newNotFoundResponse(req));
* return;
* }
* // Do something before calling the next middleware
* nextCb([mcb = std::move(mcb)](const HttpResponsePtr &resp) {
* // Do something after the next middleware returns
* mcb(resp);
* });
* }
* @endcode
*
*/
virtual void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) = 0;
~HttpMiddlewareBase() override = default;
};
/**
* @brief The reflection base class template for middlewares
*
* @tparam T The type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have non-default constructors.
*/
template <typename T, bool AutoCreation = true>
class HttpMiddleware : public DrObject<T>, public HttpMiddlewareBase
{
public:
static constexpr bool isAutoCreation{AutoCreation};
~HttpMiddleware() override = default;
};
namespace internal
{
DROGON_EXPORT void handleException(
const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
}
#ifdef __cpp_impl_coroutine
struct [[nodiscard]] MiddlewareNextAwaiter
: public CallbackAwaiter<HttpResponsePtr>
{
public:
MiddlewareNextAwaiter(MiddlewareNextCallback &&nextCb)
: nextCb_(std::move(nextCb))
{
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
nextCb_([this, handle](const HttpResponsePtr &resp) {
setValue(resp);
handle.resume();
});
}
private:
MiddlewareNextCallback nextCb_;
};
template <typename T, bool AutoCreation = true>
class HttpCoroMiddleware : public DrObject<T>, public HttpMiddlewareBase
{
public:
static constexpr bool isAutoCreation{AutoCreation};
~HttpCoroMiddleware() override = default;
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) final
{
drogon::async_run([this,
req,
nextCb = std::move(nextCb),
mcb = std::move(mcb)]() mutable -> drogon::Task<> {
HttpResponsePtr resp;
try
{
resp = co_await invoke(req, {std::move(nextCb)});
}
catch (const std::exception &ex)
{
internal::handleException(ex, req, std::move(mcb));
co_return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
co_return;
}
mcb(resp);
});
}
virtual Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
MiddlewareNextAwaiter &&next) = 0;
};
#endif
} // namespace drogon
+536
View File
@@ -0,0 +1,536 @@
/**
*
* @file HttpRequest.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/exports.h>
#include <drogon/utils/Utilities.h>
#include <drogon/DrClassMap.h>
#include <drogon/HttpTypes.h>
#include <drogon/Session.h>
#include <drogon/Attribute.h>
#include <drogon/UploadFile.h>
#include <json/json.h>
#include <trantor/net/InetAddress.h>
#include <trantor/net/Certificate.h>
#include <trantor/utils/Date.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <optional>
#include <string_view>
#include <trantor/net/TcpConnection.h>
namespace drogon
{
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
/**
* @brief This template is used to convert a request object to a custom
* type object. Users must specialize the template for a particular type.
*/
template <typename T>
T fromRequest(const HttpRequest &)
{
LOG_ERROR << "You must specialize the fromRequest template for the type of "
<< DrClassMap::demangle(typeid(T).name());
exit(1);
}
/**
* @brief This template is used to create a request object from a custom
* type object by calling the newCustomHttpRequest(). Users must specialize
* the template for a particular type.
*/
template <typename T>
HttpRequestPtr toRequest(T &&)
{
LOG_ERROR << "You must specialize the toRequest template for the type of "
<< DrClassMap::demangle(typeid(T).name());
exit(1);
}
template <>
HttpRequestPtr toRequest<const Json::Value &>(const Json::Value &pJson);
template <>
HttpRequestPtr toRequest(Json::Value &&pJson);
template <>
inline HttpRequestPtr toRequest<Json::Value &>(Json::Value &pJson)
{
return toRequest((const Json::Value &)pJson);
}
template <>
std::shared_ptr<Json::Value> fromRequest(const HttpRequest &req);
/// Abstract class for webapp developer to get or set the Http request;
class DROGON_EXPORT HttpRequest
{
public:
/**
* @brief This template enables implicit type conversion. For using this
* template, user must specialize the fromRequest template. For example a
* shared_ptr<Json::Value> specialization version is available above, so
* we can use the following code to get a json object:
* @code
std::shared_ptr<Json::Value> jsonPtr = *requestPtr;
@endcode
* With this template, user can use their favorite JSON library instead of
* the default jsoncpp library or convert the request to an object of any
* custom type.
*/
template <typename T>
operator T() const
{
return fromRequest<T>(*this);
}
/**
* @brief This template enables explicit type conversion, see the above
* template.
*/
template <typename T>
T as() const
{
return fromRequest<T>(*this);
}
/// Return the method string of the request, such as GET, POST, etc.
virtual const char *methodString() const = 0;
const char *getMethodString() const
{
return methodString();
}
/// Return the enum type method of the request.
virtual HttpMethod method() const = 0;
HttpMethod getMethod() const
{
return method();
}
/**
* @brief Check if the method is or was HttpMethod::Head
* @details Allows to know that an incoming request is a HEAD request, since
* drogon sets the method to HttpMethod::Get before calling the
* controller
* @return true if method() returns HttpMethod::Head, or HttpMethod::Get but
* was previously HttpMethod::Head
*/
virtual bool isHead() const = 0;
/// Get the header string identified by the key parameter.
/**
* @note
* If there is no the header, a empty string is returned.
* The key is case insensitive
*/
virtual const std::string &getHeader(std::string key) const = 0;
/**
* @brief Set the header string identified by the field parameter
*
* @param field The field parameter is transformed to lower case before
* storing.
* @param value The value of the header.
*/
virtual void addHeader(std::string field, const std::string &value) = 0;
virtual void addHeader(std::string field, std::string &&value) = 0;
/**
* @brief Remove the header identified by the key parameter.
*
* @param key The key is case insensitive
*/
virtual void removeHeader(std::string key) = 0;
/// Get the cookie string identified by the field parameter
virtual const std::string &getCookie(const std::string &field) const = 0;
/// Get all headers of the request
virtual const SafeStringMap<std::string> &headers() const = 0;
/// Get all headers of the request
const SafeStringMap<std::string> &getHeaders() const
{
return headers();
}
/// Get all cookies of the request
virtual const SafeStringMap<std::string> &cookies() const = 0;
/// Get all cookies of the request
const SafeStringMap<std::string> &getCookies() const
{
return cookies();
}
/**
* @brief Return content length parsed from the Content-Length header
* If no Content-Length header, return null.
*/
virtual size_t realContentLength() const = 0;
size_t getRealContentLength() const
{
return realContentLength();
}
/// Get the query string of the request.
/**
* The query string is the substring after the '?' in the URL string.
*/
virtual const std::string &query() const = 0;
/// Get the query string of the request.
const std::string &getQuery() const
{
return query();
}
/// Get the content string of the request, which is the body part of the
/// request.
std::string_view body() const
{
return std::string_view(bodyData(), bodyLength());
}
/// Get the content string of the request, which is the body part of the
/// request.
std::string_view getBody() const
{
return body();
}
virtual const char *bodyData() const = 0;
virtual size_t bodyLength() const = 0;
/// Set the content string of the request.
virtual void setBody(const std::string &body) = 0;
/// Set the content string of the request.
virtual void setBody(std::string &&body) = 0;
/// Get the path of the request.
virtual const std::string &path() const = 0;
/// Get the original path of the request.(before url-decoding)
virtual const std::string &getOriginalPath() const = 0;
/// Get the path of the request.
const std::string &getPath() const
{
return path();
}
/// Get the matched path pattern after routing
std::string_view getMatchedPathPattern() const
{
return matchedPathPattern();
}
/// Get the matched path pattern after routing
std::string_view matchedPathPattern() const
{
return std::string_view(matchedPathPatternData(),
matchedPathPatternLength());
}
/// Get the matched path pattern after routing (including matched parameters
/// in the query string)
virtual const std::vector<std::string> &getRoutingParameters() const = 0;
/// This method usually is called by the framework.
virtual void setRoutingParameters(std::vector<std::string> &&params) = 0;
virtual const char *matchedPathPatternData() const = 0;
virtual size_t matchedPathPatternLength() const = 0;
/// Return the string of http version of request, such as HTTP/1.0,
/// HTTP/1.1, etc.
virtual const char *versionString() const = 0;
const char *getVersionString() const
{
return versionString();
}
/// Return the enum type version of the request.
/**
* kHttp10 means Http version is 1.0
* kHttp11 means Http version is 1.1
*/
virtual Version version() const = 0;
/// Return the enum type version of the request.
Version getVersion() const
{
return version();
}
/// Get the session to which the request belongs.
virtual const SessionPtr &session() const = 0;
/// Get the session to which the request belongs.
const SessionPtr &getSession() const
{
return session();
}
/// Get the attributes store, users can add/get any type of data to/from
/// this store
virtual const AttributesPtr &attributes() const = 0;
/// Get the attributes store, users can add/get any type of data to/from
/// this store
const AttributesPtr &getAttributes() const
{
return attributes();
}
/// Get parameters of the request.
virtual const SafeStringMap<std::string> &parameters() const = 0;
/// Get parameters of the request.
const SafeStringMap<std::string> &getParameters() const
{
return parameters();
}
/// Get a parameter identified by the @param key
virtual const std::string &getParameter(const std::string &key) const = 0;
/**
* @brief Get the optional parameter identified by the @p key. if the
* parameter doesn't exist, or the original parameter can't be converted to
* a T type object, an empty optional object is returned.
*
* @tparam T
* @param key
* @return optional<T>
*/
template <typename T>
std::optional<T> getOptionalParameter(const std::string &key)
{
auto &params = getParameters();
auto it = params.find(key);
if (it != params.end())
{
try
{
return std::optional<T>(
drogon::utils::fromString<T>(it->second));
}
catch (const std::exception &e)
{
LOG_ERROR << e.what();
return std::optional<T>{};
}
}
else
{
return std::optional<T>{};
}
}
/// Return the remote IP address and port
virtual const trantor::InetAddress &peerAddr() const = 0;
const trantor::InetAddress &getPeerAddr() const
{
return peerAddr();
}
/// Return the local IP address and port
virtual const trantor::InetAddress &localAddr() const = 0;
const trantor::InetAddress &getLocalAddr() const
{
return localAddr();
}
/// Return the creation timestamp set by the framework.
virtual const trantor::Date &creationDate() const = 0;
const trantor::Date &getCreationDate() const
{
return creationDate();
}
// Return the peer certificate (if any)
virtual const trantor::CertificatePtr &peerCertificate() const = 0;
const trantor::CertificatePtr &getPeerCertificate() const
{
return peerCertificate();
}
/// Get the Json object of the request
/**
* The content type of the request must be 'application/json',
* otherwise the method returns an empty shared_ptr object.
*/
virtual const std::shared_ptr<Json::Value> &jsonObject() const = 0;
/// Get the Json object of the request
const std::shared_ptr<Json::Value> &getJsonObject() const
{
return jsonObject();
}
/**
* @brief Get the error message of parsing the JSON body received from peer.
* This method usually is called after getting a empty shared_ptr object
* by the getJsonObject() method.
*
* @return const std::string& The error message. An empty string is returned
* when no error occurs.
*/
virtual const std::string &getJsonError() const = 0;
/// Get the content type
virtual ContentType contentType() const = 0;
ContentType getContentType() const
{
return contentType();
}
/// Set the Http method
virtual void setMethod(const HttpMethod method) = 0;
/// Set the path of the request
virtual void setPath(const std::string &path) = 0;
virtual void setPath(std::string &&path) = 0;
/**
* @brief The default behavior is to encode the value of setPath
* using urlEncode. Setting the path encode to false avoid the
* value of path will be changed by the library
*
* @param bool true --> the path will be url encoded
* false --> using value of path as it is set
*/
virtual void setPathEncode(bool) = 0;
/// Set the parameter of the request
virtual void setParameter(const std::string &key,
const std::string &value) = 0;
/// Set or get the content type
virtual void setContentTypeCode(const ContentType type) = 0;
/// Set the content-type string, The string may contain the header name and
/// CRLF. Or just the MIME type
//
/// For example, "content-type: text/plain\r\n" or "text/plain"
void setContentTypeString(const std::string_view &typeString)
{
setContentTypeString(typeString.data(), typeString.size());
}
/// Set the request content-type string, The string
/// must contain the header name and CRLF.
/// For example, "content-type: text/plain\r\n"
virtual void setCustomContentTypeString(const std::string &type) = 0;
/// Add a cookie
virtual void addCookie(std::string key, std::string value) = 0;
/**
* @brief Set the request object to the pass-through mode or not. It's not
* by default when a new request object is created.
* In pass-through mode, no additional headers (including user-agent,
* connection, etc.) are added to the request. This mode is useful for some
* applications such as a proxy.
*
* @param flag
*/
virtual void setPassThrough(bool flag) = 0;
/// The following methods are a series of factory methods that help users
/// create request objects.
/// Create a normal request with http method Get and version Http1.1.
static HttpRequestPtr newHttpRequest();
/// Create a http request with:
/// Method: Get
/// Version: Http1.1
/// Content type: application/json, the @param data is serialized into the
/// content of the request.
static HttpRequestPtr newHttpJsonRequest(const Json::Value &data);
/// Create a http request with:
/// Method: Post
/// Version: Http1.1
/// Content type: application/x-www-form-urlencoded
static HttpRequestPtr newHttpFormPostRequest();
/// Create a http file upload request with:
/// Method: Post
/// Version: Http1.1
/// Content type: multipart/form-data
/// The @param files represents pload files which are transferred to the
/// server via the multipart/form-data format
static HttpRequestPtr newFileUploadRequest(
const std::vector<UploadFile> &files);
/**
* @brief Create a custom HTTP request object. For using this template,
* users must specialize the toRequest template.
*/
template <typename T>
static HttpRequestPtr newCustomHttpRequest(T &&obj)
{
return toRequest(std::forward<T>(obj));
}
virtual bool isOnSecureConnection() const noexcept = 0;
virtual void setContentTypeString(const char *typeString,
size_t typeStringLength) = 0;
virtual bool connected() const noexcept = 0;
virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept = 0;
virtual ~HttpRequest()
{
}
};
template <>
inline HttpRequestPtr toRequest<const Json::Value &>(const Json::Value &pJson)
{
return HttpRequest::newHttpJsonRequest(pJson);
}
template <>
inline HttpRequestPtr toRequest(Json::Value &&pJson)
{
return HttpRequest::newHttpJsonRequest(std::move(pJson));
}
template <>
inline std::shared_ptr<Json::Value> fromRequest(const HttpRequest &req)
{
return req.getJsonObject();
}
} // namespace drogon
+625
View File
@@ -0,0 +1,625 @@
/**
* @file HttpResponse.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/exports.h>
#include <trantor/net/Certificate.h>
#include <trantor/net/callbacks.h>
#include <trantor/net/AsyncStream.h>
#include <drogon/DrClassMap.h>
#include <drogon/Cookie.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpTypes.h>
#include <drogon/HttpViewData.h>
#include <drogon/utils/Utilities.h>
#include <json/json.h>
#include <memory>
#include <string>
#include <string_view>
namespace drogon
{
/// Abstract class for webapp developer to get or set the Http response;
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
/**
* @brief This template is used to convert a response object to a custom
* type object. Users must specialize the template for a particular type.
*/
template <typename T>
T fromResponse(const HttpResponse &)
{
LOG_ERROR
<< "You must specialize the fromResponse template for the type of "
<< DrClassMap::demangle(typeid(T).name());
exit(1);
}
/**
* @brief This template is used to create a response object from a custom
* type object by calling the newCustomHttpResponse(). Users must specialize
* the template for a particular type.
*/
template <typename T>
HttpResponsePtr toResponse(T &&)
{
LOG_ERROR << "You must specialize the toResponse template for the type of "
<< DrClassMap::demangle(typeid(T).name());
exit(1);
}
template <>
HttpResponsePtr toResponse<const Json::Value &>(const Json::Value &pJson);
template <>
HttpResponsePtr toResponse(Json::Value &&pJson);
template <>
inline HttpResponsePtr toResponse<Json::Value &>(Json::Value &pJson)
{
return toResponse((const Json::Value &)pJson);
}
class DROGON_EXPORT ResponseStream
{
public:
explicit ResponseStream(trantor::AsyncStreamPtr asyncStream)
: asyncStream_(std::move(asyncStream))
{
}
~ResponseStream()
{
close();
}
bool send(const std::string &data)
{
if (!asyncStream_)
{
return false;
}
std::ostringstream oss;
oss << std::hex << data.length() << "\r\n";
oss << data << "\r\n";
return asyncStream_->send(oss.str());
}
void close()
{
if (asyncStream_)
{
static std::string closeStream{"0\r\n\r\n"};
asyncStream_->send(closeStream);
asyncStream_->close();
asyncStream_.reset();
}
}
private:
trantor::AsyncStreamPtr asyncStream_;
};
using ResponseStreamPtr = std::unique_ptr<ResponseStream>;
class DROGON_EXPORT HttpResponse
{
public:
/**
* @brief This template enables automatic type conversion. For using this
* template, user must specialize the fromResponse template. For example a
* shared_ptr<Json::Value> specialization version is available above, so
* we can use the following code to get a json object:
* @code
* std::shared_ptr<Json::Value> jsonPtr = *responsePtr;
* @endcode
* With this template, user can use their favorite JSON library instead of
* the default jsoncpp library or convert the response to an object of any
* custom type.
*/
template <typename T>
operator T() const
{
return fromResponse<T>(*this);
}
/**
* @brief This template enables explicit type conversion, see the above
* template.
*/
template <typename T>
T as() const
{
return fromResponse<T>(*this);
}
/// Get the status code such as 200, 404
virtual HttpStatusCode statusCode() const = 0;
HttpStatusCode getStatusCode() const
{
return statusCode();
}
/// Set the status code of the response.
virtual void setStatusCode(HttpStatusCode code) = 0;
void setCustomStatusCode(int code,
std::string_view message = std::string_view{})
{
setCustomStatusCode(code, message.data(), message.length());
}
/// Get the creation timestamp of the response.
virtual const trantor::Date &creationDate() const = 0;
const trantor::Date &getCreationDate() const
{
return creationDate();
}
/// Set the http version, http1.0 or http1.1
virtual void setVersion(const Version v) = 0;
/// Set if close the connection after the request is sent.
/**
* @param on if the parameter is false, the connection keeps alive on the
* condition that the client request has a 'keep-alive' head, otherwise it
* is closed immediately after sending the last byte of the response. It's
* false by default when the response is created.
*/
virtual void setCloseConnection(bool on) = 0;
/// Get the status set by the setCloseConnection() method.
virtual bool ifCloseConnection() const = 0;
/// Set the response content type, such as text/html, text/plain, image/png
/// and so on. If the content type
/// is a text type, the character set is utf8.
virtual void setContentTypeCode(ContentType type) = 0;
/// Set the content-type string, The string may contain the header name and
/// CRLF. Or just the MIME type For example, "content-type: text/plain\r\n"
/// or "text/plain"
void setContentTypeString(const std::string_view &typeString)
{
setContentTypeString(typeString.data(), typeString.size());
}
/// Set the response content type and the content-type string, The string
/// may contain the header name and CRLF. Or just the MIME type
/// For example, "content-type: text/plain\r\n" or "text/plain"
void setContentTypeCodeAndCustomString(ContentType type,
const std::string_view &typeString)
{
setContentTypeCodeAndCustomString(type,
typeString.data(),
typeString.length());
}
template <int N>
void setContentTypeCodeAndCustomString(ContentType type,
const char (&typeString)[N])
{
assert(N > 0);
setContentTypeCodeAndCustomString(type, typeString, N - 1);
}
/// Set the response content type and the character set.
/// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const
/// std::string &charSet = "utf-8") = 0;
/// Get the response content type.
virtual ContentType contentType() const = 0;
ContentType getContentType() const
{
return contentType();
}
/// Get the header string identified by the key parameter.
/**
* @note
* If there is no the header, a empty string is returned.
* The key is case insensitive
*/
virtual const std::string &getHeader(std::string key) const = 0;
/**
* @brief Remove the header identified by the key parameter.
*
* @param key The key is case insensitive
*/
virtual void removeHeader(std::string key) = 0;
/// Get all headers of the response
virtual const SafeStringMap<std::string> &headers() const = 0;
/// Get all headers of the response
const SafeStringMap<std::string> &getHeaders() const
{
return headers();
}
/**
* @brief Set the header string identified by the field parameter
*
* @param field The field parameter is transformed to lower case before
* storing.
* @param value The value of the header.
*/
virtual void addHeader(std::string field, const std::string &value) = 0;
virtual void addHeader(std::string field, std::string &&value) = 0;
/// Add a cookie
virtual void addCookie(const std::string &key,
const std::string &value) = 0;
/// Add a cookie
virtual void addCookie(const Cookie &cookie) = 0;
virtual void addCookie(Cookie &&cookie) = 0;
/// Get the cookie identified by the key parameter.
/// If there is no the cookie, the empty cookie is returned.
virtual const Cookie &getCookie(const std::string &key) const = 0;
/// Get all cookies.
virtual const SafeStringMap<Cookie> &cookies() const = 0;
/// Get all cookies.
const SafeStringMap<Cookie> &getCookies() const
{
return cookies();
}
/// Remove the cookie identified by the key parameter.
virtual void removeCookie(const std::string &key) = 0;
/// Set the response body(content).
/**
* @note The body must match the content type
*/
virtual void setBody(const std::string &body) = 0;
/// Set the response body(content).
virtual void setBody(std::string &&body) = 0;
/// Set the response body(content).
template <int N>
void setBody(const char (&body)[N])
{
assert(strnlen(body, N) == N - 1);
setBody(body, N - 1);
}
/// Get the response body.
std::string_view body() const
{
return std::string_view{getBodyData(), getBodyLength()};
}
/// Get the response body.
std::string_view getBody() const
{
return body();
}
/// Return the string of http version of request, such as HTTP/1.0,
/// HTTP/1.1, etc.
virtual const char *versionString() const = 0;
const char *getVersionString() const
{
return versionString();
}
/// Return the enum type version of the response.
/**
* kHttp10 means Http version is 1.0
* kHttp11 means Http version is 1.1
*/
virtual Version version() const = 0;
/// Return the enum type version of the response.
Version getVersion() const
{
return version();
}
/// Reset the response object to its initial state
virtual void clear() = 0;
/// Set the expiration time of the response cache in memory.
/// in seconds, 0 means always cache, negative means not cache, default is
/// -1.
virtual void setExpiredTime(ssize_t expiredTime) = 0;
/// Get the expiration time of the response.
virtual ssize_t expiredTime() const = 0;
ssize_t getExpiredTime() const
{
return expiredTime();
}
/// Get the json object from the server response.
/// If the response is not in json format, then a empty shared_ptr is
/// returned.
virtual const std::shared_ptr<Json::Value> &jsonObject() const = 0;
const std::shared_ptr<Json::Value> &getJsonObject() const
{
return jsonObject();
}
/**
* @brief Get the error message of parsing the JSON body received from peer.
* This method usually is called after getting a empty shared_ptr object
* by the getJsonObject() method.
*
* @return const std::string& The error message. An empty string is returned
* when no error occurs.
*/
virtual const std::string &getJsonError() const = 0;
/**
* @brief Set the response object to the pass-through mode or not. It's not
* by default when a new response object is created.
* In pass-through mode, no additional headers (including server, date,
* content-type and content-length, etc.) are added to the response. This
* mode is useful for some applications such as a proxy.
*
* @param flag
*/
virtual void setPassThrough(bool flag) = 0;
/**
* @brief Get the certificate of the peer, if any.
* @return The certificate of the peer. nullptr is none.
*/
virtual const trantor::CertificatePtr &peerCertificate() const = 0;
const trantor::CertificatePtr &getPeerCertificate() const
{
return peerCertificate();
}
/* The following methods are a series of factory methods that help users
* create response objects. */
/// Create a normal response with a status code of 200ok and a content type
/// of text/html.
static HttpResponsePtr newHttpResponse();
/// Create a response with a status code and a content type
static HttpResponsePtr newHttpResponse(HttpStatusCode code,
ContentType type);
/// Create a response which returns a 404 page.
static HttpResponsePtr newNotFoundResponse(
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response which returns a json object. Its content-type is set
/// to application/json.
static HttpResponsePtr newHttpJsonResponse(const Json::Value &data);
/// Create a response which returns a json object. Its content-type is set
/// to application/json.
static HttpResponsePtr newHttpJsonResponse(Json::Value &&data);
/// Create a response that returns a page rendered by a view named
/// viewName.
/**
* @param viewName The name of the view
* @param data is the data displayed on the page.
* @note For more details, see the wiki pages, the "View" section.
*/
static HttpResponsePtr newHttpViewResponse(
const std::string &viewName,
const HttpViewData &data = HttpViewData(),
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that returns a redirection page, redirecting to
/// another page located in the location parameter.
/**
* @param location The location to redirect
* @param status The HTTP status code, k302Found by default. Users could set
* it to one of the 301, 302, 303, 307, ...
*/
static HttpResponsePtr newRedirectionResponse(
const std::string &location,
HttpStatusCode status = k302Found);
/// Create a response that returns a file to the client.
/**
* @param fullPath is the full path to the file.
* @param attachmentFileName if the parameter is not empty, the browser
* does not open the file, but saves it as an attachment.
* @param type the content type code. If the parameter is CT_NONE, the
* content type is set by drogon based on the file extension and typeString.
* Set it to CT_CUSTOM when no drogon internal content type matches.
* @param typeString the MIME string of the content type.
*/
static HttpResponsePtr newFileResponse(
const std::string &fullPath,
const std::string &attachmentFileName = "",
ContentType type = CT_NONE,
const std::string &typeString = "",
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that returns part of a file to the client.
/**
* @brief If offset and length can not be satisfied, statusCode will be set
* to k416RequestedRangeNotSatisfiable, and nothing else will be modified.
*
* @param fullPath is the full path to the file.
* @param offset is the offset to begin sending, in bytes.
* @param length is the total length to send, in bytes. In particular,
* length = 0 means send all content from offset till end of file.
* @param setContentRange whether set 'Content-Range' header automatically.
* @param attachmentFileName if the parameter is not empty, the browser
* does not open the file, but saves it as an attachment.
* @param type the content type code. If the parameter is CT_NONE, the
* content type is set by drogon based on the file extension and typeString.
* Set it to CT_CUSTOM when no drogon internal content type matches.
* @param typeString the MIME string of the content type.
*/
static HttpResponsePtr newFileResponse(
const std::string &fullPath,
size_t offset,
size_t length,
bool setContentRange = true,
const std::string &attachmentFileName = "",
ContentType type = CT_NONE,
const std::string &typeString = "",
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that returns a file to the client from buffer in
/// memory/stack
/**
* @param pBuffer is a uint 8 bit flat buffer for object/files in memory
* @param bufferLength is the length of the expected buffer
* @param attachmentFileName if the parameter is not empty, the browser
* does not open the file, but saves it as an attachment.
* @param type the content type code. If the parameter is CT_NONE, the
* content type is set by drogon based on the file extension and typeString.
* Set it to CT_CUSTOM when no drogon internal content type matches.
* @param typeString the MIME string of the content type.
*/
static HttpResponsePtr newFileResponse(
const unsigned char *pBuffer,
size_t bufferLength,
const std::string &attachmentFileName = "",
ContentType type = CT_NONE,
const std::string &typeString = "");
/// Create a response that returns a file to the client from a callback
/// function
/**
* @note if the Connection is keep-alive and the Content-Length header is
* not set, the stream data is sent with Transfer-Encoding: chunked.
* @param callback function to retrieve the stream data (stream ends when a
* zero size is returned) the callback will be called with
* nullptr when the send is finished/interrupted so that it
* cleans up its internals.
* @param attachmentFileName if the parameter is not empty, the browser
* does not open the file, but saves it as an
* attachment.
* @param type the content type code. If the parameter is CT_NONE, the
* content type is set by drogon based on the file extension and
* typeString. Set it to CT_CUSTOM when no drogon internal
* content type matches.
* @param typeString the MIME string of the content type.
*/
static HttpResponsePtr newStreamResponse(
const std::function<std::size_t(char *, std::size_t)> &callback,
const std::string &attachmentFileName = "",
ContentType type = CT_NONE,
const std::string &typeString = "",
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that allows sending asynchronous data from a callback
/// function
/**
* @note Async streams are always sent with Transfer-Encoding: chunked.
* @param callback function that receives the asynchronous HTTP stream. You
* may call the stream->send() method to transmit new data.
* The send method will return true as long as the stream is
* still open. Once you have finished sending data, or the
* stream->send() function returned false, you should call
* stream->close() to gracefully close the chunked transfer.
* @param disableKickoffTimeout set this to true to disable trantors default
* kickoff timeout. This is useful if you need
* long running asynchronous streams.
*/
static HttpResponsePtr newAsyncStreamResponse(
const std::function<void(ResponseStreamPtr)> &callback,
bool disableKickoffTimeout = false);
/**
* @brief Create a custom HTTP response object. For using this template,
* users must specialize the toResponse template.
*/
template <typename T>
static HttpResponsePtr newCustomHttpResponse(T &&obj)
{
return toResponse(std::forward<T>(obj));
}
/**
* @brief If the response is a file response (i.e. created by
* newFileResponse) returns the path on the filesystem. Otherwise a
* empty string.
*/
virtual const std::string &sendfileName() const = 0;
/**
* @brief Returns the range of the file response as a pair ot size_t
* (offset, length). Length of 0 means the entire file is sent. Behavior of
* this function is undefined if the response if not a file response
*/
using SendfileRange = std::pair<size_t, size_t>; // { offset, length }
virtual const SendfileRange &sendfileRange() const = 0;
/**
* @brief If the response is a stream response (i.e. created by
* newStreamResponse) returns the callback function. Otherwise a
* null function.
*/
virtual const std::function<std::size_t(char *, std::size_t)> &
streamCallback() const = 0;
/**
* @brief If the response is a async stream response (i.e. created by
* asyncStreamCallback) returns the stream ptr.
*/
virtual const std::function<void(ResponseStreamPtr)> &asyncStreamCallback()
const = 0;
/**
* @brief Returns the content type associated with the response
*/
virtual std::string contentTypeString() const = 0;
virtual ~HttpResponse()
{
}
private:
virtual void setBody(const char *body, size_t len) = 0;
virtual const char *getBodyData() const = 0;
virtual size_t getBodyLength() const = 0;
virtual void setContentTypeCodeAndCustomString(ContentType type,
const char *typeString,
size_t typeStringLength) = 0;
virtual void setContentTypeString(const char *typeString,
size_t typeStringLength) = 0;
virtual void setCustomStatusCode(int code,
const char *message,
size_t messageLength) = 0;
};
template <>
inline HttpResponsePtr toResponse<const Json::Value &>(const Json::Value &pJson)
{
return HttpResponse::newHttpJsonResponse(pJson);
}
template <>
inline HttpResponsePtr toResponse(Json::Value &&pJson)
{
return HttpResponse::newHttpJsonResponse(std::move(pJson));
}
template <>
inline std::shared_ptr<Json::Value> fromResponse(const HttpResponse &resp)
{
return resp.getJsonObject();
}
} // namespace drogon
@@ -0,0 +1,116 @@
/**
*
* HttpSimpleController.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/DrObject.h>
#include <drogon/utils/HttpConstraint.h>
#include <drogon/HttpAppFramework.h>
#include <trantor/utils/Logger.h>
#include <iostream>
#include <string>
#include <vector>
#define PATH_LIST_BEGIN \
static void initPathRouting() \
{
#define PATH_ADD(path, ...) registerSelf__(path, {__VA_ARGS__})
#define PATH_LIST_END }
namespace drogon
{
/**
* @brief The abstract base class for HTTP simple controllers.
*
*/
class HttpSimpleControllerBase : public virtual DrObjectBase
{
public:
/**
* @brief The function is called when a HTTP request is routed to the
* controller.
*
* @param req The HTTP request.
* @param callback The callback via which a response is returned.
*/
virtual void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) = 0;
virtual ~HttpSimpleControllerBase()
{
}
};
/**
* @brief The reflection base class template for HTTP simple controllers
*
* @tparam T The type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have nondefault constructors.
*/
template <typename T, bool AutoCreation = true>
class HttpSimpleController : public DrObject<T>, public HttpSimpleControllerBase
{
public:
static const bool isAutoCreation = AutoCreation;
virtual ~HttpSimpleController()
{
}
protected:
HttpSimpleController()
{
}
static void registerSelf__(
const std::string &path,
const std::vector<internal::HttpConstraint> &constraints)
{
LOG_TRACE << "register simple controller("
<< HttpSimpleController<T, AutoCreation>::classTypeName()
<< ") on path:" << path;
app().registerHttpSimpleController(
path,
HttpSimpleController<T, AutoCreation>::classTypeName(),
constraints);
}
private:
class pathRegistrator
{
public:
pathRegistrator()
{
if (AutoCreation)
{
T::initPathRouting();
}
}
};
friend pathRegistrator;
static pathRegistrator registrator_;
virtual void *touch()
{
return &registrator_;
}
};
template <typename T, bool AutoCreation>
typename HttpSimpleController<T, AutoCreation>::pathRegistrator
HttpSimpleController<T, AutoCreation>::registrator_;
} // namespace drogon
+294
View File
@@ -0,0 +1,294 @@
/**
* @file HttpTypes.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 <atomic>
#include <thread>
#include <iostream>
#include <string_view>
#include <trantor/utils/LogStream.h>
#include <drogon/utils/Utilities.h>
namespace drogon
{
enum HttpStatusCode
{
kUnknown = 0,
k100Continue = 100,
k101SwitchingProtocols = 101,
k102Processing = 102,
k103EarlyHints = 103,
k200OK = 200,
k201Created = 201,
k202Accepted = 202,
k203NonAuthoritativeInformation = 203,
k204NoContent = 204,
k205ResetContent = 205,
k206PartialContent = 206,
k207MultiStatus = 207,
k208AlreadyReported = 208,
k226IMUsed = 226,
k300MultipleChoices = 300,
k301MovedPermanently = 301,
k302Found = 302,
k303SeeOther = 303,
k304NotModified = 304,
k305UseProxy = 305,
k306Unused = 306,
k307TemporaryRedirect = 307,
k308PermanentRedirect = 308,
k400BadRequest = 400,
k401Unauthorized = 401,
k402PaymentRequired = 402,
k403Forbidden = 403,
k404NotFound = 404,
k405MethodNotAllowed = 405,
k406NotAcceptable = 406,
k407ProxyAuthenticationRequired = 407,
k408RequestTimeout = 408,
k409Conflict = 409,
k410Gone = 410,
k411LengthRequired = 411,
k412PreconditionFailed = 412,
k413RequestEntityTooLarge = 413,
k414RequestURITooLarge = 414,
k415UnsupportedMediaType = 415,
k416RequestedRangeNotSatisfiable = 416,
k417ExpectationFailed = 417,
k418ImATeapot = 418,
k421MisdirectedRequest = 421,
k422UnprocessableEntity = 422,
k423Locked = 423,
k424FailedDependency = 424,
k425TooEarly = 425,
k426UpgradeRequired = 426,
k428PreconditionRequired = 428,
k429TooManyRequests = 429,
k431RequestHeaderFieldsTooLarge = 431,
k451UnavailableForLegalReasons = 451,
k500InternalServerError = 500,
k501NotImplemented = 501,
k502BadGateway = 502,
k503ServiceUnavailable = 503,
k504GatewayTimeout = 504,
k505HTTPVersionNotSupported = 505,
k506VariantAlsoNegotiates = 506,
k507InsufficientStorage = 507,
k508LoopDetected = 508,
k510NotExtended = 510,
k511NetworkAuthenticationRequired = 511
};
enum class Version
{
kUnknown = 0,
kHttp10,
kHttp11
};
enum ContentType
{
CT_NONE = 0,
CT_APPLICATION_JSON,
CT_TEXT_PLAIN,
CT_TEXT_HTML,
CT_APPLICATION_X_FORM,
CT_APPLICATION_X_JAVASCRIPT [[deprecated("use CT_TEXT_JAVASCRIPT")]],
CT_TEXT_JAVASCRIPT,
CT_TEXT_CSS,
CT_TEXT_CSV,
CT_TEXT_XML, // suggests human readable xml
CT_APPLICATION_XML, // suggest machine-to-machine xml
CT_TEXT_XSL,
CT_APPLICATION_WASM,
CT_APPLICATION_OCTET_STREAM,
CT_APPLICATION_FONT_WOFF,
CT_APPLICATION_FONT_WOFF2,
CT_APPLICATION_GZIP,
CT_APPLICATION_JAVA_ARCHIVE,
CT_APPLICATION_PDF,
CT_APPLICATION_MSWORD,
CT_APPLICATION_MSWORDX,
CT_APPLICATION_VND_MS_FONTOBJ,
CT_APPLICATION_VND_RAR,
CT_APPLICATION_XHTML,
CT_APPLICATION_X_7Z,
CT_APPLICATION_X_BZIP,
CT_APPLICATION_X_BZIP2,
CT_APPLICATION_X_HTTPD_PHP,
CT_APPLICATION_X_FONT_TRUETYPE,
CT_APPLICATION_X_FONT_OPENTYPE,
CT_APPLICATION_X_TAR,
CT_APPLICATION_X_TGZ,
CT_APPLICATION_X_XZ,
CT_APPLICATION_ZIP,
CT_AUDIO_AAC,
CT_AUDIO_AC3,
CT_AUDIO_AIFF,
CT_AUDIO_FLAC,
CT_AUDIO_MATROSKA,
CT_AUDIO_MPEG,
CT_AUDIO_MPEG4,
CT_AUDIO_OGG,
CT_AUDIO_WAVE,
CT_AUDIO_WEBM,
CT_AUDIO_X_APE,
CT_AUDIO_X_MS_WMA,
CT_AUDIO_X_TTA,
CT_AUDIO_X_WAVPACK,
CT_IMAGE_APNG,
CT_IMAGE_AVIF,
CT_IMAGE_BMP,
CT_IMAGE_GIF,
CT_IMAGE_ICNS,
CT_IMAGE_JPG,
CT_IMAGE_JP2,
CT_IMAGE_PNG,
CT_IMAGE_SVG_XML,
CT_IMAGE_TIFF,
CT_IMAGE_WEBP,
CT_IMAGE_X_MNG,
CT_IMAGE_X_TGA,
CT_IMAGE_XICON,
CT_VIDEO_APG,
CT_VIDEO_AV1,
CT_VIDEO_QUICKTIME,
CT_VIDEO_MATROSKA,
CT_VIDEO_MP4,
CT_VIDEO_MPEG,
CT_VIDEO_MPEG2TS,
CT_VIDEO_OGG,
CT_VIDEO_WEBM,
CT_VIDEO_X_M4V,
CT_VIDEO_X_MSVIDEO,
CT_MULTIPART_FORM_DATA,
CT_CUSTOM
};
enum FileType
{
FT_UNKNOWN = 0,
FT_CUSTOM,
FT_DOCUMENT,
FT_ARCHIVE,
FT_AUDIO,
FT_MEDIA,
FT_IMAGE
};
enum HttpMethod
{
Get = 0,
Post,
Head,
Put,
Delete,
Options,
Patch,
Invalid
};
enum class ReqResult
{
Ok = 0,
BadResponse,
NetworkFailure,
BadServerAddress,
Timeout,
HandshakeError,
InvalidCertificate,
EncryptionFailure,
};
enum class WebSocketMessageType
{
Text = 0,
Binary,
Ping,
Pong,
Close,
Unknown
};
inline std::string_view to_string_view(drogon::ReqResult result)
{
switch (result)
{
case ReqResult::Ok:
return "OK";
case ReqResult::BadResponse:
return "Bad response from server";
case ReqResult::NetworkFailure:
return "Network failure";
case ReqResult::BadServerAddress:
return "Bad server address";
case ReqResult::Timeout:
return "Timeout";
case ReqResult::HandshakeError:
return "Handshake error";
case ReqResult::InvalidCertificate:
return "Invalid certificate";
case ReqResult::EncryptionFailure:
return "Unrecoverable encryption failure";
default:
return "Unknown error";
}
}
inline std::string to_string(drogon::ReqResult result)
{
auto sv = to_string_view(result);
return std::string(sv.data(), sv.size());
}
inline std::ostream &operator<<(std::ostream &out, drogon::ReqResult result)
{
return out << to_string_view(result);
}
inline trantor::LogStream &operator<<(trantor::LogStream &out,
drogon::ReqResult result)
{
return out << to_string_view(result);
}
inline std::string_view to_string_view(drogon::HttpMethod method)
{
switch (method)
{
case drogon::HttpMethod::Get:
return "GET";
case drogon::HttpMethod::Post:
return "POST";
case drogon::HttpMethod::Head:
return "HEAD";
case drogon::HttpMethod::Put:
return "PUT";
case drogon::HttpMethod::Delete:
return "DELETE";
case drogon::HttpMethod::Options:
return "OPTIONS";
case drogon::HttpMethod::Patch:
return "PATCH";
default:
return "INVALID";
}
}
inline std::string to_string(drogon::HttpMethod method)
{
auto sv = to_string_view(method);
return std::string(sv.data(), sv.size());
}
} // namespace drogon
+175
View File
@@ -0,0 +1,175 @@
/**
*
* @file HttpViewData.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/exports.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <sstream>
#include <string>
#include <unordered_map>
#include <stdarg.h>
#include <stdio.h>
#include <type_traits>
#include <any>
#include <string_view>
namespace drogon
{
/// This class represents the data set displayed in views.
class DROGON_EXPORT HttpViewData
{
public:
/// The function template is used to get an item in the data set by the key
/// parameter.
template <typename T>
const T &get(const std::string &key) const
{
static const T nullVal = T();
auto it = viewData_.find(key);
if (it != viewData_.end())
{
if (typeid(T) == it->second.type())
{
return *(std::any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
return nullVal;
}
/// Insert an item identified by the key parameter into the data set;
void insert(const std::string &key, std::any &&obj)
{
viewData_[key] = std::move(obj);
}
void insert(const std::string &key, const std::any &obj)
{
viewData_[key] = obj;
}
/// Insert an item identified by the key parameter into the data set; The
/// item is converted to a string.
template <typename T>
void insertAsString(const std::string &key, T &&val)
{
std::stringstream ss;
ss << val;
viewData_[key] = ss.str();
}
/// Insert a formatted string identified by the key parameter.
void insertFormattedString(const std::string &key, const char *format, ...)
{
std::string strBuffer;
strBuffer.resize(128);
va_list ap, backup_ap;
va_start(ap, format);
va_copy(backup_ap, ap);
auto result = vsnprintf((char *)strBuffer.data(),
strBuffer.size(),
format,
backup_ap);
va_end(backup_ap);
if ((result >= 0) &&
(static_cast<std::string::size_type>(result) < strBuffer.size()))
{
strBuffer.resize(static_cast<std::string::size_type>(result));
}
else
{
while (true)
{
if (result < 0)
{
// Older snprintf() behavior. Just try doubling the buffer
// size
strBuffer.resize(strBuffer.size() * 2);
}
else
{
strBuffer.resize(result + 1);
}
va_copy(backup_ap, ap);
result = vsnprintf((char *)strBuffer.data(),
strBuffer.size(),
format,
backup_ap);
va_end(backup_ap);
if ((result >= 0) &&
((std::string::size_type)result < strBuffer.size()))
{
strBuffer.resize(result);
break;
}
}
}
va_end(ap);
viewData_[key] = std::move(strBuffer);
}
/// Get the 'any' object by the key parameter.
std::any &operator[](const std::string &key) const
{
return viewData_[key];
}
/// Translate some special characters to HTML format
/**
* such as:
* @code
" --> &quot;
& --> &amp;
< --> &lt;
> --> &gt;
@endcode
*/
static std::string htmlTranslate(const char *str, size_t length);
static std::string htmlTranslate(const std::string_view &str)
{
return htmlTranslate(str.data(), str.length());
}
static bool needTranslation(const std::string_view &str)
{
for (auto const &c : str)
{
switch (c)
{
case '"':
case '&':
case '<':
case '>':
return true;
default:
continue;
}
}
return false;
}
protected:
using ViewDataMap = std::unordered_map<std::string, std::any>;
mutable ViewDataMap viewData_;
};
} // namespace drogon
+168
View File
@@ -0,0 +1,168 @@
/**
*
* @file IOThreadStorage.h
* @author Daniel Mensinger
*
* Copyright 2019, Daniel Mensinger. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/HttpAppFramework.h>
#include <trantor/utils/NonCopyable.h>
#include <memory>
#include <vector>
#include <limits>
#include <functional>
namespace drogon
{
/**
* @brief Utility class for thread storage handling
*
* Thread storage allows the efficient handling of reusable data without thread
* synchronisation. For instance, such a thread storage would be useful to store
* database connections.
*
* Example usage:
*
* @code
* struct MyThreadData {
* int threadLocal = 42;
* std::string something = "foo";
* };
*
* class MyController : public HttpController<MyController> {
* public:
* METHOD_LIST_BEGIN
* ADD_METHOD_TO(MyController::endpoint, "/some/path", Get);
* METHOD_LIST_END
*
* void login(const HttpRequestPtr &req,
* std::function<void (const HttpResponsePtr &)> &&callback) {
* assert(storage_->threadLocal == 42);
*
* // handle the request
* }
*
* private:
* IOThreadStorage<MyThreadData> storage_;
* };
* @endcode
*/
template <typename C>
class IOThreadStorage : public trantor::NonCopyable
{
public:
using ValueType = C;
using InitCallback = std::function<void(ValueType &, size_t)>;
template <typename... Args>
IOThreadStorage(Args &&...args)
{
static_assert(std::is_constructible<C, Args &&...>::value,
"Unable to construct storage with given signature");
size_t numThreads = app().getThreadNum();
assert(numThreads > 0 &&
numThreads != (std::numeric_limits<size_t>::max)());
// set the size to numThreads+1 to enable access to this in the main
// thread.
storage_.reserve(numThreads + 1);
for (size_t i = 0; i <= numThreads; ++i)
{
storage_.emplace_back(std::forward<Args>(args)...);
}
}
void init(const InitCallback &initCB)
{
for (size_t i = 0; i < storage_.size(); ++i)
{
initCB(storage_[i], i);
}
}
/**
* @brief Get the thread storage associate with the current thread
*
* This function may only be called in a request handler
*/
inline ValueType &getThreadData()
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return storage_[idx];
}
inline const ValueType &getThreadData() const
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return storage_[idx];
}
/**
* @brief Sets the thread data for the current thread
*
* This function may only be called in a request handler
*/
inline void setThreadData(const ValueType &newData)
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
storage_[idx] = newData;
}
inline void setThreadData(ValueType &&newData)
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
storage_[idx] = std::move(newData);
}
inline ValueType *operator->()
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return &storage_[idx];
}
inline ValueType &operator*()
{
return getThreadData();
}
inline const ValueType *operator->() const
{
size_t idx = app().getCurrentThreadIndex();
assert(idx < storage_.size());
return &storage_[idx];
}
inline const ValueType &operator*() const
{
return getThreadData();
}
private:
std::vector<ValueType> storage_;
};
inline trantor::EventLoop *getIOThreadStorageLoop(size_t index) noexcept(false)
{
if (index > drogon::app().getThreadNum())
{
throw std::out_of_range("Event loop index is out of range");
}
if (index == drogon::app().getThreadNum())
return drogon::app().getLoop();
return drogon::app().getIOLoop(index);
}
} // namespace drogon
@@ -0,0 +1,36 @@
/**
*
* @file IntranetIpFilter.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/exports.h>
#include <drogon/HttpFilter.h>
namespace drogon
{
/**
* @brief A filter that prohibit access from external networks
*/
class DROGON_EXPORT IntranetIpFilter : public HttpFilter<IntranetIpFilter>
{
public:
IntranetIpFilter()
{
}
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
} // namespace drogon
@@ -0,0 +1,36 @@
/**
*
* @file LocalHostFilter.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/exports.h>
#include <drogon/HttpFilter.h>
namespace drogon
{
/**
* @brief A filter that prohibit access from other hosts.
*/
class DROGON_EXPORT LocalHostFilter : public HttpFilter<LocalHostFilter>
{
public:
LocalHostFilter()
{
}
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
} // namespace drogon
+191
View File
@@ -0,0 +1,191 @@
/**
*
* @file MultiPart.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/utils/Utilities.h"
#include <drogon/exports.h>
#include <drogon/HttpRequest.h>
#include <unordered_map>
#include <string>
#include <vector>
#include <memory>
#include <string_view>
namespace drogon
{
class HttpFileImpl;
/**
* @brief This class represents a uploaded file by a HTTP request.
*
*/
class DROGON_EXPORT HttpFile
{
public:
explicit HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept;
/// Return the file name;
const std::string &getFileName() const noexcept;
/// Return the file extension;
/// Note: After the HttpFile object is destroyed, do not use this
/// std::string_view object.
std::string_view getFileExtension() const noexcept;
/// Return the name of the item in multiple parts.
const std::string &getItemName() const noexcept;
/// Return the type of file.
FileType getFileType() const noexcept;
/// Set the file name, usually called by the MultiPartParser parser.
void setFileName(const std::string &fileName) noexcept;
/// Set the contents of the file, usually called by the MultiPartParser
/// parser.
void setFile(const char *data, size_t length) noexcept;
/// 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 @p 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;
/**
* @brief return the content of the file.
*
* @return std::string_view
*/
std::string_view fileContent() const noexcept
{
return std::string_view{fileData(), fileLength()};
}
/// Return the file length.
size_t fileLength() const noexcept;
/// Return the content-type of the file.
drogon::ContentType getContentType() const noexcept;
/**
* @brief return the pointer of the file data.
*
* @return const char*
* @note This function just returns the beginning of the file data in
* memory. Users mustn't assume that there is an \0 character at the end of
* the file data even if the type of the file is text. One should get the
* length of the file by the fileLength() method, or use the fileContent()
* method.
*/
const char *fileData() const noexcept;
/// Return the md5 string of the file
std::string getMd5() const noexcept;
/// Return the content transfer encoding of the file.
const std::string &getContentTransferEncoding() const noexcept;
private:
std::shared_ptr<HttpFileImpl> implPtr_;
};
/// A parser class which help the user to get the files and the parameters in
/// the multipart format request.
class DROGON_EXPORT MultiPartParser
{
public:
MultiPartParser(){};
MultiPartParser(const MultiPartParser &other) = default; // Copyable
MultiPartParser(MultiPartParser &&other) = default; // Movable
~MultiPartParser(){};
/// Get files, This method should be called after calling the parse()
/// method.
const std::vector<HttpFile> &getFiles() const;
/// Get files in a map, the keys of the map are item names of the files.
std::unordered_map<std::string, HttpFile> getFilesMap() const;
/// Get parameters, This method should be called after calling the parse ()
/// method.
const SafeStringMap<std::string> &getParameters() const;
/// Get the value of an optional parameter
/// This method should be called after calling the parse() method.
template <typename T>
std::optional<T> getOptionalParameter(const std::string &key)
{
auto &params = getParameters();
auto it = params.find(key);
if (it != params.end())
{
try
{
return std::optional<T>(utils::fromString<T>(it->second));
}
catch (const std::exception &e)
{
LOG_ERROR << e.what();
return std::optional<T>{};
}
}
else
{
return std::optional<T>{};
}
}
/// Get the value of a parameter
/// This method should be called after calling the parse() method.
/// Note: returns a default T object if the parameter is missing
template <typename T>
T getParameter(const std::string &key)
{
return getOptionalParameter<T>(key).value_or(T{});
}
/// Parse the http request stream to get files and parameters.
int parse(const HttpRequestPtr &req);
protected:
std::vector<HttpFile> files_;
SafeStringMap<std::string> parameters_;
int parse(const HttpRequestPtr &req,
const char *boundaryData,
size_t boundaryLen);
int parseEntity(const HttpRequestPtr &req,
const char *begin,
const char *end);
};
/// In order to be compatible with old interfaces
using FileUpload = MultiPartParser;
} // namespace drogon
+36
View File
@@ -0,0 +1,36 @@
// this file is generated by program automatically,don't modify it!
/**
*
* @file NotFound.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/exports.h>
#include <drogon/DrTemplate.h>
namespace drogon
{
/**
* @brief This class is used by the drogon to generate the 404 page. Users don't
* use this class directly.
*/
class DROGON_EXPORT NotFound final : public drogon::DrTemplate<NotFound>
{
public:
NotFound()
{
}
std::string genText(const drogon::HttpViewData &) override;
};
} // namespace drogon
+304
View File
@@ -0,0 +1,304 @@
/**
*
* @file PubSubService.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 <functional>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <memory>
#include <unordered_map>
namespace drogon
{
using SubscriberID = uint64_t;
/**
* @brief This class template presents an unnamed topic.
*
* @tparam MessageType
*/
template <typename MessageType>
class Topic : public trantor::NonCopyable
{
public:
using MessageHandler = std::function<void(const MessageType &)>;
#if __cplusplus >= 201703L | defined _WIN32
using SharedMutex = std::shared_mutex;
#else
using SharedMutex = std::shared_timed_mutex;
#endif
/**
* @brief Publish a message, every subscriber in the topic will receive the
* message.
*
* @param message
*/
void publish(const MessageType &message) const
{
std::shared_lock<SharedMutex> lock(mutex_);
for (auto &pair : handlersMap_)
{
pair.second(message);
}
}
/**
* @brief Subscribe to the topic.
*
* @param handler is invoked when a message arrives.
* @return SubscriberID
*/
SubscriberID subscribe(const MessageHandler &handler)
{
std::unique_lock<SharedMutex> lock(mutex_);
handlersMap_[++id_] = handler;
return id_;
}
/**
* @brief Subscribe to the topic.
*
* @param handler is invoked when a message arrives.
* @return SubscriberID
*/
SubscriberID subscribe(MessageHandler &&handler)
{
std::unique_lock<SharedMutex> lock(mutex_);
handlersMap_[++id_] = std::move(handler);
return id_;
}
/**
* @brief Unsubscribe from the topic.
*/
void unsubscribe(SubscriberID id)
{
std::unique_lock<SharedMutex> lock(mutex_);
handlersMap_.erase(id);
}
/**
* @brief Check if the topic is empty.
*
* @return true means there are no subscribers.
* @return false means there are subscribers in the topic.
*/
bool empty() const
{
std::shared_lock<SharedMutex> lock(mutex_);
return handlersMap_.empty();
}
/**
* @brief Remove all subscribers from the topic.
*
*/
void clear()
{
std::unique_lock<SharedMutex> lock(mutex_);
handlersMap_.clear();
}
private:
std::unordered_map<SubscriberID, MessageHandler> handlersMap_;
mutable SharedMutex mutex_;
SubscriberID id_{0};
};
/**
* @brief This class template implements a publish-subscribe pattern with
* multiple named topics.
*
* @tparam MessageType The message type.
*/
template <typename MessageType>
class PubSubService : public trantor::NonCopyable
{
public:
using MessageHandler =
std::function<void(const std::string &, const MessageType &)>;
#if __cplusplus >= 201703L | defined _WIN32
using SharedMutex = std::shared_mutex;
#else
using SharedMutex = std::shared_timed_mutex;
#endif
/**
* @brief Publish a message to a topic. The message will be broadcasted to
* every subscriber.
*/
void publish(const std::string &topicName, const MessageType &message) const
{
std::shared_ptr<Topic<MessageType>> topicPtr;
{
std::shared_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter != topicMap_.end())
{
topicPtr = iter->second;
}
else
{
return;
}
}
topicPtr->publish(message);
}
/**
* @brief Subscribe to a topic. When a message is published to the topic,
* the handler is invoked by passing the topic and message as parameters.
*/
SubscriberID subscribe(const std::string &topicName,
const MessageHandler &handler)
{
auto topicHandler = [topicName, handler](const MessageType &message) {
handler(topicName, message);
};
return subscribeToTopic(topicName, std::move(topicHandler));
}
/**
* @brief Subscribe to a topic. When a message is published to the topic,
* the handler is invoked by passing the topic and message as parameters.
* @param topicName Topic name.
* @param handler The message handler.
* @return The subscriber ID.
*/
SubscriberID subscribe(const std::string &topicName,
MessageHandler &&handler)
{
auto topicHandler = [topicName, handler = std::move(handler)](
const MessageType &message) {
handler(topicName, message);
};
return subscribeToTopic(topicName, std::move(topicHandler));
}
/**
* @brief Unsubscribe from a topic.
*
* @param topicName Topic name.
* @param id The subscriber ID returned from the subscribe method.
*/
void unsubscribe(const std::string &topicName, SubscriberID id)
{
{
std::shared_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter == topicMap_.end())
{
return;
}
iter->second->unsubscribe(id);
if (!iter->second->empty())
return;
}
std::unique_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter == topicMap_.end())
{
return;
}
if (iter->second->empty())
topicMap_.erase(iter);
}
/**
* @brief return the number of topics.
*/
size_t size() const
{
std::shared_lock<SharedMutex> lock(mutex_);
return topicMap_.size();
}
/**
* @brief remove all topics.
*/
void clear()
{
std::unique_lock<SharedMutex> lock(mutex_);
topicMap_.clear();
}
/**
* @brief Remove a topic
*
*/
void removeTopic(const std::string &topicName)
{
std::unique_lock<SharedMutex> lock(mutex_);
topicMap_.erase(topicName);
}
/**
* @brief Check if a topic is empty.
*
* @param topicName The topic name.
* @return true means there are no subscribers.
* @return false means there are subscribers in the topic.
*/
bool isTopicEmpty(const std::string &topicName) const
{
std::shared_ptr<Topic<MessageType>> topicPtr;
{
std::shared_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter != topicMap_.end())
{
topicPtr = iter->second;
}
else
{
return true;
}
}
return topicPtr->empty();
}
private:
std::unordered_map<std::string, std::shared_ptr<Topic<MessageType>>>
topicMap_;
mutable SharedMutex mutex_;
SubscriberID subID_ = 0;
SubscriberID subscribeToTopic(
const std::string &topicName,
typename Topic<MessageType>::MessageHandler &&handler)
{
{
std::shared_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter != topicMap_.end())
{
return iter->second->subscribe(std::move(handler));
}
}
std::unique_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter != topicMap_.end())
{
return iter->second->subscribe(std::move(handler));
}
auto topicPtr = std::make_shared<Topic<MessageType>>();
auto id = topicPtr->subscribe(std::move(handler));
topicMap_[topicName] = std::move(topicPtr);
return id;
}
};
} // namespace drogon
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <drogon/exports.h>
#include <memory>
#include <chrono>
#include <mutex>
#include <string>
namespace drogon
{
enum class DROGON_EXPORT RateLimiterType
{
kFixedWindow,
kSlidingWindow,
kTokenBucket
};
inline RateLimiterType stringToRateLimiterType(const std::string &type)
{
if (type == "fixedWindow" || type == "fixed_window")
return RateLimiterType::kFixedWindow;
else if (type == "slidingWindow" || type == "sliding_window")
return RateLimiterType::kSlidingWindow;
return RateLimiterType::kTokenBucket;
}
class DROGON_EXPORT RateLimiter;
using RateLimiterPtr = std::shared_ptr<RateLimiter>;
/**
* @brief This class is used to limit the number of requests per second
*
* */
class DROGON_EXPORT RateLimiter
{
public:
/**
* @brief Create a rate limiter
* @param type The type of the rate limiter
* @param capacity The maximum number of requests in the time unit.
* @param timeUnit The time unit of the rate limiter.
* @return A rate limiter pointer
*/
static RateLimiterPtr newRateLimiter(
RateLimiterType type,
size_t capacity,
std::chrono::duration<double> timeUnit = std::chrono::seconds(60));
/**
* @brief Check if a request is allowed
*
* @return true The request is allowed
* @return false The request is not allowed
*/
virtual bool isAllowed() = 0;
virtual ~RateLimiter() noexcept = default;
};
class DROGON_EXPORT SafeRateLimiter : public RateLimiter
{
public:
SafeRateLimiter(RateLimiterPtr limiter) : limiter_(limiter)
{
}
bool isAllowed() override
{
std::lock_guard<std::mutex> lock(mutex_);
return limiter_->isAllowed();
}
~SafeRateLimiter() noexcept override = default;
private:
RateLimiterPtr limiter_;
std::mutex mutex_;
};
} // namespace drogon
+117
View File
@@ -0,0 +1,117 @@
/**
*
* @file RequestStream.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 <string>
#include <functional>
#include <memory>
#include <exception>
namespace drogon
{
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class RequestStreamReader;
using RequestStreamReaderPtr = std::shared_ptr<RequestStreamReader>;
struct MultipartHeader
{
std::string name;
std::string filename;
std::string contentType;
};
class DROGON_EXPORT RequestStream
{
public:
virtual ~RequestStream() = default;
virtual void setStreamReader(RequestStreamReaderPtr reader) = 0;
};
using RequestStreamPtr = std::shared_ptr<RequestStream>;
namespace internal
{
DROGON_EXPORT RequestStreamPtr createRequestStream(const HttpRequestPtr &req);
}
enum class StreamErrorCode
{
kNone = 0,
kBadRequest,
kConnectionBroken
};
class StreamError final : public std::exception
{
public:
const char *what() const noexcept override
{
return message_.data();
}
StreamErrorCode code() const
{
return code_;
}
StreamError(StreamErrorCode code, const std::string &message)
: message_(message), code_(code)
{
}
StreamError(StreamErrorCode code, std::string &&message)
: message_(std::move(message)), code_(code)
{
}
StreamError() = delete;
private:
std::string message_;
StreamErrorCode code_;
};
/**
* An interface for stream request reading.
* User should create an implementation class, or use built-in handlers
*/
class DROGON_EXPORT RequestStreamReader
{
public:
virtual ~RequestStreamReader() = default;
virtual void onStreamData(const char *, size_t) = 0;
virtual void onStreamFinish(std::exception_ptr) = 0;
using StreamDataCallback = std::function<void(const char *, size_t)>;
using StreamFinishCallback = std::function<void(std::exception_ptr)>;
// Create a handler with default implementation
static RequestStreamReaderPtr newReader(StreamDataCallback dataCb,
StreamFinishCallback finishCb);
// A handler that drops all data
static RequestStreamReaderPtr newNullReader();
using MultipartHeaderCallback = std::function<void(MultipartHeader header)>;
static RequestStreamReaderPtr newMultipartReader(
const HttpRequestPtr &req,
MultipartHeaderCallback headerCb,
StreamDataCallback dataCb,
StreamFinishCallback finishCb);
};
} // namespace drogon
+281
View File
@@ -0,0 +1,281 @@
/**
*
* @file Session.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/Logger.h>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <optional>
#include <any>
namespace drogon
{
/**
* @brief This class represents a session stored in the framework.
* One can get or set any type of data to a session object.
*/
class Session
{
public:
using SessionMap = std::map<std::string, std::any>;
/**
* @brief Get the data identified by the key parameter.
* @note if the data is not found, a default value is returned.
* For example:
* @code
auto userName = sessionPtr->get<std::string>("user name");
@endcode
*/
template <typename T>
T get(const std::string &key) const
{
{
std::lock_guard<std::mutex> lck(mutex_);
auto it = sessionMap_.find(key);
if (it != sessionMap_.end())
{
if (typeid(T) == it->second.type())
{
return *(std::any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
}
return T();
}
/**
* @brief Get the data identified by the key parameter and return an
* optional object that wraps the data.
*
* @tparam T
* @param key
* @return optional<T>
*/
template <typename T>
std::optional<T> getOptional(const std::string &key) const
{
{
std::lock_guard<std::mutex> lck(mutex_);
auto it = sessionMap_.find(key);
if (it != sessionMap_.end())
{
if (typeid(T) == it->second.type())
{
return *(std::any_cast<T>(&(it->second)));
}
else
{
LOG_ERROR << "Bad type";
}
}
}
return std::nullopt;
}
/**
* @brief Modify or visit the data identified by the key parameter.
*
* @tparam T the type of the data.
* @param key
* @param handler A callable that can modify or visit the data. The
* signature of the handler should be equivalent to 'void(T&)' or
* 'void(const T&)'
*
* @note This function is multiple-thread safe. if the data identified by
* the key doesn't exist, a new one is created and passed to the handler.
* The changing of the data is protected by the mutex of the session.
*/
template <typename T, typename Callable>
void modify(const std::string &key, Callable &&handler)
{
std::lock_guard<std::mutex> lck(mutex_);
auto it = sessionMap_.find(key);
if (it != sessionMap_.end())
{
if (typeid(T) == it->second.type())
{
handler(*(std::any_cast<T>(&(it->second))));
}
else
{
LOG_ERROR << "Bad type";
}
}
else
{
auto item = T();
handler(item);
sessionMap_.insert(std::make_pair(key, std::any(std::move(item))));
}
}
/**
* @brief Modify or visit the session data.
*
* @tparam Callable: The signature of the callable should be equivalent to
* `void (Session::SessionMap &)` or `void (const Session::SessionMap &)`
* @param handler A callable that can modify the sessionMap_ inside the
* session.
* @note This function is multiple-thread safe.
*/
template <typename Callable>
void modify(Callable &&handler)
{
std::lock_guard<std::mutex> lck(mutex_);
handler(sessionMap_);
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
sessionPtr->insert("user name", userNameString);
@endcode
* @note If the key already exists, the element is not inserted.
*/
void insert(const std::string &key, const std::any &obj)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_.insert(std::make_pair(key, obj));
}
/**
* @brief Insert a key-value pair
* @note here the any object can be created implicitly. for example
* @code
sessionPtr->insert("user name", userNameString);
@endcode
* @note If the key already exists, the element is not inserted.
*/
void insert(const std::string &key, std::any &&obj)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_.insert(std::make_pair(key, std::move(obj)));
}
/**
* @brief Erase the data identified by the given key.
*/
void erase(const std::string &key)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_.erase(key);
}
/**
* @brief Return true if the data identified by the key exists.
*/
bool find(const std::string &key)
{
std::lock_guard<std::mutex> lck(mutex_);
if (sessionMap_.find(key) == sessionMap_.end())
{
return false;
}
return true;
}
/**
* @brief Clear all data in the session.
*/
void clear()
{
std::lock_guard<std::mutex> lck(mutex_);
sessionMap_.clear();
}
/**
* @brief Get the session ID of the current session.
*/
std::string sessionId() const
{
std::lock_guard<std::mutex> lck(mutex_);
return sessionId_;
}
/**
* @brief Let the framework create a new session ID for this session and set
* it to the client.
* @note This method does not change the session ID now.
*/
void changeSessionIdToClient()
{
needToChange_ = true;
needToSet_ = true;
}
Session() = delete;
private:
SessionMap sessionMap_;
mutable std::mutex mutex_;
std::string sessionId_;
bool needToSet_{false};
bool needToChange_{false};
friend class SessionManager;
friend class HttpAppFrameworkImpl;
/**
* @brief Constructor, usually called by the framework
*/
Session(const std::string &id, bool needToSet)
: sessionId_(id), needToSet_(needToSet)
{
}
/**
* @brief Change the state of the session, usually called by the framework
*/
void hasSet()
{
needToSet_ = false;
}
/**
* @brief If the session ID needs to be changed.
*
*/
bool needToChangeSessionId() const
{
return needToChange_;
}
/**
* @brief If the session ID needs to be set to the client through cookie,
* return true
*/
bool needSetToClient() const
{
return needToSet_;
}
void setSessionId(const std::string &id)
{
std::lock_guard<std::mutex> lck(mutex_);
sessionId_ = id;
needToChange_ = false;
}
};
using SessionPtr = std::shared_ptr<Session>;
} // namespace drogon
+85
View File
@@ -0,0 +1,85 @@
/**
*
* UploadFile.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string>
namespace drogon
{
/**
* This class represents an upload file which will be transferred to the server
* via the multipart/form-data format
*/
class UploadFile
{
public:
/// Constructor
/**
* @param filePath The file location on local host, including file name.
* @param fileName The file name provided to the server. If it is empty by
* default, the file name in the @p filePath is provided to the server.
* @param itemName The item name on the browser form.
* @param contentType The Mime content type for the part
*/
explicit UploadFile(const std::string &filePath,
const std::string &fileName = "",
const std::string &itemName = "file",
ContentType contentType = CT_NONE)
: path_(filePath), itemName_(itemName), contentType_(contentType)
{
if (!fileName.empty())
{
fileName_ = fileName;
}
else
{
auto pos = filePath.rfind('/');
if (pos != std::string::npos)
{
fileName_ = filePath.substr(pos + 1);
}
else
{
fileName_ = filePath;
}
}
}
const std::string &path() const
{
return path_;
}
const std::string &fileName() const
{
return fileName_;
}
const std::string &itemName() const
{
return itemName_;
}
ContentType contentType() const
{
return contentType_;
}
private:
std::string path_;
std::string fileName_;
std::string itemName_;
ContentType contentType_;
};
} // namespace drogon
+264
View File
@@ -0,0 +1,264 @@
/**
*
* @file WebSocketClient.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/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/WebSocketConnection.h>
#include <drogon/HttpTypes.h>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
#include <functional>
#include <memory>
#include <string>
#include <trantor/net/EventLoop.h>
namespace drogon
{
class WebSocketClient;
using WebSocketClientPtr = std::shared_ptr<WebSocketClient>;
using WebSocketRequestCallback = std::function<
void(ReqResult, const HttpResponsePtr &, const WebSocketClientPtr &)>;
#ifdef __cpp_impl_coroutine
namespace internal
{
struct [[nodiscard]] WebSocketConnectionAwaiter
: public CallbackAwaiter<HttpResponsePtr>
{
WebSocketConnectionAwaiter(WebSocketClient *client, HttpRequestPtr req)
: client_(client), req_(std::move(req))
{
}
void await_suspend(std::coroutine_handle<> handle);
private:
WebSocketClient *client_;
HttpRequestPtr req_;
};
} // namespace internal
#endif
/**
* @brief WebSocket client abstract class
*
*/
class DROGON_EXPORT WebSocketClient
{
public:
/// Get the WebSocket connection that is typically used to send messages.
virtual WebSocketConnectionPtr getConnection() = 0;
/**
* @brief Set messages handler. When a message is received from the server,
* the callback is called.
*
* @param callback The function to call when a message is received.
*/
virtual void setMessageHandler(
const std::function<void(std::string &&message,
const WebSocketClientPtr &,
const WebSocketMessageType &)> &callback) = 0;
/// Set the connection closing handler. When the connection is established
/// or closed, the @p callback is called with a bool parameter.
/**
* @brief Set the connection closing handler. When the websocket connection
* is closed, the callback is called
*
* @param callback The function to call when the connection is closed.
*/
virtual void setConnectionClosedHandler(
const std::function<void(const WebSocketClientPtr &)> &callback) = 0;
/// Connect to the server.
virtual void connectToServer(const HttpRequestPtr &request,
const WebSocketRequestCallback &callback) = 0;
/**
* @brief Set the client certificate used by the HTTP connection
*
* @param cert Path to the certificate
* @param key Path to the certificate's private key
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
*/
virtual void setCertPath(const std::string &cert,
const std::string &key) = 0;
/**
* @brief Supplies command style options for `SSL_CONF_cmd`
*
* @param sslConfCmds options for SSL_CONF_cmd
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
* @code
addSSLConfigs({{"-dhparam", "/path/to/dhparam"}, {"-strict", ""}});
* @endcode
*/
virtual void addSSLConfigs(
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0;
#ifdef __cpp_impl_coroutine
/**
* @brief Set messages handler. When a message is received from the server,
* the callback is called.
*
* @param callback The function to call when a message is received.
*/
void setAsyncMessageHandler(
const std::function<Task<>(std::string &&message,
const WebSocketClientPtr &,
const WebSocketMessageType &)> &callback)
{
setMessageHandler([callback](std::string &&message,
const WebSocketClientPtr &client,
const WebSocketMessageType &type) -> void {
[callback](std::string &&message,
const WebSocketClientPtr client,
const WebSocketMessageType type) -> AsyncTask {
co_await callback(std::move(message), client, type);
}(std::move(message), client, type);
});
}
/// Set the connection closing handler. When the connection is established
/// or closed, the @param callback is called with a bool parameter.
/**
* @brief Set the connection closing handler. When the websocket connection
* is closed, the callback is called
*
* @param callback The function to call when the connection is closed.
*/
void setAsyncConnectionClosedHandler(
const std::function<Task<>(const WebSocketClientPtr &)> &callback)
{
setConnectionClosedHandler(
[callback](const WebSocketClientPtr &client) {
[=]() -> AsyncTask { co_await callback(client); }();
});
}
/// Connect to the server.
internal::WebSocketConnectionAwaiter connectToServerCoro(
const HttpRequestPtr &request)
{
return internal::WebSocketConnectionAwaiter(this, request);
}
#endif
/// Get the event loop of the client;
virtual trantor::EventLoop *getLoop() = 0;
/// Stop trying to connect to the server or close the connection.
virtual void stop() = 0;
/**
* @brief Create a websocket client using the given ip and port to connect
* to server.
*
* @param ip The ip address of the server.
* @param port The port of the server.
* @param useSSL If useSSL is set to true, the client connects to the server
* using SSL.
* @param loop If the loop parameter is set to nullptr, the client uses the
* HttpAppFramework's event loop, otherwise it runs in the loop identified
* by the parameter.
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* enabled for HTTPS.
* @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking.
* @return HttpClientPtr The smart pointer to the new client object.
* @return WebSocketClientPtr The smart pointer to the WebSocket client.
* @note The ip parameter support for both ipv4 and ipv6 address
*/
static WebSocketClientPtr newWebSocketClient(
const std::string &ip,
uint16_t port,
bool useSSL = false,
trantor::EventLoop *loop = nullptr,
bool useOldTLS = false,
bool validateCert = true);
/// Create a websocket client using the given hostString to connect to
/// server
/**
* @param hostString must be prefixed by 'ws://' or 'wss://'
* Examples for hostString:
* @code
wss://www.google.com
ws://www.google.com
wss://127.0.0.1:8080/
ws://127.0.0.1
@endcode
* @param loop if the parameter is set to nullptr, the client uses the
* HttpAppFramework's main event loop, otherwise it runs in the loop
* identified by the parameter.
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* enabled for HTTPS.
* @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking.
* @note
* Don't add path and parameters in hostString, the request path and
* parameters should be set in HttpRequestPtr when calling the
* connectToServer() method.
*
*/
static WebSocketClientPtr newWebSocketClient(
const std::string &hostString,
trantor::EventLoop *loop = nullptr,
bool useOldTLS = false,
bool validateCert = true);
virtual ~WebSocketClient() = default;
};
#ifdef __cpp_impl_coroutine
inline void internal::WebSocketConnectionAwaiter::await_suspend(
std::coroutine_handle<> handle)
{
client_->connectToServer(req_,
[this, handle](ReqResult result,
const HttpResponsePtr &resp,
const WebSocketClientPtr &) {
if (result == ReqResult::Ok)
setValue(resp);
else
{
std::string reason;
if (result == ReqResult::BadResponse)
reason = "BadResponse";
else if (result ==
ReqResult::NetworkFailure)
reason = "NetworkFailure";
else if (result ==
ReqResult::BadServerAddress)
reason = "BadServerAddress";
else if (result == ReqResult::Timeout)
reason = "Timeout";
setException(std::make_exception_ptr(
std::runtime_error(reason)));
}
handle.resume();
});
}
#endif
} // namespace drogon
@@ -0,0 +1,236 @@
/**
*
* @file WebSocketConnection.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 <json/value.h>
#include <memory>
#include <string>
#include <drogon/HttpTypes.h>
#include <string_view>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/NonCopyable.h>
namespace drogon
{
enum class CloseCode
{
/*1000 indicates a normal closure, meaning that the purpose for which the
connection was established has been fulfilled.*/
kNormalClosure = 1000,
/*1001 indicates that an endpoint is "going away", such as a server going
down or a browser having navigated away from a page.*/
kEndpointGone = 1001,
/*1002 indicates that an endpoint is terminating the connection due to a
protocol error.*/
kProtocolError = 1002,
/*1003 indicates that an endpoint is terminating the connection because it
has received a type of data it cannot accept (e.g., an endpoint that
understands only text data MAY send this if it receives a binary
message).*/
kInvalidMessage = 1003,
/*1005 is a reserved value and MUST NOT be set as a status code in a Close
control frame by an endpoint. It is designated for use in applications
expecting a status code to indicate that no status code was actually
present.*/
kNone = 1005,
/*1006 is a reserved value and MUST NOT be set as a status code in a Close
control frame by an endpoint. It is designated for use in applications
expecting a status code to indicate that the connection was closed
abnormally, e.g., without sending or receiving a Close control frame.
*/
kAbnormally = 1006,
/*1007 indicates that an endpoint is terminating the connection because it
has received data within a message that was not consistent with the type
of the message (e.g., non-UTF-8 [RFC3629] data within a text message).*/
kWrongMessageContent = 1007,
/*1008 indicates that an endpoint is terminating the connection because it
has received a message that violates its policy. This is a generic
status code that can be returned when there is no other more suitable
status code (e.g., 1003 or 1009) or if there is a need to hide specific
details about the policy.
*/
kViolation = 1008,
/*1009 indicates that an endpoint is terminating the connection because it
has received a message that is too big for it to process.*/
kMessageTooBig = 1009,
/*1010 indicates that an endpoint (client) is terminating the connection
because it has expected the server to negotiate one or more extension,
but the server didn't return them in the response message of the
WebSocket handshake. The list of extensions that are needed SHOULD
appear in the /reason/ part of the Close frame. Note that this status
code is not used by the server, because it can fail the WebSocket
handshake instead.*/
kNeedMoreExtensions = 1010,
/*1011 indicates that a server is terminating the connection because it
encountered an unexpected condition that prevented it from fulfilling the
request.*/
kUnexpectedCondition = 1011,
/*1015 is a reserved value and MUST NOT be set as a status code in a Close
control frame by an endpoint. It is designated for use in applications
expecting a status code to indicate that the connection was closed due to
a failure to perform a TLS handshake (e.g., the server certificate can't
be verified).*/
kTLSFailed = 1015
};
/**
* @brief The WebSocket connection abstract class.
*
*/
class WebSocketConnection
{
public:
WebSocketConnection() = default;
virtual ~WebSocketConnection(){};
/**
* @brief Send a message to the peer
*
* @param msg The message to be sent.
* @param len The message length.
* @param type The message type.
*/
virtual void send(
const char *msg,
uint64_t len,
const WebSocketMessageType type = WebSocketMessageType::Text) = 0;
/**
* @brief Send a message to the peer
*
* @param msg The message to be sent.
* @param type The message type.
*/
virtual void send(
std::string_view msg,
const WebSocketMessageType type = WebSocketMessageType::Text) = 0;
/**
* @brief Send a message to the peer
*
* @param json The JSON message to be sent.
* @param type The message type.
*/
virtual void sendJson(
const Json::Value &json,
const WebSocketMessageType type = WebSocketMessageType::Text) = 0;
/// Return the local IP address and port number of the connection
virtual const trantor::InetAddress &localAddr() const = 0;
/// Return the remote IP address and port number of the connection
virtual const trantor::InetAddress &peerAddr() const = 0;
/// Return true if the connection is open
virtual bool connected() const = 0;
/// Return true if the connection is closed
virtual bool disconnected() const = 0;
/**
* @brief Shut down the write direction, which means that further send
* operations are disabled.
*
* @param code Please refer to the enum class CloseCode. (RFC6455 7.4.1)
* @param reason The reason for closing the connection.
*/
virtual void shutdown(const CloseCode code = CloseCode::kNormalClosure,
const std::string &reason = "") = 0;
/// Close the connection
virtual void forceClose() = 0;
/**
* @brief Set custom data on the connection
*
* @param context The custom data.
*/
void setContext(const std::shared_ptr<void> &context)
{
contextPtr_ = context;
}
/**
* @brief Set custom data on the connection
*
* @param context The custom data.
*/
void setContext(std::shared_ptr<void> &&context)
{
contextPtr_ = std::move(context);
}
/**
* @brief Get custom data from the connection
*
* @tparam T The type of the data
* @return std::shared_ptr<T> The smart pointer to the data object.
*/
template <typename T>
std::shared_ptr<T> getContext() const
{
return std::static_pointer_cast<T>(contextPtr_);
}
/**
* @brief Get the custom data reference from the connection.
* @note Please make sure that the context is available.
* @tparam T The type of the data stored in the context.
* @return T&
*/
template <typename T>
T &getContextRef() const
{
return *(static_cast<T *>(contextPtr_.get()));
}
/// Return true if the context is set by user.
bool hasContext()
{
return (bool)contextPtr_;
}
/// Clear the context.
void clearContext()
{
contextPtr_.reset();
}
/**
* @brief Set the heartbeat(ping) message sent to the peer.
*
* @param message The ping message.
* @param interval The sending interval.
* @note
* Both the server and the client in Drogon automatically send the pong
* message after receiving the ping message.
* An empty ping message is sent every 30 seconds by default. The method
* overrides the default behavior.
*/
virtual void setPingMessage(
const std::string &message,
const std::chrono::duration<double> &interval) = 0;
/**
* @brief Disable sending ping messages to the peer.
*/
virtual void disablePing() = 0;
private:
std::shared_ptr<void> contextPtr_;
};
using WebSocketConnectionPtr = std::shared_ptr<WebSocketConnection>;
} // namespace drogon
@@ -0,0 +1,138 @@
/**
*
* WebSocketController.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/DrObject.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/WebSocketConnection.h>
#include <drogon/HttpTypes.h>
#include <trantor/utils/Logger.h>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#define WS_PATH_LIST_BEGIN \
static void initPathRouting() \
{
#define WS_PATH_ADD(path, ...) registerSelf__(path, {__VA_ARGS__})
#define WS_ADD_PATH_VIA_REGEX(regExp, ...) \
registerSelfRegex__(regExp, {__VA_ARGS__})
#define WS_PATH_LIST_END }
namespace drogon
{
/**
* @brief The abstract base class for WebSocket controllers.
*
*/
class WebSocketControllerBase : public virtual DrObjectBase
{
public:
// This function is called when a new message is received
virtual void handleNewMessage(const WebSocketConnectionPtr &,
std::string &&,
const WebSocketMessageType &) = 0;
// This function is called after a new connection of WebSocket is
// established.
virtual void handleNewConnection(const HttpRequestPtr &,
const WebSocketConnectionPtr &) = 0;
// This function is called after a WebSocket connection is closed
virtual void handleConnectionClosed(const WebSocketConnectionPtr &) = 0;
virtual ~WebSocketControllerBase()
{
}
};
using WebSocketControllerBasePtr = std::shared_ptr<WebSocketControllerBase>;
/**
* @brief The reflection base class template for WebSocket controllers
*
* @tparam T the type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have nondefault constructors.
*/
template <typename T, bool AutoCreation = true>
class WebSocketController : public DrObject<T>, public WebSocketControllerBase
{
public:
static const bool isAutoCreation = AutoCreation;
virtual ~WebSocketController()
{
}
protected:
WebSocketController()
{
}
static void registerSelf__(
const std::string &path,
const std::vector<internal::HttpConstraint> &constraints)
{
LOG_TRACE << "register websocket controller("
<< WebSocketController<T, AutoCreation>::classTypeName()
<< ") on path:" << path;
app().registerWebSocketController(
path,
WebSocketController<T, AutoCreation>::classTypeName(),
constraints);
}
static void registerSelfRegex__(
const std::string &regExp,
const std::vector<internal::HttpConstraint> &constraints)
{
LOG_TRACE << "register websocket controller("
<< WebSocketController<T, AutoCreation>::classTypeName()
<< ") on regExp:" << regExp;
app().registerWebSocketControllerRegex(
regExp,
WebSocketController<T, AutoCreation>::classTypeName(),
constraints);
}
private:
class pathRegistrator
{
public:
pathRegistrator()
{
if (AutoCreation)
{
T::initPathRouting();
}
}
};
friend pathRegistrator;
static pathRegistrator registrator_;
virtual void *touch()
{
return &registrator_;
}
};
template <typename T, bool AutoCreation>
typename WebSocketController<T, AutoCreation>::pathRegistrator
WebSocketController<T, AutoCreation>::registrator_;
} // namespace drogon
+50
View File
@@ -0,0 +1,50 @@
/**
*
* drogon.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/EventLoop.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <drogon/CacheMap.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpClient.h>
#include <drogon/HttpController.h>
#include <drogon/HttpSimpleController.h>
#include <drogon/utils/Utilities.h>
#include <drogon/MultiPart.h>
#include <drogon/plugins/Plugin.h>
#include <drogon/plugins/SecureSSLRedirector.h>
#include <drogon/plugins/AccessLogger.h>
#include <drogon/plugins/RealIpResolver.h>
#include <drogon/plugins/Hodor.h>
#include <drogon/plugins/SlashRemover.h>
#include <drogon/plugins/GlobalFilters.h>
#include <drogon/plugins/PromExporter.h>
#include <drogon/IntranetIpFilter.h>
#include <drogon/LocalHostFilter.h>
#include <drogon/Cookie.h>
#include <drogon/Session.h>
#include <drogon/IOThreadStorage.h>
#include <drogon/UploadFile.h>
#include <drogon/orm/DbClient.h>
/**
* @mainpage
* ### Overview
* Drogon is a C++14/17-based HTTP application framework. Drogon can be used to
* easily build various types of web application server programs using C++.
*/
@@ -0,0 +1,39 @@
/**
*
* drogon_callbacks.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 <functional>
#include <memory>
namespace drogon
{
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
using AdviceCallback = std::function<void(const HttpResponsePtr &)>;
using AdviceChainCallback = std::function<void()>;
using AdviceStartSessionCallback = std::function<void(const std::string &)>;
using AdviceDestroySessionCallback = std::function<void(const std::string &)>;
using FilterCallback = std::function<void(const HttpResponsePtr &)>;
using FilterChainCallback = std::function<void()>;
using HttpReqCallback = std::function<void(ReqResult, const HttpResponsePtr &)>;
using MiddlewareCallback = std::function<void(const HttpResponsePtr &)>;
using MiddlewareNextCallback =
std::function<void(std::function<void(const HttpResponsePtr &)> &&)>;
} // namespace drogon
+739
View File
@@ -0,0 +1,739 @@
#pragma once
#include <trantor/utils/NonCopyable.h>
#include <drogon/DrObject.h>
#include <drogon/exports.h>
#include <memory>
#include <mutex>
#include <sstream>
#include <atomic>
#include <string_view>
#include <cstddef>
/**
* @brief Drogon Test is a minimal effort test framework developed because the
* major C++ test frameworks doesn't handle async programs well. Drogon Test's
* syntax is inspired by both Google Test and Catch2
*/
namespace drogon
{
namespace test
{
#define TEST_CTX drogon_test_ctx_
#define DROGON_TESTCASE_PREIX_ drtest__
#define DROGON_TESTCASE_PREIX_STR_ "drtest__"
#define TEST_FLAG_ drgood__
#define DROGON_TEST_STRINGIFY__(x) #x
#define DROGON_TEST_STRINGIFY(x) DROGON_TEST_STRINGIFY__(x)
#define DROGON_TEST_CONCAT__(a, b) a##b
#define DROGON_TEST_CONCAT(a, b) DROGON_TEST_CONCAT__(a, b)
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC)
#define DROGON_TEST_START_SUPRESSION_ _Pragma("GCC diagnostic push")
#define DROGON_TEST_END_SUPRESSION_ _Pragma("GCC diagnostic pop")
#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
_Pragma("GCC diagnostic ignored \"-Wparentheses\"")
#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
_Pragma("GCC diagnostic ignored \"-Wunused-value\"")
#elif defined(__clang__) && !defined(_MSC_VER)
#define DROGON_TEST_START_SUPRESSION_ _Pragma("clang diagnostic push")
#define DROGON_TEST_END_SUPRESSION_ _Pragma("clang diagnostic pop")
#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
_Pragma("clang diagnostic ignored \"-Wparentheses\"")
#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
_Pragma("clang diagnostic ignored \"-Wunused-value\"")
// MSVC don't have an equlivent. Add other compilers here
#else
#define DROGON_TEST_START_SUPRESSION_
#define DROGON_TEST_END_SUPRESSION_
#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_
#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_
#endif
class Case;
namespace internal
{
DROGON_EXPORT extern std::atomic<size_t> numAssertions;
DROGON_EXPORT extern std::atomic<size_t> numCorrectAssertions;
DROGON_EXPORT extern std::atomic<size_t> numFailedTestCases;
DROGON_EXPORT extern bool printSuccessfulTests;
DROGON_EXPORT void registerCase(Case *test);
DROGON_EXPORT void unregisterCase(Case *test);
template <typename _Tp, typename dummy = void>
struct is_printable : std::false_type
{
};
template <typename _Tp>
struct is_printable<
_Tp,
std::enable_if_t<std::is_same_v<decltype(std::cout << std::declval<_Tp>()),
std::ostream &>>> : std::true_type
{
};
inline std::string escapeString(const std::string_view sv)
{
std::string result;
result.reserve(sv.size());
for (auto ch : sv)
{
if (ch == '\n')
result += "\\n";
else if (ch == '\r')
result += "\\r";
else if (ch == '\t')
result += "\\t";
else if (ch == '\b')
result += "\\b";
else if (ch == '\\')
result += "\\\\";
else if (ch == '"')
result += "\"";
else if (ch == '\v')
result += "\\v";
else if (ch == '\a')
result += "\\a";
else
result.push_back(ch);
}
return result;
}
DROGON_EXPORT std::string prettifyString(const std::string_view sv,
size_t maxLength = 120);
template <typename... Args>
inline void outputReason(Args &&...args)
{
(std::cout << ... << std::forward<Args>(args));
}
template <typename T>
inline std::string attemptPrint(T &&v)
{
using Type = std::remove_cv_t<std::remove_reference_t<T>>;
if constexpr (std::is_same_v<Type, std::nullptr_t>)
return "nullptr";
else if constexpr (std::is_same_v<Type, char>)
return "'" + std::string(1, v) + "'";
else if constexpr (std::is_convertible_v<Type, std::string_view>)
return prettifyString(v);
else if constexpr (internal::is_printable<Type>::value)
{
std::stringstream ss;
ss << v;
return ss.str();
}
return "{un-printable}";
}
inline std::string stringifyFuncCall(const std::string &funcName)
{
return funcName + "()";
}
inline std::string stringifyFuncCall(const std::string &funcName,
const std::string &param1)
{
return funcName + "(" + param1 + ")";
}
inline std::string stringifyFuncCall(const std::string &funcName,
const std::string &param1,
const std::string &param2)
{
return funcName + "(" + param1 + ", " + param2 + ")";
}
struct ComparsionResult
{
std::pair<bool, std::string> result() const
{
return {comparsionResilt_, expansion_};
}
bool comparsionResilt_;
std::string expansion_;
};
template <typename T>
struct Lhs
{
template <typename _ = void> // HACK: prevent this function to be evaluated
// when not invoked
std::pair<bool, std::string> result() const
{
return {(bool)ref_, attemptPrint(ref_)};
}
Lhs(const T &lhs) : ref_(lhs)
{
}
const T &ref_;
template <typename RhsType>
ComparsionResult operator<(const RhsType &rhs)
{
return ComparsionResult{ref_ < rhs,
attemptPrint(ref_) + " < " +
attemptPrint(ref_)};
}
template <typename RhsType>
ComparsionResult operator>(const RhsType &rhs)
{
return ComparsionResult{ref_ > rhs,
attemptPrint(ref_) + " > " + attemptPrint(rhs)};
}
template <typename RhsType>
ComparsionResult operator<=(const RhsType &rhs)
{
return ComparsionResult{ref_ <= rhs,
attemptPrint(ref_) +
" <= " + attemptPrint(rhs)};
}
template <typename RhsType>
ComparsionResult operator>=(const RhsType &rhs)
{
return ComparsionResult{ref_ >= rhs,
attemptPrint(ref_) +
" >= " + attemptPrint(rhs)};
}
template <typename RhsType>
ComparsionResult operator==(const RhsType &rhs)
{
return ComparsionResult{ref_ == rhs,
attemptPrint(ref_) +
" == " + attemptPrint(rhs)};
}
template <typename RhsType>
ComparsionResult operator!=(const RhsType &rhs)
{
return ComparsionResult{ref_ != rhs,
attemptPrint(ref_) +
" != " + attemptPrint(rhs)};
}
template <typename RhsType>
ComparsionResult operator&&(const RhsType &rhs)
{
static_assert(!std::is_same_v<RhsType, void>,
" && is not supported in expression decomposition");
return {};
}
template <typename RhsType>
ComparsionResult operator||(const RhsType &rhs)
{
static_assert(!std::is_same_v<RhsType, void>,
" || is not supported in expression decomposition");
return {};
}
template <typename RhsType>
ComparsionResult operator|(const RhsType &rhs)
{
static_assert(!std::is_same_v<RhsType, void>,
" | is not supported in expression decomposition");
return {};
}
template <typename RhsType>
ComparsionResult operator&(const RhsType &rhs)
{
static_assert(!std::is_same_v<RhsType, void>,
" & is not supported in expression decomposition");
return {};
}
};
struct Decomposer
{
template <typename T>
Lhs<T> operator<=(const T &other)
{
return Lhs<T>(other);
}
};
} // namespace internal
class DROGON_EXPORT ThreadSafeStream final
{
public:
ThreadSafeStream(std::ostream &os) : os_(os)
{
mtx_.lock();
}
~ThreadSafeStream()
{
mtx_.unlock();
}
template <typename T>
std::ostream &operator<<(const T &rhs)
{
return os_ << rhs;
}
static std::mutex mtx_;
std::ostream &os_;
};
DROGON_EXPORT ThreadSafeStream print();
DROGON_EXPORT ThreadSafeStream printErr();
class CaseBase : public trantor::NonCopyable
{
public:
CaseBase() = default;
CaseBase(const std::string &name) : name_(name)
{
}
CaseBase(std::shared_ptr<CaseBase> parent, const std::string &name)
: parent_(parent), name_(name)
{
}
virtual ~CaseBase() = default;
std::string fullname() const
{
std::string result;
auto curr = this;
while (curr != nullptr)
{
result = curr->name() + result;
if (curr->parent_ != nullptr)
result = "." + result;
curr = curr->parent_.get();
}
return result;
}
const std::string &name() const
{
return name_;
}
void setFailed()
{
if (failed_ == false)
{
internal::numFailedTestCases++;
failed_ = true;
}
}
bool failed() const
{
return failed_;
}
protected:
bool failed_ = false;
std::shared_ptr<CaseBase> parent_ = nullptr;
std::string name_;
};
class Case : public CaseBase
{
public:
Case(const std::string &name) : CaseBase(name)
{
internal::registerCase(this);
}
Case(std::shared_ptr<Case> parent, const std::string &name)
: CaseBase(parent, name)
{
internal::registerCase(this);
}
virtual ~Case()
{
internal::unregisterCase(this);
}
};
struct TestCase : public CaseBase
{
TestCase(const std::string &name) : CaseBase(name)
{
}
virtual ~TestCase() = default;
virtual void doTest_(std::shared_ptr<Case>) = 0;
};
DROGON_EXPORT void printTestStats();
DROGON_EXPORT int run(int argc, char **argv);
} // namespace test
} // namespace drogon
#define ERROR_MSG(func_name, expr) \
drogon::test::printErr() \
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
<< " \x1B[0;31m FAILED:\x1B[0m\n" \
<< " \033[0;34m" \
<< drogon::test::internal::stringifyFuncCall(func_name, expr) \
<< "\x1B[0m\n"
#define PASSED_MSG(func_name, expr) \
drogon::test::print() \
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
<< " \x1B[0;32m PASSED:\x1B[0m\n" \
<< " \033[0;34m" \
<< drogon::test::internal::stringifyFuncCall(func_name, expr) \
<< "\x1B[0m\n"
#define SET_TEST_SUCCESS__ \
do \
{ \
TEST_FLAG_ = true; \
} while (0);
#define TEST_INTERNAL__(func_name, \
expr, \
eval, \
on_exception, \
on_non_standard_exception, \
on_leaving) \
do \
{ \
bool TEST_FLAG_ = false; \
using drogon::test::internal::stringifyFuncCall; \
using drogon::test::printErr; \
drogon::test::internal::numAssertions++; \
try \
{ \
eval; \
} \
catch (const std::exception &e) \
{ \
(void)e; \
on_exception; \
} \
catch (...) \
{ \
on_non_standard_exception; \
} \
if (TEST_FLAG_) \
drogon::test::internal::numCorrectAssertions++; \
else \
TEST_CTX->setFailed(); \
on_leaving; \
} while (0);
#define EVAL_AND_CHECK_TRUE__(func_name, expr) \
do \
{ \
bool drresult__; \
std::string drexpansion__; \
DROGON_TEST_START_SUPRESSION_ \
DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
std::tie(drresult__, drexpansion__) = \
(drogon::test::internal::Decomposer() <= expr).result(); \
DROGON_TEST_END_SUPRESSION_ \
if (!drresult__) \
{ \
ERROR_MSG(func_name, #expr) \
<< "With expansion\n" \
<< " \033[0;33m" << drexpansion__ << "\x1B[0m\n\n"; \
} \
else \
SET_TEST_SUCCESS__; \
} while (0);
#define PRINT_UNEXPECTED_EXCEPTION__(func_name, expr) \
do \
{ \
ERROR_MSG(func_name, expr) \
<< "An unexpected exception is thrown. what():\n" \
<< " \033[0;33m" << e.what() << "\x1B[0m\n\n"; \
} while (0);
#define PRINT_PASSED__(func_name, expr) \
do \
{ \
if (drogon::test::internal::printSuccessfulTests && TEST_FLAG_) \
{ \
PASSED_MSG(func_name, expr) << "\n"; \
} \
} while (0);
#define RETURN_ON_FAILURE__ \
do \
{ \
if (!TEST_FLAG_) \
return; \
} while (0);
#define CO_RETURN_ON_FAILURE__ \
do \
{ \
if (!TEST_FLAG_) \
co_return; \
} while (0);
#define DIE_ON_FAILURE__ \
do \
{ \
using namespace drogon::test; \
if (!TEST_FLAG_) \
{ \
printTestStats(); \
printErr() << "Force exiting due to a mandation failed.\n"; \
exit(1); \
} \
} while (0);
#define PRINT_NONSTANDARD_EXCEPTION__(func_name, expr) \
do \
{ \
ERROR_MSG(func_name, expr) \
<< "Unexpected unknown exception is thrown.\n\n"; \
} while (0);
#define EVAL__(expr) \
do \
{ \
expr; \
} while (0);
#define NOTHING__ \
{ \
}
#define PRINT_ERR_NOEXCEPTION__(expr, func_name) \
do \
{ \
if (!TEST_FLAG_) \
ERROR_MSG(func_name, expr) \
<< "With expecitation\n" \
<< " Expected to throw an exception. But none are " \
"thrown.\n\n"; \
} while (0);
#define PRINT_ERR_WITHEXCEPTION__(expr, func_name) \
do \
{ \
if (!TEST_FLAG_) \
ERROR_MSG(func_name, expr) \
<< "With expecitation\n" \
<< " Should to not throw an exception. But one is " \
"thrown.\n\n"; \
} while (0);
#define PRINT_ERR_BAD_EXCEPTION__( \
expr, func_name, excep_type, exceptionThrown, correctExceptionType) \
do \
{ \
assert((exceptionThrown && correctExceptionType) || !exceptionThrown); \
if (exceptionThrown == true && correctExceptionType == false) \
{ \
ERROR_MSG(func_name, expr) \
<< "With expecitation\n" \
<< " Exception have been throw but not of type \033[0;33m" \
<< #excep_type << "\033[0m.\n\n"; \
} \
else if (exceptionThrown == false) \
{ \
ERROR_MSG(func_name, expr) \
<< "With expecitation\n" \
<< " A \033[0;33m" << #excep_type \
<< "\033[0m exception is expected. But nothing was thrown" \
<< "\033[0m.\n\n"; \
} \
} while (0);
#define CHECK_INTERNAL__(expr, func_name, on_leave) \
do \
{ \
TEST_INTERNAL__(func_name, \
expr, \
EVAL_AND_CHECK_TRUE__(func_name, expr), \
PRINT_UNEXPECTED_EXCEPTION__(func_name, #expr), \
PRINT_NONSTANDARD_EXCEPTION__(func_name, #expr), \
on_leave PRINT_PASSED__(func_name, #expr)); \
} while (0)
#define CHECK_THROWS_INTERNAL__(expr, func_name, on_leave) \
do \
{ \
TEST_INTERNAL__(func_name, \
expr, \
EVAL__(expr), \
SET_TEST_SUCCESS__, \
SET_TEST_SUCCESS__, \
PRINT_ERR_NOEXCEPTION__(#expr, func_name) \
on_leave PRINT_PASSED__(func_name, #expr)); \
} while (0)
#define CHECK_THROWS_AS_INTERNAL__(expr, func_name, except_type, on_leave) \
do \
{ \
bool exceptionThrown = false; \
TEST_INTERNAL__( \
func_name, \
expr, \
EVAL__(expr), \
{ \
exceptionThrown = true; \
if (dynamic_cast<const except_type *>(&e) != nullptr) \
SET_TEST_SUCCESS__; \
}, \
{ exceptionThrown = true; }, \
PRINT_ERR_BAD_EXCEPTION__(#expr ", " #except_type, \
func_name, \
except_type, \
exceptionThrown, \
TEST_FLAG_) \
on_leave PRINT_PASSED__(func_name, #expr ", " #except_type)); \
} while (0)
#define CHECK_NOTHROW_INTERNAL__(expr, func_name, on_leave) \
do \
{ \
TEST_INTERNAL__(func_name, \
expr, \
EVAL__(expr) SET_TEST_SUCCESS__, \
NOTHING__, \
NOTHING__, \
PRINT_ERR_WITHEXCEPTION__(#expr, func_name) \
on_leave PRINT_PASSED__(func_name, #expr)); \
} while (0)
#define CHECK(expr) CHECK_INTERNAL__(expr, "CHECK", NOTHING__)
#define CHECK_THROWS(expr) \
CHECK_THROWS_INTERNAL__(expr, "CHECK_THROWS", NOTHING__)
#define CHECK_NOTHROW(expr) \
CHECK_NOTHROW_INTERNAL__(expr, "CHECK_NOTHROW", NOTHING__)
#define CHECK_THROWS_AS(expr, except_type) \
CHECK_THROWS_AS_INTERNAL__(expr, "CHECK_THROWS_AS", except_type, NOTHING__)
#define REQUIRE(expr) CHECK_INTERNAL__(expr, "REQUIRE", RETURN_ON_FAILURE__)
#define REQUIRE_THROWS(expr) \
CHECK_THROWS_INTERNAL__(expr, "REQUIRE_THROWS", RETURN_ON_FAILURE__)
#define REQUIRE_NOTHROW(expr) \
CHECK_NOTHROW_INTERNAL__(expr, "REQUIRE_NOTHROW", RETURN_ON_FAILURE__)
#define REQUIRE_THROWS_AS(expr, except_type) \
CHECK_THROWS_AS_INTERNAL__(expr, \
"REQUIRE_THROWS_AS", \
except_type, \
RETURN_ON_FAILURE__)
#define CO_REQUIRE(expr) \
CHECK_INTERNAL__(expr, "CO_REQUIRE", CO_RETURN_ON_FAILURE__)
#define CO_REQUIRE_THROWS(expr) \
CHECK_THROWS_INTERNAL__(expr, "CO_REQUIRE_THROWS", CO_RETURN_ON_FAILURE__)
#define CO_REQUIRE_NOTHROW(expr) \
CHECK_NOTHROW_INTERNAL__(expr, "CO_REQUIRE_NOTHROW", CO_RETURN_ON_FAILURE__)
#define CO_REQUIRE_THROWS_AS(expr, except_type) \
CHECK_THROWS_AS_INTERNAL__(expr, \
"CO_REQUIRE_THROWS_AS", \
except_type, \
CO_RETURN_ON_FAILURE__)
#define MANDATE(expr) CHECK_INTERNAL__(expr, "MANDATE", DIE_ON_FAILURE__)
#define MANDATE_THROWS(expr) \
CHECK_THROWS_INTERNAL__(expr, "MANDATE_THROWS", DIE_ON_FAILURE__)
#define MANDATE_NOTHROW(expr) \
CHECK_NOTHROW_INTERNAL__(expr, "MANDATE_NOTHROW", DIE_ON_FAILURE__)
#define MANDATE_THROWS_AS(expr, except_type) \
CHECK_THROWS_AS_INTERNAL__(expr, \
"MANDATE_THROWS_AS", \
except_type, \
DIE_ON_FAILURE__)
#define STATIC_REQUIRE(expr) \
do \
{ \
DROGON_TEST_START_SUPRESSION_ \
DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
TEST_CTX; \
DROGON_TEST_END_SUPRESSION_ \
drogon::test::internal::numAssertions++; \
static_assert((expr), #expr " failed."); \
drogon::test::internal::numCorrectAssertions++; \
} while (0)
#define FAIL(...) \
do \
{ \
using namespace drogon::test; \
TEST_CTX->setFailed(); \
printErr() << "\x1B[1;37mIn test case " << TEST_CTX->fullname() \
<< "\n" \
<< "\x1B[0;37m" << __FILE__ << ":" << __LINE__ \
<< " \x1B[0;31m FAILED:\x1B[0m\n" \
<< " Reason: "; \
drogon::test::internal::outputReason(__VA_ARGS__); \
printErr() << "\n\n"; \
drogon::test::internal::numAssertions++; \
} while (0)
#define FAULT(...) \
do \
{ \
using namespace drogon::test; \
FAIL(__VA_ARGS__); \
printTestStats(); \
printErr() << "Force exiting due to a FAULT statement.\n"; \
exit(1); \
} while (0)
#define SUCCESS() \
do \
{ \
DROGON_TEST_START_SUPRESSION_ \
DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
TEST_CTX; \
DROGON_TEST_END_SUPRESSION_ \
if (drogon::test::internal::printSuccessfulTests) \
drogon::test::print() \
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
<< " \x1B[0;32m PASSED:\x1B[0m\n" \
<< " \033[0;34mSUCCESS()\x1B[0m\n\n"; \
drogon::test::internal::numAssertions++; \
drogon::test::internal::numCorrectAssertions++; \
} while (0)
#define DROGON_TEST_CLASS_NAME_(test_name) \
DROGON_TEST_CONCAT(DROGON_TESTCASE_PREIX_, test_name)
#define DROGON_TEST(test_name) \
struct DROGON_TEST_CLASS_NAME_(test_name) \
: public drogon::DrObject<DROGON_TEST_CLASS_NAME_(test_name)>, \
public drogon::test::TestCase \
{ \
DROGON_TEST_CLASS_NAME_(test_name) \
() : drogon::test::TestCase(#test_name) \
{ \
} \
inline void doTest_(std::shared_ptr<drogon::test::Case>) override; \
}; \
void DROGON_TEST_CLASS_NAME_(test_name)::doTest_( \
std::shared_ptr<drogon::test::Case> TEST_CTX)
#define SUBTEST(name) (std::make_shared<drogon::test::Case>(TEST_CTX, name))
#define SUBSECTION(name) \
for (std::shared_ptr<drogon::test::Case> ctx_hold__ = TEST_CTX, \
ctx_tmp__ = SUBTEST(#name); \
ctx_tmp__ != nullptr; \
TEST_CTX = ctx_hold__, ctx_tmp__ = nullptr) \
if (TEST_CTX = ctx_tmp__, TEST_CTX != nullptr)
@@ -0,0 +1,220 @@
/**
*
* AccessLogger.h
*
*/
#pragma once
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/plugins/Plugin.h>
#include <trantor/utils/AsyncFileLogger.h>
#include <vector>
namespace drogon
{
namespace plugin
{
/**
* @brief This plugin is used to print all requests to the log.
*
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::AccessLogger",
"dependencies": [],
"config": {
"use_spdlog": false,
"log_path": "./",
"log_format": "",
"log_file": "access.log",
"log_size_limit": 0,
"use_local_time": true,
"log_index": 0,
// "show_microseconds": true,
// "custom_time_format": "",
// "use_real_ip": false
// "path_exempt": ""
}
}
@endcode
*
* log_format: a format string for access logging, there are several
* placeholders that represent particular data.
* $date: the time when the log was printed.
* $request_date: the time when the request was created.
* $request_path|$path: the path of the request.
* $request_query|$query: the query string of the request.
* $request_url|$url: the URL of the request, equals to
* $request_path+"?"+$request_query.
* $request_version|$version: the http version string.
* $request: the full request line.
* $remote_addr: the remote address
* $local_addr: the local address
* $request_len|$body_bytes_received: the content length of the request.
* $method: the HTTP method of the request.
* $thread: the current thread number.
* $response_len|$body_bytes_sent: the content length of the response.
* $http_[header_name]: the header of the request.
* $cookie_[cookie_name]: the cookie of the request.
* $upstream_http_[header_name]: the header of the response sent to the
* client.
* $status_code: the status code of the response.
* $status: the status code and string of the response.
* $processing_time: request processing time in seconds with a microseconds
* resolution; time elapsed between the request object was
* created and response object was created.
* @note If the format string is empty or not configured, a default value of
* "$request_date $method $url [$body_bytes_received] ($remote_addr -
* $local_addr) $status $body_bytes_sent $processing_time" is applied.
*
* use_spdlog: log using spdlog, disabled by default.
*
* log_path: Log file path, empty by default,in which case,logs are output to
* the regular log file (or stdout based on the log configuration).
*
* log_file: The access log file name, 'access.log' by default. if the file name
* does not contain a extension, the .log extension is used.
*
* log_size_limit: 0 bytes by default, when the log file size reaches
* "log_size_limit", the log file is switched. Zero value means never switch
*
* max_files: 0 by default, when the number of old log files exceeds max_files,
* the oldest log file will be deleted. 0 means never delete.
*
* log_index: The index of log output, 0 by default.
*
* show_microseconds: Whether print microsecond in time. True by default.
*
* custom_time_format: Provide a custom format for time. If not provided or
* empty, the default format is "%Y%m%d %H:%M:%S", with microseconds followed if
* show_microseconds is true. For detailed information about formats, please
* refer to cpp reference about strftime().
*
* use_real_ip: Log the real ip of peer. This option only takes effects when
* set to true and RealIpResolver is enabled. False by default.
*
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
*
* path_exempt: must be a string or a string array, present a regular expression
* (for matching the path of a request) or a regular expression list for URLs
* that don't have to be logged.
*
*/
class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
{
public:
AccessLogger()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override;
private:
trantor::AsyncFileLogger asyncFileLogger_;
int logIndex_{0};
bool useLocalTime_{true};
bool showMicroseconds_{true};
bool useCustomTimeFormat_{false};
std::string timeFormat_;
static bool useRealIp_;
std::regex exemptRegex_;
bool regexFlag_{false};
using LogFunction = std::function<void(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &)>;
std::vector<LogFunction> logFunctions_;
void logging(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp);
void createLogFunctions(std::string format);
LogFunction newLogFunction(const std::string &placeholder);
std::map<std::string, LogFunction> logFunctionMap_;
//$request_path
static void outputReqPath(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$request_query
static void outputReqQuery(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$request_url
static void outputReqURL(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$version
static void outputVersion(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$request
static void outputReqLine(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$date
void outputDate(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) const;
//$request_date
void outputReqDate(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) const;
//$remote_addr
static void outputRemoteAddr(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$local_addr
static void outputLocalAddr(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$request_len $body_bytes_received
static void outputReqLength(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$response_len $body_bytes_sent
static void outputRespLength(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$method
static void outputMethod(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$thread
static void outputThreadNumber(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$http_[header_name]
static void outputReqHeader(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const std::string &headerName);
//$cookie_[cookie_name]
static void outputReqCookie(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const std::string &cookie);
//$upstream_http_[header_name]
static void outputRespHeader(trantor::LogStream &stream,
const drogon::HttpResponsePtr &resp,
const std::string &headerName);
//$status
static void outputStatusString(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$status_code
static void outputStatusCode(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$processing_time
static void outputProcessingTime(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
//$upstream_http_content-type $upstream_http_content_type
static void outputRespContentType(trantor::LogStream &,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &);
};
} // namespace plugin
} // namespace drogon
@@ -0,0 +1,55 @@
#pragma once
#include <drogon/plugins/Plugin.h>
#include <regex>
#include <vector>
#include <memory>
#include <drogon/HttpFilter.h>
namespace drogon
{
namespace plugin
{
/**
* @brief This plugin is used to add global filters to all HTTP requests.
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::GlobalFilters",
"dependencies": [],
"config": {
// filters: the list of global filter names.
"filters": [
"FilterName1", "FilterName2",...
],
// exempt: exempt must be a string or string array, regular
expressions for
// URLs that don't have to be filtered.
"exempt": [
"^/static/.*\\.css", "^/images/.*",...
]
}
}
@endcode
*
*/
class DROGON_EXPORT GlobalFilters
: public drogon::Plugin<GlobalFilters>,
public std::enable_shared_from_this<GlobalFilters>
{
public:
GlobalFilters()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override;
private:
std::vector<std::shared_ptr<drogon::HttpFilterBase>> filters_;
std::regex exemptPegex_;
bool regexFlag_{false};
};
} // namespace plugin
} // namespace drogon
+154
View File
@@ -0,0 +1,154 @@
/**
* @file Hodor.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/RateLimiter.h>
#include <drogon/plugins/Plugin.h>
#include <drogon/plugins/RealIpResolver.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/CacheMap.h>
#include <regex>
#include <optional>
namespace drogon
{
namespace plugin
{
/**
* @brief The Hodor plugin implements a global rate limiter that limits the
* number of requests in a particular time unit.
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::Hodor",
"dependencies": [],
"config": {
// The algorithm used to limit the number of requests.
// The default value is "token_bucket". other values are "fixed_window"
or "sliding_window".
"algorithm": "token_bucket",
// a regular expression (for matching the path of a request) list for
URLs that have to be limited. if the list is empty, all URLs are limited.
"urls": ["^/api/.*", ...],
// The time unit in seconds. the default value is 60.
"time_unit": 60,
// The maximum number of requests in a time unit. the default value 0
means no limit.
"capacity": 0,
// The maximum number of requests in a time unit for a single IP. the
default value 0 means no limit.
"ip_capacity": 0,
// The maximum number of requests in a time unit for a single user.
a function must be provided to the plugin to get the user id from the request.
the default value 0 means no limit.
"user_capacity": 0,
// Use the RealIpResolver plugin to get the real IP address of the
request. if this option is true, the RealIpResolver plugin should be added to
the dependencies list. the default value is false.
"use_real_ip_resolver": false,
// Multiple threads mode: the default value is true. if this option is
true, some mutexes are used for thread-safe.
"multi_threads": true,
// The message body of the response when the request is rejected.
"rejection_message": "Too many requests",
// In seconds, the minimum expiration time of the limiters for different
IPs or users. the default value is 600.
"limiter_expire_time": 600,
"sub_limits": [
{
"urls": ["^/api/1/.*", ...],
"capacity": 0,
"ip_capacity": 0,
"user_capacity": 0
},...
],
// Trusted proxy ip or cidr
"trust_ips": ["127.0.0.1", "172.16.0.0/12"],
}
}
@endcode
*
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
* */
class DROGON_EXPORT Hodor : public drogon::Plugin<Hodor>
{
public:
Hodor()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override;
/**
* @brief the method is used to set a function to get the user id from the
* request. users should call this method after calling the app().run()
* method. etc. use the beginning advice of AOP.
* */
void setUserIdGetter(
std::function<std::optional<std::string>(const HttpRequestPtr &)> func)
{
userIdGetter_ = std::move(func);
}
/**
* @brief the method is used to set a function to create the response when
* the rate limit is exceeded. users should call this method after calling
* the app().run() method. etc. use the beginning advice of AOP.
* */
void setRejectResponseFactory(
std::function<HttpResponsePtr(const HttpRequestPtr &)> func)
{
rejectResponseFactory_ = std::move(func);
}
private:
struct LimitStrategy
{
std::regex urlsRegex;
size_t capacity{0};
size_t ipCapacity{0};
size_t userCapacity{0};
bool regexFlag{false};
RateLimiterPtr globalLimiterPtr;
std::unique_ptr<CacheMap<std::string, RateLimiterPtr>> ipLimiterMapPtr;
std::unique_ptr<CacheMap<std::string, RateLimiterPtr>>
userLimiterMapPtr;
};
LimitStrategy makeLimitStrategy(const Json::Value &config);
std::vector<LimitStrategy> limitStrategies_;
RateLimiterType algorithm_{RateLimiterType::kTokenBucket};
std::chrono::duration<double> timeUnit_{1.0};
bool multiThreads_{true};
bool useRealIpResolver_{false};
size_t limiterExpireTime_{600};
std::function<std::optional<std::string>(const drogon::HttpRequestPtr &)>
userIdGetter_;
std::function<HttpResponsePtr(const drogon::HttpRequestPtr &)>
rejectResponseFactory_;
RealIpResolver::CIDRs trustCIDRs_;
void onHttpRequest(const drogon::HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&);
bool checkLimit(const drogon::HttpRequestPtr &req,
const LimitStrategy &strategy,
const trantor::InetAddress &ip,
const std::optional<std::string> &userId);
HttpResponsePtr rejectResponse_;
};
} // namespace plugin
} // namespace drogon
+144
View File
@@ -0,0 +1,144 @@
/**
* @file Plugin.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/DrObject.h>
#include <json/json.h>
#include <memory>
#include <trantor/utils/Logger.h>
#include <trantor/utils/NonCopyable.h>
namespace drogon
{
enum class PluginStatus
{
None,
Initializing,
Initialized
};
/**
* @brief The abstract base class for plugins.
*
*/
class DROGON_EXPORT PluginBase : public virtual DrObjectBase,
public trantor::NonCopyable
{
public:
/// This method must be called by drogon.
void initialize()
{
if (status_ == PluginStatus::None)
{
status_ = PluginStatus::Initializing;
for (auto dependency : dependencies_)
{
dependency->initialize();
}
initAndStart(config_);
status_ = PluginStatus::Initialized;
if (initializedCallback_)
initializedCallback_(this);
}
else if (status_ == PluginStatus::Initialized)
{
// Do nothing;
}
else
{
LOG_FATAL << "There are a circular dependency within plugins.";
abort();
}
}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
virtual void initAndStart(const Json::Value &config) = 0;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
virtual void shutdown() = 0;
virtual ~PluginBase()
{
}
protected:
PluginBase()
{
}
private:
PluginStatus status_{PluginStatus::None};
friend class PluginsManager;
void setConfig(const Json::Value &config)
{
config_ = config;
}
void addDependency(PluginBase *dp)
{
dependencies_.push_back(dp);
}
void setInitializedCallback(const std::function<void(PluginBase *)> &cb)
{
initializedCallback_ = cb;
}
Json::Value config_;
std::vector<PluginBase *> dependencies_;
std::function<void(PluginBase *)> initializedCallback_;
};
template <typename T>
struct IsPlugin
{
using TYPE = std::remove_cv_t<typename std::remove_reference_t<T>>;
static int test(void *)
{
return 0;
}
static char test(PluginBase *)
{
return 0;
}
static constexpr bool value =
(sizeof(test((TYPE *)nullptr)) == sizeof(char));
};
/**
* @brief The reflection base class for plugins.
*
* @tparam T The type of the implementation plugin classes.
*/
template <typename T>
class Plugin : public PluginBase, public DrObject<T>
{
public:
virtual ~Plugin()
{
}
protected:
Plugin()
{
}
};
} // namespace drogon
@@ -0,0 +1,98 @@
/**
* @file PromExporter.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/plugins/Plugin.h>
#include <drogon/utils/monitoring/Registry.h>
#include <drogon/utils/monitoring/Collector.h>
#include <memory>
#include <mutex>
namespace drogon
{
namespace plugin
{
/**
* @brief The PromExporter plugin implements a prometheus exporter.
* The json configuration is as follows:
* @code
{
"name": "drogon::plugin::PromExporter",
"dependencies": [],
"config": {
// The path of the metrics. the default value is "/metrics".
"path": "/metrics",
// The list of collectors.
"collectors":[
{
// The name of the collector.
"name": "http_requests_total",
// The help message of the collector.
"help": "The total number of http requests",
// The type of the collector. The default value is "counter".
// The other possible value is as following:
// "gauge", "histogram".
"type": "counter",
// The labels of the collector.
"labels": ["method", "status"]
}
]
}
}
@endcode
* */
class DROGON_EXPORT PromExporter
: public drogon::Plugin<PromExporter>,
public std::enable_shared_from_this<PromExporter>,
public drogon::monitoring::Registry
{
public:
PromExporter()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override
{
}
~PromExporter() override
{
}
void registerCollector(
const std::shared_ptr<drogon::monitoring::CollectorBase> &collector)
override;
std::shared_ptr<drogon::monitoring::CollectorBase> getCollector(
const std::string &name) const noexcept(false);
template <typename T>
std::shared_ptr<drogon::monitoring::Collector<T>> getCollector(
const std::string &name) const
{
return std::dynamic_pointer_cast<drogon::monitoring::Collector<T>>(
getCollector(name));
}
private:
mutable std::mutex mutex_;
std::unordered_map<std::string,
std::shared_ptr<drogon::monitoring::CollectorBase>>
collectors_;
std::string path_{"/metrics"};
std::string exportMetrics();
};
} // namespace plugin
} // namespace drogon
@@ -0,0 +1,79 @@
/**
*
* RealIpResolver.h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
#include <trantor/net/InetAddress.h>
#include <drogon/HttpRequest.h>
#include <vector>
namespace drogon
{
namespace plugin
{
/**
* @brief This plugin is used to resolve client real ip from HTTP request.
* @note This plugin currently supports only ipv4 address or cidr.
*
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::RealIpResolver",
"dependencies": [],
"config": {
// Trusted proxy ip or cidr
"trust_ips": ["127.0.0.1", "172.16.0.0/12"],
// Which header to parse ip form. Default is x-forwarded-for
"from_header": "x-forwarded-for",
// The result will be inserted to HttpRequest attribute map with this
// key. Default is "real-ip"
"attribute_key": "real-ip"
}
}
@endcode
*
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
*
*/
class DROGON_EXPORT RealIpResolver : public drogon::Plugin<RealIpResolver>
{
public:
RealIpResolver()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override;
static const trantor::InetAddress &GetRealAddr(
const drogon::HttpRequestPtr &req);
private:
const trantor::InetAddress &getRealAddr(
const drogon::HttpRequestPtr &req) const;
struct CIDR
{
explicit CIDR(const std::string &ipOrCidr);
in_addr_t addr_{0};
in_addr_t mask_{32};
};
using CIDRs = std::vector<CIDR>;
static bool matchCidr(const trantor::InetAddress &addr,
const CIDRs &trustCIDRs);
friend class Hodor;
CIDRs trustCIDRs_;
std::string fromHeader_;
std::string attributeKey_;
bool useXForwardedFor_{false};
};
} // namespace plugin
} // namespace drogon
@@ -0,0 +1,119 @@
/**
* @file Redirector.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/plugins/Plugin.h>
#include <drogon/HttpRequest.h>
#include <vector>
namespace drogon
{
namespace plugin
{
/**
* @brief The RedirectorHandler is a function object that can be registered to
* the Redirector plugin. It is used to redirect requests to proper URLs. Users
* can modify the protocol, host and path of the request. If a false value is
* returned, the request will be considered as invalid and a 404 response will
* be sent to the client.
*/
using RedirectorHandler =
std::function<bool(const drogon::HttpRequestPtr &,
std::string &, //"http://" or "https://"
std::string &, // host
bool &)>; // path changed or not
/**
* @brief The PathRewriteHandler is a function object that can be registered to
* the Redirector plugin. It is used to rewrite the path of the request. The
* Redirector plugin will call all registered PathRewriteHandlers in the order
* of registration. If one or more handlers return true, the request will be
* redirected to the new path.
*/
using PathRewriteHandler = std::function<bool(const drogon::HttpRequestPtr &)>;
/**
* @brief The ForwardHandler is a function object that can be registered to the
* Redirector plugin. It is used to forward the request to next processing steps
* in the framework. The Redirector plugin will call all registered
* ForwardHandlers in the order of registration. Users can use this handler to
* change the request path or any other part of the request.
*/
using ForwardHandler = std::function<void(const drogon::HttpRequestPtr &)>;
/**
* @brief This plugin is used to redirect requests to proper URLs. It is a
* helper plugin for other plugins, e.g. SlashRemover.
* Users can register a handler to this plugin to redirect requests. All
* handlers will be called in the order of registration.
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::Redirector",
"dependencies": [],
"config": {
}
}
@endcode
*
*/
class DROGON_EXPORT Redirector : public drogon::Plugin<Redirector>,
public std::enable_shared_from_this<Redirector>
{
public:
Redirector()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override;
void registerRedirectHandler(RedirectorHandler &&handler)
{
handlers_.emplace_back(std::move(handler));
}
void registerRedirectHandler(const RedirectorHandler &handler)
{
handlers_.emplace_back(handler);
}
void registerPathRewriteHandler(PathRewriteHandler &&handler)
{
pathRewriteHandlers_.emplace_back(std::move(handler));
}
void registerPathRewriteHandler(const PathRewriteHandler &handler)
{
pathRewriteHandlers_.emplace_back(handler);
}
void registerForwardHandler(ForwardHandler &&handler)
{
forwardHandlers_.emplace_back(std::move(handler));
}
void registerForwardHandler(const ForwardHandler &handler)
{
forwardHandlers_.emplace_back(handler);
}
private:
std::vector<RedirectorHandler> handlers_;
std::vector<PathRewriteHandler> pathRewriteHandlers_;
std::vector<ForwardHandler> forwardHandlers_;
};
} // namespace plugin
} // namespace drogon
@@ -0,0 +1,80 @@
/**
*
* @file drogon_plugin_SecureSSLRedirector.h
*
*/
#pragma once
#include <drogon/exports.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/plugins/Plugin.h>
#include <regex>
#include <memory>
namespace drogon
{
namespace plugin
{
/**
* @brief This plugin is used to redirect all non-HTTPS requests to HTTPS
* (except for those URLs matching a regular expression listed in
* the 'ssl_redirect_exempt' list).
*
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::SecureSSLRedirector",
"dependencies": ["drogon::plugin::Redirector"],
"config": {
"ssl_redirect_exempt": ["^/.*\\.jpg", ...],
"secure_ssl_host": "localhost:8849"
}
}
@endcode
*
* ssl_redirect_exempt: must be a string or a string array, present a regular
expression
* (for matching the path of a request) or a regular expression list for URLs
that don't
* have to be redirected.
*
* secure_ssl_host: If this string is not empty, all SSL redirects
* will be directed to this host rather than the originally-requested host.
*
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
*
*/
class DROGON_EXPORT SecureSSLRedirector
: public drogon::Plugin<SecureSSLRedirector>,
public std::enable_shared_from_this<SecureSSLRedirector>
{
public:
SecureSSLRedirector()
{
}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
void shutdown() override;
private:
bool redirectingAdvice(const HttpRequestPtr &,
std::string &,
std::string &) const;
bool redirectToSSL(const HttpRequestPtr &,
std::string &,
std::string &) const;
std::regex exemptRegex_;
bool regexFlag_{false};
std::string secureHost_;
};
} // namespace plugin
} // namespace drogon
@@ -0,0 +1,61 @@
/**
* @file SlashRemover.h
* @author Mis1eader
*
* Copyright 2023, Mis1eader. 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 "drogon/utils/FunctionTraits.h"
#include <json/value.h>
namespace drogon::plugin
{
/**
* @brief The SlashRemover plugin redirects requests to proper paths if they
* contain excessive slashes.
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::SlashRemover",
"dependencies": ["drogon::plugin::Redirector"],
"config": {
// If true, it removes all trailing slashes, e.g.
///home// -> ///home
"remove_trailing_slashes": true,
// If true, it removes all duplicate slashes, e.g.
///home// -> /home/
"remove_duplicate_slashes": true,
// If true, redirects the request, otherwise forwards
internally.
"redirect": true
}
}
@endcode
*
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
* */
class DROGON_EXPORT SlashRemover : public drogon::Plugin<SlashRemover>
{
public:
SlashRemover()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override;
private:
bool trailingSlashes_{true}, duplicateSlashes_{true}, redirect_{true};
};
} // namespace drogon::plugin
@@ -0,0 +1,247 @@
/**
*
* FunctionTraits.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/DrObject.h>
#include <drogon/RequestStream.h>
#include <functional>
#include <memory>
#include <tuple>
#include <type_traits>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
class HttpRequest;
class HttpResponse;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
namespace internal
{
#ifdef __cpp_impl_coroutine
template <typename T>
using resumable_type = is_resumable<T>;
#else
template <typename T>
struct resumable_type : std::false_type
{
};
#endif
template <typename>
struct FunctionTraits;
//
// Basic match, inherited by all other matches
//
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (*)(Arguments...)>
{
using result_type = ReturnType;
template <std::size_t Index>
using argument =
typename std::tuple_element_t<Index, std::tuple<Arguments...>>;
static const std::size_t arity = sizeof...(Arguments);
using class_type = void;
using return_type = ReturnType;
static const bool isHTTPFunction = false;
static const bool isClassFunction = false;
static const bool isStreamHandler = false;
static const bool isDrObjectClass = false;
static const bool isCoroutine = false;
static const std::string name()
{
return std::string("Normal or Static Function");
}
};
//
// Match normal functions
//
// normal function for HTTP handling
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = ReturnType;
};
// normal function with custom request object
template <typename T, typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(T &&customReq,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
using class_type = void;
using first_param_type = T;
using return_type = ReturnType;
};
// normal function with stream handler
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(const HttpRequestPtr &req,
RequestStreamPtr &&streamCtx,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
static const bool isStreamHandler = true;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = ReturnType;
};
//
// Match functor,lambda,std::function... inherits normal function matches
//
template <typename Function>
struct FunctionTraits
: public FunctionTraits<
decltype(&std::remove_reference_t<Function>::operator())>
{
static const bool isClassFunction = false;
static const bool isDrObjectClass = false;
using class_type = void;
static const std::string name()
{
return std::string("Functor");
}
};
//
// Match class functions, inherits normal function matches
//
// class const method
template <typename ClassType, typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
: FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isClassFunction = true;
static const bool isDrObjectClass =
std::is_base_of<DrObject<ClassType>, ClassType>::value;
using class_type = ClassType;
static const std::string name()
{
return std::string("Class Function");
}
};
// class non-const method
template <typename ClassType, typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...)>
: FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isClassFunction = true;
static const bool isDrObjectClass =
std::is_base_of<DrObject<ClassType>, ClassType>::value;
using class_type = ClassType;
static const std::string name()
{
return std::string("Class Function");
}
};
//
// Match coroutine functions
//
#ifdef __cpp_impl_coroutine
template <typename... Arguments>
struct FunctionTraits<
AsyncTask (*)(HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
Arguments...)> : FunctionTraits<AsyncTask (*)(Arguments...)>
{
static const bool isHTTPFunction = true;
static const bool isCoroutine = true;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = AsyncTask;
};
template <typename... Arguments>
struct FunctionTraits<
Task<> (*)(HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
Arguments...)> : FunctionTraits<AsyncTask (*)(Arguments...)>
{
static const bool isHTTPFunction = true;
static const bool isCoroutine = true;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = Task<>;
};
template <typename... Arguments>
struct FunctionTraits<Task<HttpResponsePtr> (*)(HttpRequestPtr req,
Arguments...)>
: FunctionTraits<AsyncTask (*)(Arguments...)>
{
static const bool isHTTPFunction = true;
static const bool isCoroutine = true;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = Task<HttpResponsePtr>;
};
#endif
//
// Bad matches
//
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = false;
using class_type = void;
};
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(HttpRequestPtr &&req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = false;
using class_type = void;
};
} // namespace internal
} // namespace drogon
@@ -0,0 +1,71 @@
/**
*
* HttpConstraint.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 <string>
namespace drogon
{
namespace internal
{
enum class ConstraintType
{
None,
HttpMethod,
HttpMiddleware
};
class HttpConstraint
{
public:
HttpConstraint(HttpMethod method)
: type_(ConstraintType::HttpMethod), method_(method)
{
}
HttpConstraint(std::string middlewareName)
: type_(ConstraintType::HttpMiddleware),
middlewareName_(std::move(middlewareName))
{
}
HttpConstraint(const char *middlewareName)
: type_(ConstraintType::HttpMiddleware), middlewareName_(middlewareName)
{
}
ConstraintType type() const
{
return type_;
}
HttpMethod getHttpMethod() const
{
return method_;
}
const std::string &getMiddlewareName() const
{
return middlewareName_;
}
private:
ConstraintType type_{ConstraintType::None};
HttpMethod method_{HttpMethod::Invalid};
std::string middlewareName_;
};
} // namespace internal
} // namespace drogon
@@ -0,0 +1,140 @@
/**
*
* OStringStream.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string>
#include <sstream>
#include <string_view>
namespace drogon
{
namespace internal
{
template <typename T, typename = void>
struct CanConvertToString : std::false_type
{
};
template <typename T>
struct CanConvertToString<
T,
std::void_t<decltype(std::to_string(std::declval<T>()))>> : std::true_type
{
};
} // namespace internal
class OStringStream
{
public:
OStringStream() = default;
void reserve(size_t size)
{
buffer_.reserve(size);
}
template <typename T>
OStringStream &operator<<(T &&value)
{
if constexpr (internal::CanConvertToString<T>::value)
{
buffer_.append(std::to_string(std::forward<T>(value)));
return *this;
}
else
{
std::stringstream ss;
ss << std::forward<T>(value);
buffer_.append(ss.str());
return *this;
}
}
template <int N>
OStringStream &operator<<(const char (&buf)[N])
{
buffer_.append(buf, N - 1);
return *this;
}
OStringStream &operator<<(const std::string_view &str)
{
buffer_.append(str.data(), str.length());
return *this;
}
OStringStream &operator<<(std::string_view &&str)
{
buffer_.append(str.data(), str.length());
return *this;
}
OStringStream &operator<<(const std::string &str)
{
buffer_.append(str);
return *this;
}
OStringStream &operator<<(std::string &&str)
{
buffer_.append(std::move(str));
return *this;
}
OStringStream &operator<<(const double &d)
{
std::stringstream ss;
ss << d;
buffer_.append(ss.str());
return *this;
}
OStringStream &operator<<(const float &f)
{
std::stringstream ss;
ss << f;
buffer_.append(ss.str());
return *this;
}
OStringStream &operator<<(double &&d)
{
std::stringstream ss;
ss << d;
buffer_.append(ss.str());
return *this;
}
OStringStream &operator<<(float &&f)
{
std::stringstream ss;
ss << f;
buffer_.append(ss.str());
return *this;
}
std::string &str()
{
return buffer_;
}
const std::string &str() const
{
return buffer_;
}
private:
std::string buffer_;
};
} // namespace drogon
+596
View File
@@ -0,0 +1,596 @@
/**
*
* @file Utilities.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/exports.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/Funcs.h>
#include <trantor/utils/Utilities.h>
#include <trantor/utils/LogStream.h>
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <limits>
#include <sstream>
#include <algorithm>
#include <filesystem>
#include <string_view>
#include <unordered_map>
#include <type_traits>
#ifdef _WIN32
#include <time.h>
DROGON_EXPORT char *strptime(const char *s, const char *f, struct tm *tm);
DROGON_EXPORT time_t timegm(struct tm *tm);
#endif
namespace drogon
{
namespace internal
{
template <typename T, typename = void>
struct CanConvertFromStringStream : std::false_type
{
};
template <typename T>
struct CanConvertFromStringStream<
T,
std::void_t<decltype(std::declval<std::stringstream &>() >>
std::declval<T &>())>> : std::true_type
{
};
template <typename T>
struct CanConstructFromString : std::is_constructible<T, std::string>
{
};
template <typename T>
struct CanConvertFromString : std::is_assignable<T &, std::string>
{
};
} // namespace internal
/**
* @brief Get the HTTP messages corresponding to the HTTP status codes
*
* @param code HTTP status code
*
* @return the corresponding message
*/
DROGON_EXPORT const std::string_view &statusCodeToString(int code);
namespace utils
{
/// Determine if the string is an integer
DROGON_EXPORT bool isInteger(std::string_view str);
/// Determine if the string is base64 encoded
DROGON_EXPORT bool isBase64(std::string_view str);
/// Generate random a string
/**
* @param length The string length
* The returned string consists of uppercase and lowercase letters and numbers
*/
DROGON_EXPORT std::string genRandomString(int length);
/// Convert a binary string to hex format
DROGON_EXPORT std::string binaryStringToHex(const unsigned char *ptr,
size_t length,
bool lowerCase = false);
/// Get a binary string from hexadecimal format
DROGON_EXPORT std::string hexToBinaryString(const char *ptr, size_t length);
/// Get a binary vector from hexadecimal format
DROGON_EXPORT std::vector<char> hexToBinaryVector(const char *ptr,
size_t length);
DROGON_EXPORT void binaryStringToHex(const char *ptr,
size_t length,
char *out,
bool lowerCase = false);
/// Split the string into multiple separated strings.
/**
* @param str string to split
* @param separator element separator
* @param acceptEmptyString if true, empty strings are accepted in the
* result, for example, splitting the ",1,2,,3," by "," produces
* ["","1","2","","3",""]
*/
inline std::vector<std::string> splitString(const std::string &str,
const std::string &separator,
bool acceptEmptyString = false)
{
return trantor::splitString(str, separator, acceptEmptyString);
}
DROGON_EXPORT std::set<std::string> splitStringToSet(
const std::string &str,
const std::string &separator);
/// Get UUID string.
DROGON_EXPORT std::string getUuid(bool lowercase = true);
/// Get the encoded length of base64.
constexpr size_t base64EncodedLength(size_t in_len, bool padded = true)
{
return padded ? ((in_len + 3 - 1) / 3) * 4 : (in_len * 8 + 6 - 1) / 6;
}
/// Encode the string to base64 format.
DROGON_EXPORT void base64Encode(const unsigned char *bytesToEncode,
size_t inLen,
unsigned char *outputBuffer,
bool urlSafe = false,
bool padded = true);
/// Encode the string to base64 format.
inline std::string base64Encode(const unsigned char *bytesToEncode,
size_t inLen,
bool urlSafe = false,
bool padded = true)
{
std::string ret;
ret.resize(base64EncodedLength(inLen, padded));
base64Encode(
bytesToEncode, inLen, (unsigned char *)ret.data(), urlSafe, padded);
return ret;
}
/// Encode the string to base64 format.
inline std::string base64Encode(std::string_view data,
bool urlSafe = false,
bool padded = true)
{
return base64Encode((unsigned char *)data.data(),
data.size(),
urlSafe,
padded);
}
/// Encode the string to base64 format with no padding.
inline void base64EncodeUnpadded(const unsigned char *bytesToEncode,
size_t inLen,
unsigned char *outputBuffer,
bool urlSafe = false)
{
base64Encode(bytesToEncode, inLen, outputBuffer, urlSafe, false);
}
/// Encode the string to base64 format with no padding.
inline std::string base64EncodeUnpadded(const unsigned char *bytesToEncode,
size_t inLen,
bool urlSafe = false)
{
return base64Encode(bytesToEncode, inLen, urlSafe, false);
}
/// Encode the string to base64 format with no padding.
inline std::string base64EncodeUnpadded(std::string_view data,
bool urlSafe = false)
{
return base64Encode(data, urlSafe, false);
}
/// Get the decoded length of base64.
constexpr size_t base64DecodedLength(size_t inLen)
{
return (inLen * 3) / 4;
}
/// Decode the base64 format string.
/// Return the number of bytes written.
DROGON_EXPORT size_t base64Decode(const char *encodedString,
size_t inLen,
unsigned char *outputBuffer);
/// Decode the base64 format string.
inline std::string base64Decode(std::string_view encodedString)
{
auto inLen = encodedString.size();
std::string ret;
ret.resize(base64DecodedLength(inLen));
ret.resize(
base64Decode(encodedString.data(), inLen, (unsigned char *)ret.data()));
return ret;
}
DROGON_EXPORT std::vector<char> base64DecodeToVector(
std::string_view encodedString);
/// Check if the string need decoding
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);
/// Decode from or encode to the URL format string
DROGON_EXPORT std::string urlDecode(const char *begin, const char *end);
inline std::string urlDecode(const std::string &szToDecode)
{
auto begin = szToDecode.data();
return urlDecode(begin, begin + szToDecode.length());
}
inline std::string urlDecode(const std::string_view &szToDecode)
{
auto begin = szToDecode.data();
return urlDecode(begin, begin + szToDecode.length());
}
DROGON_EXPORT std::string urlEncode(const std::string &);
DROGON_EXPORT std::string urlEncodeComponent(const std::string &);
/// Get the MD5 digest of a string.
DROGON_EXPORT std::string getMd5(const char *data, const size_t dataLen);
inline std::string getMd5(const std::string &originalString)
{
return getMd5(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getSha1(const char *data, const size_t dataLen);
inline std::string getSha1(const std::string &originalString)
{
return getSha1(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getSha256(const char *data, const size_t dataLen);
inline std::string getSha256(const std::string &originalString)
{
return getSha256(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getSha3(const char *data, const size_t dataLen);
inline std::string getSha3(const std::string &originalString)
{
return getSha3(originalString.data(), originalString.length());
}
DROGON_EXPORT std::string getBlake2b(const char *data, const size_t dataLen);
inline std::string getBlake2b(const std::string &originalString)
{
return getBlake2b(originalString.data(), originalString.length());
}
/// Compress or decompress data using gzip lib.
/**
* @param data the input data
* @param ndata the input data length
*/
DROGON_EXPORT std::string gzipCompress(const char *data, const size_t ndata);
DROGON_EXPORT std::string gzipDecompress(const char *data, const size_t ndata);
/// Compress or decompress data using brotli lib.
/**
* @param data the input data
* @param ndata the input data length
*/
DROGON_EXPORT std::string brotliCompress(const char *data, const size_t ndata);
DROGON_EXPORT std::string brotliDecompress(const char *data,
const size_t ndata);
/// Get the http full date string
/**
* rfc2616-3.3.1
* Full Date format(RFC 822)
* like this:
* @code
Sun, 06 Nov 1994 08:49:37 GMT
Wed, 12 Sep 2018 09:22:40 GMT
@endcode
*/
DROGON_EXPORT char *getHttpFullDate(
const trantor::Date &date = trantor::Date::now());
DROGON_EXPORT const std::string &getHttpFullDateStr(
const trantor::Date &date = trantor::Date::now());
DROGON_EXPORT void dateToCustomFormattedString(const std::string &fmtStr,
std::string &str,
const trantor::Date &date);
/// Get the trantor::Date object according to the http full date string
/**
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
*/
DROGON_EXPORT trantor::Date getHttpDate(const std::string &httpFullDateString);
/// Get a formatted string
DROGON_EXPORT std::string formattedString(const char *format, ...);
/// Recursively create a file system path
/**
* Return 0 or -1 on success or failure.
*/
DROGON_EXPORT int createPath(const std::string &path);
/**
* @details Convert a wide string path with arbitrary directory separators
* to a UTF-8 portable path for use with trantor.
*
* This is a helper, mainly for Windows and multi-platform projects.
*
* @note On Windows, backslash directory separators are converted to slash to
* keep portable paths.
*
* @remarks On other OSes, backslashes are not converted to slash, since they
* are valid characters for directory/file names.
*
* @param strPath Wide string path.
*
* @return std::string UTF-8 path, with slash directory separator.
*/
inline std::string fromWidePath(const std::wstring &strPath)
{
return trantor::utils::fromWidePath(strPath);
}
/**
* @details Convert a UTF-8 path with arbitrary directory separator to a wide
* string path.
*
* This is a helper, mainly for Windows and multi-platform projects.
*
* @note On Windows, slash directory separators are converted to backslash.
* Although it accepts both slash and backslash as directory separator in its
* API, it is better to stick to its standard.
* @remarks On other OSes, slashes are not converted to backslashes, since they
* are not interpreted as directory separators and are valid characters for
* directory/file names.
*
* @param strUtf8Path Ascii path considered as being UTF-8.
*
* @return std::wstring path with, on windows, standard backslash directory
* separator to stick to its standard.
*/
inline std::wstring toWidePath(const std::string &strUtf8Path)
{
return trantor::utils::toWidePath(strUtf8Path);
}
/**
* @brief Convert a generic (UTF-8) path with to an OS native path.
* @details This is a helper, mainly for Windows and multi-platform projects.
*
* On Windows, slash directory separators are converted to backslash, and a
* wide string is returned.
*
* On other OSes, returns an UTF-8 string _without_ altering the directory
* separators.
*
* @param strPath Wide string or UTF-8 path.
*
* @return An OS path, suitable for use with the OS API.
*/
#if defined(_WIN32) && !defined(__MINGW32__)
inline std::wstring toNativePath(const std::string &strPath)
{
return trantor::utils::toNativePath(strPath);
}
inline const std::wstring &toNativePath(const std::wstring &strPath)
{
return trantor::utils::toNativePath(strPath);
}
#else // __WIN32
inline const std::string &toNativePath(const std::string &strPath)
{
return trantor::utils::toNativePath(strPath);
}
inline std::string toNativePath(const std::wstring &strPath)
{
return trantor::utils::toNativePath(strPath);
}
#endif // _WIN32
/**
* @brief Convert a OS native path (wide string on Windows) to a generic UTF-8
* path.
* @details This is a helper, mainly for Windows and multi-platform projects.
*
* On Windows, backslash directory separators are converted to slash, and a
* a UTF-8 string is returned, suitable for libraries that supports UTF-8 paths
* like OpenSSL or drogon.
*
* On other OSes, returns an UTF-8 string without altering the directory
* separators (backslashes are *NOT* replaced with slashes, since they
* are valid characters for directory/file names).
*
* @param strPath Wide string or UTF-8 path.
*
* @return A generic path.
*/
inline const std::string &fromNativePath(const std::string &strPath)
{
return trantor::utils::fromNativePath(strPath);
}
// Convert on all systems
inline std::string fromNativePath(const std::wstring &strPath)
{
return trantor::utils::fromNativePath(strPath);
}
/// Replace all occurrences of from to to inplace
/**
* @param s string to alter
* @param from string to replace
* @param to string to replace with
*/
DROGON_EXPORT void replaceAll(std::string &s,
const std::string &from,
const std::string &to);
/**
* @brief Generates cryptographically secure random bytes.
*
* @param ptr the pointer which the random bytes are stored to
* @param size number of bytes to generate
*
* @return true if generation is successful. False otherwise
*/
DROGON_EXPORT bool secureRandomBytes(void *ptr, size_t size);
/**
* @brief Generates cryptographically secure random string.
*
* @param size number of characters to generate
*
* @return the random string
*/
DROGON_EXPORT std::string secureRandomString(size_t size);
template <typename T>
T fromString(const std::string &p) noexcept(false)
{
if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
{
std::size_t pos;
auto v = std::stoll(p, &pos);
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (pos != p.size())
throw std::invalid_argument("Invalid value");
if ((v < static_cast<long long>((std::numeric_limits<T>::min)())) ||
(v > static_cast<long long>((std::numeric_limits<T>::max)())))
throw std::out_of_range("Value out of range");
return static_cast<T>(v);
}
else if constexpr (std::is_integral<T>::value &&
(!std::is_signed<T>::value))
{
std::size_t pos;
auto v = std::stoull(p, &pos);
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (pos != p.size())
throw std::invalid_argument("Invalid value");
if (v >
static_cast<unsigned long long>((std::numeric_limits<T>::max)()))
throw std::out_of_range("Value out of range");
return static_cast<T>(v);
}
else if constexpr (std::is_floating_point<T>::value)
{
std::size_t pos;
auto v = std::stold(p, &pos);
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (pos != p.size())
throw std::invalid_argument("Invalid value");
if ((v < static_cast<long double>((std::numeric_limits<T>::min)())) ||
(v > static_cast<long double>((std::numeric_limits<T>::max)())))
throw std::out_of_range("Value out of range");
return static_cast<T>(v);
}
else if constexpr (internal::CanConvertFromStringStream<T>::value)
{
T value{};
if (!p.empty())
{
std::stringstream ss(p);
// must except in case of invalid value, not return a default value
// (else it returns 0 for integers if the string is empty or
// non-numeric)
ss.exceptions(std::ios_base::failbit);
ss >> value;
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (!ss.eof())
std::runtime_error("Bad type conversion");
}
return value;
}
else
{
throw std::runtime_error("Bad type conversion");
}
}
template <>
inline std::string fromString<std::string>(const std::string &p) noexcept(false)
{
return p;
}
template <>
inline bool fromString<bool>(const std::string &p) noexcept(false)
{
if (!p.empty() && std::all_of(p.begin(), p.end(), [](unsigned char c) {
return std::isdigit(c);
}))
return (std::stoll(p) != 0);
std::string l{p};
std::transform(p.begin(), p.end(), l.begin(), [](unsigned char c) {
return (char)tolower(c);
});
if (l == "true")
{
return true;
}
else if (l == "false")
{
return false;
}
throw std::runtime_error("Can't convert from string '" + p + "' to bool");
}
DROGON_EXPORT bool supportsTls() noexcept;
namespace internal
{
DROGON_EXPORT extern const size_t fixedRandomNumber;
struct SafeStringHash
{
size_t operator()(const std::string &str) const
{
const size_t A = 6665339;
const size_t B = 2534641;
size_t h = fixedRandomNumber;
for (char ch : str)
h = (h * A) ^ (ch * B);
return h;
}
};
} // namespace internal
} // namespace utils
template <typename T>
using SafeStringMap =
std::unordered_map<std::string, T, utils::internal::SafeStringHash>;
} // namespace drogon
namespace trantor
{
inline LogStream &operator<<(LogStream &ls, const std::string_view &v)
{
if (!v.empty())
ls.append(v.data(), v.length());
return ls;
}
inline LogStream &operator<<(LogStream &ls, const std::filesystem::path &p)
{
return ls << p.string();
}
} // namespace trantor
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
/**
*
* monitoring.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Metric.h>
#include <drogon/utils/monitoring/Registry.h>
#include <drogon/utils/monitoring/Collector.h>
#include <drogon/utils/monitoring/Sample.h>
@@ -0,0 +1,135 @@
/**
*
* Collector.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Date.h>
#include <drogon/utils/monitoring/Sample.h>
#include <drogon/utils/monitoring/Metric.h>
#include <drogon/utils/monitoring/Registry.h>
#include <string>
#include <string_view>
#include <vector>
#include <mutex>
#include <map>
#include <algorithm>
#include <memory>
namespace drogon
{
namespace monitoring
{
struct SamplesGroup
{
std::shared_ptr<Metric> metric;
std::vector<Sample> samples;
};
class CollectorBase : public std::enable_shared_from_this<CollectorBase>
{
public:
virtual ~CollectorBase() = default;
virtual std::vector<SamplesGroup> collect() const = 0;
virtual const std::string &name() const = 0;
virtual const std::string &help() const = 0;
virtual const std::string_view type() const = 0;
};
/**
* @brief The Collector class template is used to collect samples from a group
* of metric.
*/
template <typename T>
class Collector : public CollectorBase
{
public:
Collector(const std::string &name,
const std::string &help,
const std::vector<std::string> &labelNames)
: name_(name), help_(help), labelsNames_(labelNames)
{
}
template <typename... Arguments>
const std::shared_ptr<T> &metric(
const std::vector<std::string> &labelValues,
Arguments... args) noexcept(false)
{
if (labelValues.size() != labelsNames_.size())
{
throw std::runtime_error(
"The number of label values is not equal to the number of "
"label names!");
}
std::lock_guard<std::mutex> guard(mutex_);
auto iter = metrics_.find(labelValues);
if (iter != metrics_.end())
{
return iter->second;
}
auto metric =
std::make_shared<T>(name_, labelsNames_, labelValues, args...);
metrics_[labelValues] = metric;
return metrics_[labelValues];
}
std::vector<SamplesGroup> collect() const override
{
std::lock_guard<std::mutex> guard(mutex_);
std::vector<SamplesGroup> samples;
for (auto &pair : metrics_)
{
SamplesGroup samplesGroup;
auto &metric = pair.second;
samplesGroup.metric = metric;
auto metricSamples = metric->collect();
samplesGroup.samples = std::move(metricSamples);
samples.emplace_back(std::move(samplesGroup));
}
return samples;
}
const std::string &name() const override
{
return name_;
}
const std::string &help() const override
{
return help_;
}
const std::string_view type() const override
{
return T::type();
}
void registerTo(Registry &registry)
{
registry.registerCollector(shared_from_this());
}
const std::vector<std::string> &labelsNames() const
{
return labelsNames_;
}
private:
const std::string name_;
const std::string help_;
const std::vector<std::string> labelsNames_;
std::map<std::vector<std::string>, std::shared_ptr<T>> metrics_;
mutable std::mutex mutex_;
};
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,82 @@
/**
*
* Counter.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Metric.h>
#include <string_view>
#include <mutex>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a counter metric.
* */
class Counter : public Metric
{
public:
Counter(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues) noexcept(false)
: Metric(name, labelNames, labelValues)
{
}
std::vector<Sample> collect() const override
{
Sample s;
s.name = name_;
{
std::lock_guard<std::mutex> lock(mutex_);
s.value = value_;
}
return {s};
}
/**
* Increment the counter by 1.
* */
void increment()
{
std::lock_guard<std::mutex> lock(mutex_);
value_++;
}
/**
* Increment the counter by the given value.
* */
void increment(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ += value;
}
void reset()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ = 0;
}
static std::string_view type()
{
return "counter";
}
private:
mutable std::mutex mutex_;
double value_{0};
};
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,109 @@
/**
*
* Gauge.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Metric.h>
#include <string_view>
#include <atomic>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a gauge metric.
* */
class Gauge : public Metric
{
public:
/**
* Construct a gauge metric with a name and a help string.
* */
Gauge(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues) noexcept(false)
: Metric(name, labelNames, labelValues)
{
}
std::vector<Sample> collect() const override
{
Sample s;
std::lock_guard<std::mutex> lock(mutex_);
s.name = name_;
s.value = value_;
s.timestamp = timestamp_;
return {s};
}
/**
* Increment the counter by 1.
* */
void increment()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ += 1;
}
void decrement()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ -= 1;
}
void decrement(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ -= value;
}
/**
* Increment the counter by the given value.
* */
void increment(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ += value;
}
void reset()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ = 0;
}
void set(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ = value;
}
static std::string_view type()
{
return "gauge";
}
void setToCurrentTime()
{
std::lock_guard<std::mutex> lock(mutex_);
timestamp_ = trantor::Date::now();
}
private:
mutable std::mutex mutex_;
double value_{0};
trantor::Date timestamp_{0};
};
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,123 @@
/**
*
* Histogram.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/exports.h>
#include <drogon/utils/monitoring/Metric.h>
#include <trantor/net/EventLoopThread.h>
#include <string_view>
#include <atomic>
#include <mutex>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a counter metric.
* */
class DROGON_EXPORT Histogram : public Metric
{
public:
struct TimeBucket
{
std::vector<uint64_t> buckets;
uint64_t count{0};
double sum{0};
};
Histogram(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues,
const std::vector<double> &bucketBoundaries,
const std::chrono::duration<double> &maxAge,
uint64_t timeBucketsCount,
trantor::EventLoop *loop = nullptr) noexcept(false)
: Metric(name, labelNames, labelValues),
maxAge_(maxAge),
timeBucketCount_(timeBucketsCount),
bucketBoundaries_(bucketBoundaries)
{
if (loop == nullptr)
{
loopThreadPtr_ = std::make_unique<trantor::EventLoopThread>();
loopPtr_ = loopThreadPtr_->getLoop();
loopThreadPtr_->run();
}
else
{
loopPtr_ = loop;
}
if (maxAge > std::chrono::seconds(0))
{
if (timeBucketsCount == 0)
{
throw std::runtime_error(
"timeBucketsCount must be greater than 0");
}
}
timeBuckets_.emplace_back();
timeBuckets_.back().buckets.resize(bucketBoundaries.size() + 1);
// check the bucket boundaries are sorted
for (size_t i = 1; i < bucketBoundaries.size(); i++)
{
if (bucketBoundaries[i] <= bucketBoundaries[i - 1])
{
throw std::runtime_error(
"The bucket boundaries must be sorted");
}
}
}
void observe(double value);
std::vector<Sample> collect() const override;
~Histogram() override
{
if (timerId_ != trantor::InvalidTimerId)
{
loopPtr_->invalidateTimer(timerId_);
}
}
static std::string_view type()
{
return "histogram";
}
private:
std::deque<TimeBucket> timeBuckets_;
std::unique_ptr<trantor::EventLoopThread> loopThreadPtr_;
trantor::EventLoop *loopPtr_{nullptr};
mutable std::mutex mutex_;
std::chrono::duration<double> maxAge_;
trantor::TimerId timerId_{trantor::InvalidTimerId};
size_t timeBucketCount_{0};
const std::vector<double> bucketBoundaries_;
void rotateTimeBuckets()
{
std::lock_guard<std::mutex> guard(mutex_);
TimeBucket bucket;
bucket.buckets.resize(bucketBoundaries_.size() + 1);
timeBuckets_.emplace_back(std::move(bucket));
if (timeBuckets_.size() > timeBucketCount_)
{
auto expiredTimeBucket = timeBuckets_.front();
timeBuckets_.erase(timeBuckets_.begin());
}
}
};
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,77 @@
/**
*
* Metric.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Sample.h>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a metric.
* */
class Metric : public std::enable_shared_from_this<Metric>
{
public:
/**
* Construct a metric with a name and a help string.
* */
Metric(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues) noexcept(false)
: name_(name)
{
if (labelNames.size() != labelValues.size())
{
throw std::runtime_error(
"The number of label names is not equal to the number of label "
"values!");
}
labels_.resize(labelNames.size());
for (size_t i = 0; i < labelNames.size(); i++)
{
labels_[i].first = labelNames[i];
labels_[i].second = labelValues[i];
}
};
const std::string &name() const
{
return name_;
}
const std::vector<std::pair<std::string, std::string>> &labels() const
{
return labels_;
}
virtual ~Metric() = default;
virtual std::vector<Sample> collect() const = 0;
protected:
const std::string name_;
std::vector<std::pair<std::string, std::string>> labels_;
};
using MetricPtr = std::shared_ptr<Metric>;
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,35 @@
/**
*
* Registry.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 <memory>
namespace drogon
{
namespace monitoring
{
class CollectorBase;
/**
* This class is used to register metrics.
* */
class Registry
{
public:
virtual ~Registry() = default;
virtual void registerCollector(
const std::shared_ptr<CollectorBase> &collector) = 0;
};
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,35 @@
/**
*
* Sample.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Date.h>
#include <vector>
#include <string>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a metric.
* */
struct Sample
{
double value{0};
trantor::Date timestamp{0};
std::string name;
std::vector<std::pair<std::string, std::string>> exLabels;
};
} // namespace monitoring
} // namespace drogon
@@ -0,0 +1,76 @@
/**
*
* StopWatch.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 <chrono>
#include <functional>
#include <assert.h>
namespace drogon
{
/**
* @brief This class is used to measure the elapsed time.
*/
class StopWatch
{
public:
StopWatch() : start_(std::chrono::steady_clock::now())
{
}
~StopWatch()
{
}
/**
* @brief Reset the start time.
*/
void reset()
{
start_ = std::chrono::steady_clock::now();
}
/**
* @brief Get the elapsed time in seconds.
*/
double elapsed() const
{
return std::chrono::duration_cast<std::chrono::duration<double>>(
std::chrono::steady_clock::now() - start_)
.count();
}
private:
std::chrono::steady_clock::time_point start_;
};
class LifeTimeWatch
{
public:
LifeTimeWatch(std::function<void(double)> callback)
: stopWatch_(), callback_(std::move(callback))
{
assert(callback_);
}
~LifeTimeWatch()
{
callback_(stopWatch_.elapsed());
}
private:
StopWatch stopWatch_;
std::function<void(double)> callback_;
};
} // namespace drogon
+210
View File
@@ -0,0 +1,210 @@
/**
*
* AOPAdvice.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "AOPAdvice.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include <trantor/net/TcpConnection.h>
namespace drogon
{
static void doAdviceChain(
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>> &adviceChain,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr);
bool AopAdvice::passNewConnectionAdvices(
const trantor::TcpConnectionPtr &conn) const
{
for (auto &advice : newConnectionAdvices_)
{
if (!advice(conn->localAddr(), conn->peerAddr()))
{
return false;
}
}
return true;
}
void AopAdvice::passResponseCreationAdvices(const HttpResponsePtr &resp) const
{
if (!responseCreationAdvices_.empty())
{
for (auto &advice : responseCreationAdvices_)
{
advice(resp);
}
}
}
HttpResponsePtr AopAdvice::passSyncAdvices(const HttpRequestPtr &req) const
{
for (auto &advice : syncAdvices_)
{
if (auto resp = advice(req))
{
return resp;
}
}
return nullptr;
}
void AopAdvice::passPreRoutingObservers(const HttpRequestImplPtr &req) const
{
if (!preRoutingObservers_.empty())
{
for (auto &observer : preRoutingObservers_)
{
observer(req);
}
}
}
void AopAdvice::passPreRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (preRoutingAdvices_.empty())
{
callback(nullptr);
return;
}
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
{
if (!postRoutingObservers_.empty())
{
for (auto &observer : postRoutingObservers_)
{
observer(req);
}
}
}
void AopAdvice::passPostRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (postRoutingAdvices_.empty())
{
callback(nullptr);
return;
}
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
{
if (!preHandlingObservers_.empty())
{
for (auto &observer : preHandlingObservers_)
{
observer(req);
}
}
}
void AopAdvice::passPreHandlingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (preHandlingAdvices_.empty())
{
callback(nullptr);
return;
}
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPostHandlingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const
{
for (auto &advice : postHandlingAdvices_)
{
advice(req, resp);
}
}
void AopAdvice::passPreSendingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const
{
for (auto &advice : preSendingAdvices_)
{
advice(req, resp);
}
}
static void doAdviceChain(
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>> &adviceChain,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr)
{
if (index < adviceChain.size())
{
auto &advice = adviceChain[index];
advice(
req,
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
(*callbackPtr)(resp);
},
[index, req, callbackPtr, &adviceChain]() mutable {
auto ioLoop = req->getLoop();
if (ioLoop && !ioLoop->isInLoopThread())
{
ioLoop->queueInLoop([index,
req,
callbackPtr = std::move(callbackPtr),
&adviceChain]() mutable {
doAdviceChain(adviceChain,
index + 1,
req,
std::move(callbackPtr));
});
}
else
{
doAdviceChain(adviceChain,
index + 1,
req,
std::move(callbackPtr));
}
});
}
else
{
(*callbackPtr)(nullptr);
}
}
} // namespace drogon
+176
View File
@@ -0,0 +1,176 @@
/**
*
* AOPAdvice.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "impl_forwards.h"
#include <drogon/drogon_callbacks.h>
#include <trantor/net/InetAddress.h>
#include <functional>
#include <vector>
namespace drogon
{
class AopAdvice
{
public:
static AopAdvice &instance()
{
static AopAdvice inst;
return inst;
}
// Getters?
bool hasPreRoutingAdvices() const
{
return !preRoutingAdvices_.empty();
}
bool hasPostRoutingAdvices() const
{
return !postRoutingAdvices_.empty();
}
bool hasPreHandlingAdvices() const
{
return !preHandlingAdvices_.empty();
}
// Setters?
void registerNewConnectionAdvice(
std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)> advice)
{
newConnectionAdvices_.emplace_back(std::move(advice));
}
void registerHttpResponseCreationAdvice(
std::function<void(const HttpResponsePtr &)> advice)
{
responseCreationAdvices_.emplace_back(std::move(advice));
}
void registerSyncAdvice(
std::function<HttpResponsePtr(const HttpRequestPtr &)> advice)
{
syncAdvices_.emplace_back(std::move(advice));
}
void registerPreRoutingObserver(
std::function<void(const HttpRequestPtr &)> advice)
{
preRoutingObservers_.emplace_back(std::move(advice));
}
void registerPreRoutingAdvice(
std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> advice)
{
preRoutingAdvices_.emplace_back(std::move(advice));
}
void registerPostRoutingObserver(
std::function<void(const HttpRequestPtr &)> advice)
{
postRoutingObservers_.emplace_back(std::move(advice));
}
void registerPostRoutingAdvice(
std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> advice)
{
postRoutingAdvices_.emplace_back(std::move(advice));
}
void registerPreHandlingObserver(
std::function<void(const HttpRequestPtr &)> advice)
{
preHandlingObservers_.emplace_back(std::move(advice));
}
void registerPreHandlingAdvice(
std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> advice)
{
preHandlingAdvices_.emplace_back(std::move(advice));
}
void registerPostHandlingAdvice(
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>
advice)
{
postHandlingAdvices_.emplace_back(std::move(advice));
}
void registerPreSendingAdvice(
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>
advice)
{
preSendingAdvices_.emplace_back(std::move(advice));
}
// Executors
bool passNewConnectionAdvices(const trantor::TcpConnectionPtr &conn) const;
void passResponseCreationAdvices(const HttpResponsePtr &resp) const;
HttpResponsePtr passSyncAdvices(const HttpRequestPtr &req) const;
void passPreRoutingObservers(const HttpRequestImplPtr &req) const;
void passPreRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void passPostRoutingObservers(const HttpRequestImplPtr &req) const;
void passPostRoutingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void passPreHandlingObservers(const HttpRequestImplPtr &req) const;
void passPreHandlingAdvices(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void passPostHandlingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const;
void passPreSendingAdvices(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp) const;
private:
using SyncAdvice = std::function<HttpResponsePtr(const HttpRequestPtr &)>;
using SyncReqObserver = std::function<void(const HttpRequestPtr &)>;
using SyncObserver =
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>;
using AsyncAdvice = std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>;
// If we want to add aop functions anytime, we can add a mutex here
std::vector<std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)>>
newConnectionAdvices_;
std::vector<std::function<void(const HttpResponsePtr &)>>
responseCreationAdvices_;
std::vector<SyncAdvice> syncAdvices_;
std::vector<SyncReqObserver> preRoutingObservers_;
std::vector<AsyncAdvice> preRoutingAdvices_;
std::vector<SyncReqObserver> postRoutingObservers_;
std::vector<AsyncAdvice> postRoutingAdvices_;
std::vector<SyncReqObserver> preHandlingObservers_;
std::vector<AsyncAdvice> preHandlingAdvices_;
std::vector<SyncObserver> postHandlingAdvices_;
std::vector<SyncObserver> preSendingAdvices_;
};
} // namespace drogon
+661
View File
@@ -0,0 +1,661 @@
/**
*
* @file AccessLogger.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpUtils.h"
#include <drogon/drogon.h>
#include <drogon/plugins/AccessLogger.h>
#include <drogon/plugins/RealIpResolver.h>
#include <regex>
#include <thread>
#if !defined _WIN32 && !defined __HAIKU__
#include <unistd.h>
#include <sys/syscall.h>
#elif defined __HAIKU__
#include <unistd.h>
#else
#include <sstream>
#endif
#ifdef __FreeBSD__
#include <pthread_np.h>
#endif
#ifdef DROGON_SPDLOG_SUPPORT
#include <spdlog/spdlog.h>
#include <spdlog/logger.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#ifdef _WIN32
#include <spdlog/sinks/msvc_sink.h>
// Damn antedeluvian M$ macros
#undef min
#undef max
#endif
#ifndef _WIN32
#include <sys/wait.h>
#include <unistd.h>
#define os_access access
#elif !defined(_WIN32) || defined(__MINGW32__)
#include <sys/file.h>
#include <unistd.h>
#define os_access access
#else
#include <io.h>
#define os_access _waccess
#define R_OK 04
#define W_OK 02
#endif
#endif
using namespace drogon;
using namespace drogon::plugin;
bool AccessLogger::useRealIp_ = false;
void AccessLogger::initAndStart(const Json::Value &config)
{
useLocalTime_ = config.get("use_local_time", true).asBool();
showMicroseconds_ = config.get("show_microseconds", true).asBool();
timeFormat_ = config.get("custom_time_format", "").asString();
useCustomTimeFormat_ = !timeFormat_.empty();
useRealIp_ = config.get("use_real_ip", false).asBool();
logFunctionMap_ = {{"$request_path", outputReqPath},
{"$path", outputReqPath},
{"$date",
[this](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
outputDate(stream, req, resp);
}},
{"$request_date",
[this](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
outputReqDate(stream, req, resp);
}},
{"$request_query", outputReqQuery},
{"$request_url", outputReqURL},
{"$query", outputReqQuery},
{"$url", outputReqURL},
{"$request_version", outputVersion},
{"$version", outputVersion},
{"$request", outputReqLine},
{"$remote_addr", outputRemoteAddr},
{"$local_addr", outputLocalAddr},
{"$request_len", outputReqLength},
{"$body_bytes_received", outputReqLength},
{"$method", outputMethod},
{"$thread", outputThreadNumber},
{"$response_len", outputRespLength},
{"$body_bytes_sent", outputRespLength},
{"$status", outputStatusString},
{"$status_code", outputStatusCode},
{"$processing_time", outputProcessingTime},
{"$upstream_http_content-type", outputRespContentType},
{"$upstream_http_content_type", outputRespContentType}};
auto format = config.get("log_format", "").asString();
if (format.empty())
{
format =
"$request_date $method $url [$body_bytes_received] ($remote_addr - "
"$local_addr) $status $body_bytes_sent $processing_time";
}
createLogFunctions(format);
auto logPath = config.get("log_path", "").asString();
if (config.isMember("path_exempt"))
{
if (config["path_exempt"].isArray())
{
const auto &exempts = config["path_exempt"];
size_t exemptsCount = exempts.size();
if (exemptsCount)
{
std::string regexString;
size_t len = 0;
for (const auto &exempt : exempts)
{
assert(exempt.isString());
len += exempt.size();
}
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
const auto last = --exempts.end();
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
regexString.append("(")
.append(exempt->asString())
.append(")|");
regexString.append("(").append(last->asString()).append(")");
exemptRegex_ = std::regex(regexString);
regexFlag_ = true;
}
}
else if (config["path_exempt"].isString())
{
exemptRegex_ = std::regex(config["path_exempt"].asString());
regexFlag_ = true;
}
else
{
LOG_ERROR << "path_exempt must be a string or string array!";
}
}
#ifdef DROGON_SPDLOG_SUPPORT
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
config.get("use_spdlog", false).asBool();
if (logWithSpdlog)
{
logIndex_ = config.get("log_index", 0).asInt();
// Do nothing if already initialized...
if (!trantor::Logger::getSpdLogger(logIndex_))
{
trantor::Logger::enableSpdLog(logIndex_);
// Get the new logger & replace its sinks with the ones of the
// config
auto logger = trantor::Logger::getSpdLogger(logIndex_);
std::vector<spdlog::sink_ptr> sinks;
while (!logPath.empty())
{
// 1. check existence of folder or try to create it
auto fsLogPath =
std::filesystem::path(utils::toNativePath(logPath));
std::error_code fsErr;
if (!std::filesystem::create_directories(fsLogPath, fsErr) &&
fsErr)
{
LOG_ERROR << "could not create log file path";
break;
}
// 2. check if we have rights to create files in the folder
if (os_access(fsLogPath.native().c_str(), W_OK) != 0)
{
LOG_ERROR << "cannot create files in log folder";
break;
}
std::filesystem::path fileName(
config.get("log_file", "access.log").asString());
if (fileName.empty())
fileName = "access.log";
else
fileName.replace_extension(".log");
auto sizeLimit = config.get("log_size_limit", 0).asUInt64();
if (sizeLimit == 0)
sizeLimit = config.get("size_limit", 0).asUInt64();
if (sizeLimit == 0) // 0 is not allowed by this sink
sizeLimit = std::numeric_limits<std::size_t>::max();
std::size_t maxFiles = config.get("max_files", 0).asUInt();
sinks.push_back(
std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
(fsLogPath / fileName).string(),
sizeLimit,
// spdlog limitation
std::min(maxFiles, std::size_t(20000)),
false));
break;
}
if (sinks.empty())
sinks.push_back(
std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
#if defined(_WIN32) && defined(_DEBUG)
// On Windows with debug, it may be interesting to have the logs
// directly in the Visual Studio / WinDbg console
sinks.push_back(std::make_shared<spdlog::sinks::msvc_sink_mt>());
#endif
logger->sinks() = sinks;
// Override the pattern set in
// trantor::Logger::getDefaultSpdLogger() and let AccessLogger
// format the output
logger->set_pattern("%v");
}
}
else
#endif
if (!logPath.empty())
{
auto fileName = config.get("log_file", "access.log").asString();
auto extension = std::string(".log");
auto pos = fileName.rfind('.');
if (pos != std::string::npos)
{
extension = fileName.substr(pos);
fileName = fileName.substr(0, pos);
}
if (fileName.empty())
{
fileName = "access";
}
asyncFileLogger_.setFileName(fileName, extension, logPath);
asyncFileLogger_.startLogging();
logIndex_ = config.get("log_index", 0).asInt();
trantor::Logger::setOutputFunction(
[&](const char *msg, const uint64_t len) {
asyncFileLogger_.output(msg, len);
},
[&]() { asyncFileLogger_.flush(); },
logIndex_);
auto sizeLimit = config.get("log_size_limit", 0).asUInt64();
if (sizeLimit == 0)
{
// In earlier code, "size_limit" is taken instead of
// "log_size_limit" as it said in the comment in AccessLogger.h.
// In order to ensure backward compatibility we still take this
// field as a fallback.
sizeLimit = config.get("size_limit", 0).asUInt64();
}
if (sizeLimit > 0)
{
asyncFileLogger_.setFileSizeLimit(sizeLimit);
}
auto maxFiles = config.get("max_files", 0).asUInt();
asyncFileLogger_.setMaxFiles(maxFiles);
}
drogon::app().registerPreSendingAdvice(
[this](const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
if (regexFlag_)
{
if (!std::regex_match(req->path(), exemptRegex_))
{
logging(LOG_RAW_TO(logIndex_), req, resp);
}
}
else
{
logging(LOG_RAW_TO(logIndex_), req, resp);
}
});
}
void AccessLogger::shutdown()
{
}
void AccessLogger::logging(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp)
{
for (auto &func : logFunctions_)
{
func(stream, req, resp);
}
}
void AccessLogger::createLogFunctions(std::string format)
{
std::string rawString;
while (!format.empty())
{
LOG_TRACE << format;
auto pos = format.find('$');
if (pos != std::string::npos)
{
rawString += format.substr(0, pos);
format = format.substr(pos);
std::regex e{"^\\$[a-zA-Z0-9\\-_]+"};
std::smatch m;
if (std::regex_search(format, m, e))
{
if (!rawString.empty())
{
logFunctions_.emplace_back(
[rawString](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) {
stream << rawString;
});
rawString.clear();
}
auto placeholder = m[0];
logFunctions_.emplace_back(newLogFunction(placeholder));
format = m.suffix().str();
}
else
{
rawString += '$';
format = format.substr(1);
}
}
else
{
rawString += format;
break;
}
}
if (!rawString.empty())
{
logFunctions_.emplace_back(
[rawString =
std::move(rawString)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) {
stream << rawString << "\n";
});
}
else
{
logFunctions_.emplace_back(
[](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) { stream << "\n"; });
}
}
AccessLogger::LogFunction AccessLogger::newLogFunction(
const std::string &placeholder)
{
auto iter = logFunctionMap_.find(placeholder);
if (iter != logFunctionMap_.end())
{
return iter->second;
}
if (placeholder.find("$http_") == 0 && placeholder.size() > 6)
{
auto headerName = placeholder.substr(6);
return [headerName =
std::move(headerName)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &) {
outputReqHeader(stream, req, headerName);
};
}
if (placeholder.find("$cookie_") == 0 && placeholder.size() > 8)
{
auto cookieName = placeholder.substr(8);
return [cookieName =
std::move(cookieName)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &) {
outputReqCookie(stream, req, cookieName);
};
}
if (placeholder.find("$upstream_http_") == 0 && placeholder.size() > 15)
{
auto headerName = placeholder.substr(15);
return [headerName = std::move(
headerName)](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp) {
outputRespHeader(stream, resp, headerName);
};
}
return [placeholder](trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) {
stream << placeholder;
};
}
void AccessLogger::outputReqPath(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->path();
}
void AccessLogger::outputDate(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &) const
{
if (useCustomTimeFormat_)
{
if (useLocalTime_)
{
stream << trantor::Date::now().toCustomFormattedStringLocal(
timeFormat_, showMicroseconds_);
}
else
{
stream << trantor::Date::now().toCustomFormattedString(
timeFormat_, showMicroseconds_);
}
}
else
{
if (useLocalTime_)
{
stream << trantor::Date::now().toFormattedStringLocal(
showMicroseconds_);
}
else
{
stream << trantor::Date::now().toFormattedString(showMicroseconds_);
}
}
}
void AccessLogger::outputReqDate(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &) const
{
if (useCustomTimeFormat_)
{
if (useLocalTime_)
{
stream << req->creationDate().toCustomFormattedStringLocal(
timeFormat_, showMicroseconds_);
}
else
{
stream << req->creationDate().toCustomFormattedString(
timeFormat_, showMicroseconds_);
}
}
else
{
if (useLocalTime_)
{
stream << req->creationDate().toFormattedStringLocal(
showMicroseconds_);
}
else
{
stream << req->creationDate().toFormattedString(showMicroseconds_);
}
}
}
//$request_query
void AccessLogger::outputReqQuery(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->query();
}
//$request_url
void AccessLogger::outputReqURL(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
auto &query = req->query();
if (query.empty())
{
stream << req->path();
}
else
{
stream << req->path() << '?' << query;
}
}
//$request_version
void AccessLogger::outputVersion(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->versionString();
}
//$request
void AccessLogger::outputReqLine(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
auto &query = req->query();
if (query.empty())
{
stream << req->methodString() << " " << req->path() << " "
<< req->versionString();
}
else
{
stream << req->methodString() << " " << req->path() << '?' << query
<< " " << req->versionString();
}
}
void AccessLogger::outputRemoteAddr(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
if (useRealIp_)
{
stream << RealIpResolver::GetRealAddr(req).toIpPort();
}
else
{
stream << req->peerAddr().toIpPort();
}
}
void AccessLogger::outputLocalAddr(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->localAddr().toIpPort();
}
void AccessLogger::outputReqLength(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->body().length();
}
void AccessLogger::outputRespLength(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
stream << resp->body().length();
}
void AccessLogger::outputMethod(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
stream << req->methodString();
}
void AccessLogger::outputThreadNumber(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &)
{
#ifdef __linux__
static thread_local pid_t threadId_{0};
#else
static thread_local uint64_t threadId_{0};
#endif
#ifdef __linux__
if (threadId_ == 0)
threadId_ = static_cast<pid_t>(::syscall(SYS_gettid));
#elif defined __FreeBSD__
if (threadId_ == 0)
{
threadId_ = pthread_getthreadid_np();
}
#elif defined __OpenBSD__
if (threadId_ == 0)
{
threadId_ = getthrid();
}
#elif defined _WIN32 || defined __HAIKU__
if (threadId_ == 0)
{
std::stringstream ss;
ss << std::this_thread::get_id();
threadId_ = std::stoull(ss.str());
}
#else
if (threadId_ == 0)
{
pthread_threadid_np(NULL, &threadId_);
}
#endif
stream << threadId_;
}
//$http_[header_name]
void AccessLogger::outputReqHeader(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const std::string &headerName)
{
stream << headerName << ": " << req->getHeader(headerName);
}
//$cookie_[cookie_name]
void AccessLogger::outputReqCookie(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const std::string &cookie)
{
stream << "(cookie)" << cookie << "=" << req->getCookie(cookie);
}
//$upstream_http_[header_name]
void AccessLogger::outputRespHeader(trantor::LogStream &stream,
const drogon::HttpResponsePtr &resp,
const std::string &headerName)
{
stream << headerName << ": " << resp->getHeader(headerName);
}
//$status
void AccessLogger::outputStatusString(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
int code = resp->getStatusCode();
stream << code << " " << statusCodeToString(code);
}
//$status_code
void AccessLogger::outputStatusCode(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
stream << resp->getStatusCode();
}
//$processing_time
void AccessLogger::outputProcessingTime(trantor::LogStream &stream,
const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &)
{
auto start = req->creationDate();
auto end = trantor::Date::now();
auto duration =
end.microSecondsSinceEpoch() - start.microSecondsSinceEpoch();
auto seconds = static_cast<double>(duration) / 1000000.0;
stream << seconds;
}
//$upstream_http_content-type $upstream_http_content_type
void AccessLogger::outputRespContentType(trantor::LogStream &stream,
const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp)
{
stream << resp->contentTypeString();
}
+107
View File
@@ -0,0 +1,107 @@
/**
*
* CacheFile.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "CacheFile.h"
#include <trantor/utils/Logger.h>
#ifdef _WIN32
#include <mman.h>
#include <drogon/utils/Utilities.h>
#else
#include <unistd.h>
#include <sys/mman.h>
#endif
using namespace drogon;
CacheFile::CacheFile(const std::string &path, bool autoDelete)
: autoDelete_(autoDelete), path_(path)
{
#ifndef _MSC_VER
file_ = fopen(path_.data(), "wb+");
#else
auto wPath{drogon::utils::toNativePath(path)};
if (_wfopen_s(&file_, wPath.c_str(), L"wb+") != 0)
{
file_ = nullptr;
}
#endif
if (!file_)
LOG_SYSERR << "CacheFile fopen:";
}
CacheFile::~CacheFile()
{
if (data_)
{
munmap(data_, dataLength_);
}
if (autoDelete_ && file_)
{
fclose(file_);
#if defined(_WIN32) && !defined(__MINGW32__)
auto wPath{drogon::utils::toNativePath(path_)};
_wunlink(wPath.c_str());
#else
unlink(path_.data());
#endif
}
else if (file_)
{
fclose(file_);
}
}
void CacheFile::append(const char *data, size_t length)
{
if (file_)
{
if (!fwrite(data, length, 1, file_))
LOG_SYSERR << "CacheFile append:";
}
}
size_t CacheFile::length()
{
if (file_)
#ifdef _WIN32
return _ftelli64(file_);
#else
return ftell(file_);
#endif
return 0;
}
char *CacheFile::data()
{
if (!file_)
return nullptr;
if (!data_)
{
fflush(file_);
#ifdef _WIN32
auto fd = _fileno(file_);
#else
auto fd = fileno(file_);
#endif
dataLength_ = length();
data_ = static_cast<char *>(
mmap(nullptr, dataLength_, PROT_READ, MAP_SHARED, fd, 0));
if (data_ == MAP_FAILED)
{
data_ = nullptr;
LOG_SYSERR << "CacheFile mmap:";
}
}
return data_;
}
+53
View File
@@ -0,0 +1,53 @@
/**
*
* CacheFile.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/NonCopyable.h>
#include <string>
#include <string_view>
#include <stdio.h>
namespace drogon
{
class CacheFile : public trantor::NonCopyable
{
public:
explicit CacheFile(const std::string &path, bool autoDelete = true);
~CacheFile();
void append(const std::string &data)
{
append(data.data(), data.length());
}
void append(const char *data, size_t length);
std::string_view getStringView()
{
if (data())
return std::string_view(data_, dataLength_);
return std::string_view();
}
private:
char *data();
size_t length();
FILE *file_{nullptr};
bool autoDelete_{true};
const std::string path_;
char *data_{nullptr};
size_t dataLength_{0};
};
} // namespace drogon
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <json/json.h>
#include <vector>
#include <string>
#include <memory>
#include <fstream>
namespace drogon
{
class ConfigAdapter
{
public:
virtual ~ConfigAdapter() = default;
virtual Json::Value getJson(const std::string &content) const
noexcept(false) = 0;
virtual std::vector<std::string> getExtensions() const = 0;
};
using ConfigAdapterPtr = std::shared_ptr<ConfigAdapter>;
} // namespace drogon
+40
View File
@@ -0,0 +1,40 @@
#include "ConfigAdapterManager.h"
#include "JsonConfigAdapter.h"
#include "YamlConfigAdapter.h"
#include <algorithm>
using namespace drogon;
#define REGISTER_CONFIG_ADAPTER(adapter) \
{ \
auto adapterPtr = std::make_shared<adapter>(); \
auto exts = adapterPtr->getExtensions(); \
for (auto ext : exts) \
{ \
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); \
adapters_[ext] = adapterPtr; \
} \
}
ConfigAdapterManager &ConfigAdapterManager::instance()
{
static ConfigAdapterManager instance;
return instance;
}
Json::Value ConfigAdapterManager::getJson(const std::string &content,
std::string ext) const noexcept(false)
{
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
auto it = adapters_.find(ext);
if (it == adapters_.end())
{
throw std::runtime_error("No valid parser for this config file!");
}
return it->second->getJson(content);
}
ConfigAdapterManager::ConfigAdapterManager()
{
REGISTER_CONFIG_ADAPTER(JsonConfigAdapter);
REGISTER_CONFIG_ADAPTER(YamlConfigAdapter);
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "ConfigAdapterManager.h"
#include "ConfigAdapter.h"
#include <map>
namespace drogon
{
class ConfigAdapterManager
{
public:
static ConfigAdapterManager &instance();
Json::Value getJson(const std::string &content, std::string ext) const
noexcept(false);
private:
ConfigAdapterManager();
std::map<std::string, ConfigAdapterPtr> adapters_;
};
} // namespace drogon
+709
View File
@@ -0,0 +1,709 @@
/**
*
* @file ConfigLoader.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "ConfigLoader.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/config.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <thread>
#include <trantor/utils/Logger.h>
#if !defined(_WIN32)
#include <unistd.h>
#define os_access access
#else
#include <io.h>
#ifndef __MINGW32__
#define os_access _waccess
#define R_OK 04
#define W_OK 02
#else
#define os_access access
#endif
#endif
#include <drogon/utils/Utilities.h>
#include "ConfigAdapterManager.h"
#include <filesystem>
using namespace drogon;
static bool bytesSize(std::string &sizeStr, size_t &size)
{
if (sizeStr.empty())
{
size = -1;
return true;
}
else
{
size = 1;
switch (sizeStr[sizeStr.length() - 1])
{
case 'k':
case 'K':
size = 1024;
sizeStr.resize(sizeStr.length() - 1);
break;
case 'M':
case 'm':
size = (1024 * 1024);
sizeStr.resize(sizeStr.length() - 1);
break;
case 'g':
case 'G':
size = (1024 * 1024 * 1024);
sizeStr.resize(sizeStr.length() - 1);
break;
#if ((ULONG_MAX) != (UINT_MAX))
// 64bit system
case 't':
case 'T':
size = (1024L * 1024L * 1024L * 1024L);
sizeStr.resize(sizeStr.length() - 1);
break;
#endif
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '7':
case '8':
case '9':
break;
default:
return false;
break;
}
std::istringstream iss(sizeStr);
size_t tmpSize;
iss >> tmpSize;
if (iss.fail())
{
return false;
}
if ((size_t(-1) / tmpSize) >= size)
size *= tmpSize;
else
{
size = -1;
}
return true;
}
}
ConfigLoader::ConfigLoader(const std::string &configFile)
{
if (os_access(drogon::utils::toNativePath(configFile).c_str(), 0) != 0)
{
throw std::runtime_error("Config file " + configFile + " not found!");
}
if (os_access(drogon::utils::toNativePath(configFile).c_str(), R_OK) != 0)
{
throw std::runtime_error("No permission to read config file " +
configFile);
}
configFile_ = configFile;
auto pos = configFile.find_last_of('.');
if (pos == std::string::npos)
{
throw std::runtime_error("Invalid config file name!");
}
auto ext = configFile.substr(pos + 1);
std::ifstream infile(drogon::utils::toNativePath(configFile).c_str(),
std::ifstream::in);
// get the content of the infile
std::string content((std::istreambuf_iterator<char>(infile)),
std::istreambuf_iterator<char>());
try
{
configJsonRoot_ =
ConfigAdapterManager::instance().getJson(content, std::move(ext));
}
catch (std::exception &e)
{
throw std::runtime_error("Error reading config file " + configFile +
": " + e.what());
}
}
ConfigLoader::ConfigLoader(const Json::Value &data) : configJsonRoot_(data)
{
}
ConfigLoader::ConfigLoader(Json::Value &&data)
: configJsonRoot_(std::move(data))
{
}
ConfigLoader::~ConfigLoader()
{
}
static void loadLogSetting(const Json::Value &log)
{
if (!log)
return;
auto useSpdlog = log.get("use_spdlog", false).asBool();
auto logPath = log.get("log_path", "").asString();
auto baseName = log.get("logfile_base_name", "").asString();
auto logSize = log.get("log_size_limit", 100000000).asUInt64();
auto maxFiles = log.get("max_files", 0).asUInt();
HttpAppFrameworkImpl::instance().setLogPath(
logPath, baseName, logSize, maxFiles, useSpdlog);
auto logLevel = log.get("log_level", "DEBUG").asString();
if (logLevel == "TRACE")
{
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
}
else if (logLevel == "DEBUG")
{
trantor::Logger::setLogLevel(trantor::Logger::kDebug);
}
else if (logLevel == "INFO")
{
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
}
else if (logLevel == "WARN")
{
trantor::Logger::setLogLevel(trantor::Logger::kWarn);
}
auto localTime = log.get("display_local_time", false).asBool();
trantor::Logger::setDisplayLocalTime(localTime);
}
static void loadControllers(const Json::Value &controllers)
{
if (!controllers)
return;
for (auto const &controller : controllers)
{
auto path = controller.get("path", "").asString();
auto ctrlName = controller.get("controller", "").asString();
if (path == "" || ctrlName == "")
continue;
std::vector<internal::HttpConstraint> constraints;
if (!controller["http_methods"].isNull())
{
for (auto const &method : controller["http_methods"])
{
auto strMethod = method.asString();
std::transform(strMethod.begin(),
strMethod.end(),
strMethod.begin(),
[](unsigned char c) { return tolower(c); });
if (strMethod == "get")
{
constraints.push_back(Get);
}
else if (strMethod == "post")
{
constraints.push_back(Post);
}
else if (strMethod == "head") // The branch never work
{
constraints.push_back(Head);
}
else if (strMethod == "put")
{
constraints.push_back(Put);
}
else if (strMethod == "delete")
{
constraints.push_back(Delete);
}
else if (strMethod == "patch")
{
constraints.push_back(Patch);
}
}
}
if (!controller["filters"].isNull())
{
for (auto const &filter : controller["filters"])
{
constraints.push_back(filter.asString());
}
}
drogon::app().registerHttpSimpleController(path, ctrlName, constraints);
}
}
static void loadApp(const Json::Value &app)
{
if (!app)
return;
// threads number
auto threadsNum = app.get("threads_num", 1).asUInt64();
if (threadsNum == 1)
{
threadsNum = app.get("number_of_threads", 1).asUInt64();
}
if (threadsNum == 0)
{
// set the number to the number of processors.
threadsNum = std::thread::hardware_concurrency();
LOG_TRACE << "The number of processors is " << threadsNum;
}
if (threadsNum < 1)
threadsNum = 1;
drogon::app().setThreadNum(threadsNum);
// session
auto enableSession = app.get("enable_session", false).asBool();
if (enableSession)
{
auto timeout = app.get("session_timeout", 0).asUInt64();
auto sameSite = app.get("session_same_site", "Null").asString();
auto cookieKey = app.get("session_cookie_key", "JSESSIONID").asString();
auto maxAge = app.get("session_max_age", -1).asInt();
drogon::app().enableSession(timeout,
Cookie::convertString2SameSite(sameSite),
cookieKey,
maxAge);
}
else
drogon::app().disableSession();
// document root
auto documentRoot = app.get("document_root", "").asString();
if (documentRoot != "")
{
drogon::app().setDocumentRoot(documentRoot);
}
if (!app["static_file_headers"].empty())
{
if (app["static_file_headers"].isArray())
{
std::vector<std::pair<std::string, std::string>> headers;
for (auto &header : app["static_file_headers"])
{
headers.emplace_back(
std::make_pair(header["name"].asString(),
header["value"].asString()));
}
drogon::app().setStaticFileHeaders(headers);
}
else
{
throw std::runtime_error(
"The static_file_headers option must be an array");
}
}
// upload path
auto uploadPath = app.get("upload_path", "uploads").asString();
drogon::app().setUploadPath(uploadPath);
// file types
auto fileTypes = app["file_types"];
if (fileTypes.isArray() && !fileTypes.empty())
{
std::vector<std::string> types;
for (auto const &fileType : fileTypes)
{
types.push_back(fileType.asString());
LOG_TRACE << "file type:" << types.back();
}
drogon::app().setFileTypes(types);
}
// locations
if (app.isMember("locations"))
{
auto &locations = app["locations"];
if (!locations.isArray())
{
throw std::runtime_error("The locations option must be an array");
}
for (auto &location : locations)
{
auto uri = location.get("uri_prefix", "").asString();
if (uri.empty())
continue;
auto defaultContentType =
location.get("default_content_type", "").asString();
auto alias = location.get("alias", "").asString();
auto isCaseSensitive =
location.get("is_case_sensitive", false).asBool();
auto allAll = location.get("allow_all", true).asBool();
auto isRecursive = location.get("is_recursive", true).asBool();
if (!location["filters"].isNull())
{
if (location["filters"].isArray())
{
std::vector<std::string> filters;
for (auto const &filter : location["filters"])
{
filters.push_back(filter.asString());
}
drogon::app().addALocation(uri,
defaultContentType,
alias,
isCaseSensitive,
allAll,
isRecursive,
filters);
}
else
{
throw std::runtime_error("the filters of location '" + uri +
"' should be an array");
}
}
else
{
drogon::app().addALocation(uri,
defaultContentType,
alias,
isCaseSensitive,
allAll,
isRecursive);
}
}
}
// max connections
auto maxConns = app.get("max_connections", 0).asUInt64();
if (maxConns > 0)
{
drogon::app().setMaxConnectionNum(maxConns);
}
// max connections per IP
auto maxConnsPerIP = app.get("max_connections_per_ip", 0).asUInt64();
if (maxConnsPerIP > 0)
{
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
}
#if !defined(_WIN32) && !TARGET_OS_IOS
// dynamic views
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
if (enableDynamicViews)
{
auto viewsPaths = app["dynamic_views_path"];
if (viewsPaths.isArray() && viewsPaths.size() > 0)
{
std::vector<std::string> paths;
for (auto const &viewsPath : viewsPaths)
{
paths.push_back(viewsPath.asString());
LOG_TRACE << "views path:" << paths.back();
}
auto outputPath =
app.get("dynamic_views_output_path", "").asString();
drogon::app().enableDynamicViewsLoading(paths, outputPath);
}
}
#endif
auto stackLimit = app.get("json_parser_stack_limit", 1000).asUInt64();
drogon::app().setJsonParserStackLimit(stackLimit);
auto unicodeEscaping =
app.get("enable_unicode_escaping_in_json", true).asBool();
drogon::app().setUnicodeEscapingInJson(unicodeEscaping);
auto &precision = app["float_precision_in_json"];
if (!precision.isNull())
{
auto precisionLength = precision.get("precision", 0).asUInt64();
auto precisionType =
precision.get("precision_type", "significant").asString();
drogon::app().setFloatPrecisionInJson((unsigned int)precisionLength,
precisionType);
}
// log
loadLogSetting(app["log"]);
// run as daemon
auto runAsDaemon = app.get("run_as_daemon", false).asBool();
if (runAsDaemon)
{
drogon::app().enableRunAsDaemon();
}
// handle SIGTERM
auto handleSigterm = app.get("handle_sig_term", true).asBool();
if (!handleSigterm)
{
drogon::app().disableSigtermHandling();
}
// relaunch
auto relaunch = app.get("relaunch_on_error", false).asBool();
if (relaunch)
{
drogon::app().enableRelaunchOnError();
}
auto useSendfile = app.get("use_sendfile", true).asBool();
drogon::app().enableSendfile(useSendfile);
auto useGzip = app.get("use_gzip", true).asBool();
drogon::app().enableGzip(useGzip);
auto useBr = app.get("use_brotli", false).asBool();
drogon::app().enableBrotli(useBr);
auto staticFilesCacheTime = app.get("static_files_cache_time", 5).asInt();
drogon::app().setStaticFilesCacheTime(staticFilesCacheTime);
loadControllers(app["simple_controllers_map"]);
// Kick off idle connections
auto kickOffTimeout = app.get("idle_connection_timeout", 60).asUInt64();
drogon::app().setIdleConnectionTimeout(kickOffTimeout);
auto server = app.get("server_header_field", "").asString();
if (!server.empty())
drogon::app().setServerHeaderField(server);
auto sendServerHeader = app.get("enable_server_header", true).asBool();
drogon::app().enableServerHeader(sendServerHeader);
auto sendDateHeader = app.get("enable_date_header", true).asBool();
drogon::app().enableDateHeader(sendDateHeader);
auto keepaliveReqs = app.get("keepalive_requests", 0).asUInt64();
drogon::app().setKeepaliveRequestsNumber(keepaliveReqs);
auto pipeliningReqs = app.get("pipelining_requests", 0).asUInt64();
drogon::app().setPipeliningRequestsNumber(pipeliningReqs);
auto useGzipStatic = app.get("gzip_static", true).asBool();
drogon::app().setGzipStatic(useGzipStatic);
auto useBrStatic = app.get("br_static", true).asBool();
drogon::app().setBrStatic(useBrStatic);
auto maxBodySize = app.get("client_max_body_size", "1M").asString();
size_t size;
if (bytesSize(maxBodySize, size))
{
drogon::app().setClientMaxBodySize(size);
}
else
{
throw std::runtime_error("Error format of client_max_body_size");
}
auto maxMemoryBodySize =
app.get("client_max_memory_body_size", "64K").asString();
if (bytesSize(maxMemoryBodySize, size))
{
drogon::app().setClientMaxMemoryBodySize(size);
}
else
{
throw std::runtime_error("Error format of client_max_memory_body_size");
}
auto maxWsMsgSize =
app.get("client_max_websocket_message_size", "128K").asString();
if (bytesSize(maxWsMsgSize, size))
{
drogon::app().setClientMaxWebSocketMessageSize(size);
}
else
{
throw std::runtime_error(
"Error format of client_max_websocket_message_size");
}
drogon::app().enableReusePort(app.get("reuse_port", false).asBool());
drogon::app().setHomePage(app.get("home_page", "index.html").asString());
drogon::app().setImplicitPageEnable(
app.get("use_implicit_page", true).asBool());
drogon::app().setImplicitPage(
app.get("implicit_page", "index.html").asString());
auto mimes = app["mime"];
if (!mimes.isNull())
{
auto names = mimes.getMemberNames();
for (const auto &mime : names)
{
auto ext = mimes[mime];
std::vector<std::string> exts;
if (ext.isString())
exts.push_back(ext.asString());
else if (ext.isArray())
{
for (const auto &extension : ext)
exts.push_back(extension.asString());
}
for (const auto &extension : exts)
drogon::app().registerCustomExtensionMime(extension, mime);
}
}
bool enableCompressedRequests =
app.get("enabled_compressed_request", false).asBool();
drogon::app().enableCompressedRequest(enableCompressedRequests);
drogon::app().enableRequestStream(
app.get("enable_request_stream", false).asBool());
}
static void loadDbClients(const Json::Value &dbClients)
{
if (!dbClients)
return;
for (auto const &client : dbClients)
{
auto type = client.get("rdbms", "postgresql").asString();
std::transform(type.begin(),
type.end(),
type.begin(),
[](unsigned char c) { return tolower(c); });
auto host = client.get("host", "127.0.0.1").asString();
unsigned short port = client.get("port", 5432).asUInt();
auto dbname = client.get("dbname", "").asString();
if (dbname.empty() && type != "sqlite3")
{
throw std::runtime_error(
"Please configure dbname in the configuration file");
}
auto user = client.get("user", "postgres").asString();
auto password = client.get("passwd", "").asString();
if (password.empty())
{
password = client.get("password", "").asString();
}
auto connNum = client.get("connection_number", 1).asUInt();
if (connNum == 1)
{
connNum = client.get("number_of_connections", 1).asUInt();
}
auto name = client.get("name", "default").asString();
auto filename = client.get("filename", "").asString();
auto isFast = client.get("is_fast", false).asBool();
auto characterSet = client.get("characterSet", "").asString();
if (characterSet.empty())
{
characterSet = client.get("client_encoding", "").asString();
}
auto connectOptions = client.get("connect_options", Json::Value());
auto timeout = client.get("timeout", -1.0).asDouble();
auto autoBatch = client.get("auto_batch", false).asBool();
std::unordered_map<std::string, std::string> options;
if (connectOptions.isObject() && !connectOptions.empty())
{
for (const auto &key : connectOptions.getMemberNames())
{
options[key] = connectOptions[key].asString();
}
}
HttpAppFrameworkImpl::instance().addDbClient(type,
host,
port,
dbname,
user,
password,
connNum,
filename,
name,
isFast,
characterSet,
timeout,
autoBatch,
std::move(options));
}
}
static void loadRedisClients(const Json::Value &redisClients)
{
if (!redisClients)
return;
for (auto const &client : redisClients)
{
std::promise<std::string> promise;
auto future = promise.get_future();
auto host = client.get("host", "127.0.0.1").asString();
trantor::Resolver::newResolver()->resolve(
host, [&promise](const trantor::InetAddress &address) {
promise.set_value(address.toIp());
});
auto port = client.get("port", 6379).asUInt();
auto username = client.get("username", "").asString();
auto password = client.get("passwd", "").asString();
if (password.empty())
{
password = client.get("password", "").asString();
}
auto connNum = client.get("connection_number", 1).asUInt();
if (connNum == 1)
{
connNum = client.get("number_of_connections", 1).asUInt();
}
auto name = client.get("name", "default").asString();
auto isFast = client.get("is_fast", false).asBool();
auto timeout = client.get("timeout", -1.0).asDouble();
auto db = client.get("db", 0).asUInt();
auto hostIp = future.get();
drogon::app().createRedisClient(hostIp,
port,
name,
password,
connNum,
isFast,
timeout,
db,
username);
}
}
static void loadListeners(const Json::Value &listeners)
{
if (!listeners)
return;
LOG_TRACE << "Has " << listeners.size() << " listeners";
for (auto const &listener : listeners)
{
auto addr = listener.get("address", "0.0.0.0").asString();
auto port = (uint16_t)listener.get("port", 0).asUInt();
auto useSSL = listener.get("https", false).asBool();
auto cert = listener.get("cert", "").asString();
auto key = listener.get("key", "").asString();
auto useOldTLS = listener.get("use_old_tls", false).asBool();
std::vector<std::pair<std::string, std::string>> sslConfCmds;
if (listener.isMember("ssl_conf"))
{
for (const auto &opt : listener["ssl_conf"])
{
if (opt.size() == 0 || opt.size() > 2)
{
LOG_FATAL << "SSL configuration option should be an 1 or "
"2-element array";
abort();
}
sslConfCmds.emplace_back(opt[0].asString(),
opt.get(1, "").asString());
}
}
LOG_TRACE << "Add listener:" << addr << ":" << port;
drogon::app().addListener(
addr, port, useSSL, cert, key, useOldTLS, sslConfCmds);
}
}
static void loadSSL(const Json::Value &sslConf)
{
if (!sslConf)
return;
auto key = sslConf.get("key", "").asString();
auto cert = sslConf.get("cert", "").asString();
drogon::app().setSSLFiles(cert, key);
std::vector<std::pair<std::string, std::string>> sslConfCmds;
if (sslConf.isMember("conf"))
{
for (const auto &opt : sslConf["conf"])
{
if (opt.size() == 0 || opt.size() > 2)
{
LOG_FATAL << "SSL configuration option should be an 1 or "
"2-element array";
abort();
}
sslConfCmds.emplace_back(opt[0].asString(),
opt.get(1, "").asString());
}
}
drogon::app().setSSLConfigCommands(sslConfCmds);
}
void ConfigLoader::load()
{
// std::cout<<configJsonRoot_<<std::endl;
loadApp(configJsonRoot_["app"]);
loadSSL(configJsonRoot_["ssl"]);
loadListeners(configJsonRoot_["listeners"]);
loadDbClients(configJsonRoot_["db_clients"]);
loadRedisClients(configJsonRoot_["redis_clients"]);
}
+42
View File
@@ -0,0 +1,42 @@
/**
*
* ConfigLoader.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <json/json.h>
#include <string>
#include <trantor/utils/NonCopyable.h>
namespace drogon
{
class ConfigLoader : public trantor::NonCopyable
{
public:
explicit ConfigLoader(const std::string &configFile) noexcept(false);
explicit ConfigLoader(const Json::Value &data);
explicit ConfigLoader(Json::Value &&data);
~ConfigLoader();
const Json::Value &jsonValue() const
{
return configJsonRoot_;
}
void load() noexcept(false);
private:
std::string configFile_;
Json::Value configJsonRoot_;
};
} // namespace drogon
+63
View File
@@ -0,0 +1,63 @@
/**
*
* @file ControllerBinderBase.h
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <drogon/IOThreadStorage.h>
#include <drogon/HttpResponse.h>
#include "HttpRequestImpl.h"
namespace drogon
{
class HttpMiddlewareBase;
/**
* @brief A component to associate router class and controller class
*/
struct ControllerBinderBase
{
std::string handlerName_;
std::vector<std::string> middlewareNames_;
std::vector<std::shared_ptr<HttpMiddlewareBase>> middlewares_;
IOThreadStorage<HttpResponsePtr> responseCache_;
std::shared_ptr<std::string> corsMethods_;
bool isCORS_{false};
virtual ~ControllerBinderBase() = default;
virtual void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const = 0;
virtual bool isStreamHandler() const
{
return false;
}
};
struct RouteResult
{
enum
{
Success,
MethodNotAllowed,
NotFound
} result;
std::shared_ptr<ControllerBinderBase> binderPtr;
};
} // namespace drogon
+87
View File
@@ -0,0 +1,87 @@
/**
*
* Cookie.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/Cookie.h>
#include <drogon/utils/Utilities.h>
#include <trantor/utils/Logger.h>
using namespace drogon;
std::string Cookie::cookieString() const
{
constexpr std::string_view prefix = "Set-Cookie: ";
std::string ret;
// reserve space to reduce frequency allocation
ret.reserve(prefix.size() + key_.size() + value_.size() + 30);
ret = prefix;
ret.append(key_).append("=").append(value_).append("; ");
if (expiresDate_.microSecondsSinceEpoch() !=
(std::numeric_limits<int64_t>::max)() &&
expiresDate_.microSecondsSinceEpoch() >= 0)
{
ret.append("Expires=")
.append(utils::getHttpFullDateStr(expiresDate_))
.append("; ");
}
if (maxAge_.has_value())
{
ret.append("Max-Age=")
.append(std::to_string(maxAge_.value()))
.append("; ");
}
if (!domain_.empty())
{
ret.append("Domain=").append(domain_).append("; ");
}
if (!path_.empty())
{
ret.append("Path=").append(path_).append("; ");
}
if (sameSite_ != SameSite::kNull)
{
switch (sameSite_)
{
case SameSite::kLax:
ret.append("SameSite=Lax; ");
break;
case SameSite::kStrict:
ret.append("SameSite=Strict; ");
break;
case SameSite::kNone:
ret.append("SameSite=None; ");
// Cookies with SameSite=None must now also specify the Secure
// attribute (they require a secure context/HTTPS).
ret.append("Secure; ");
break;
default:
// Lax replaced None as the default value to ensure that users
// have reasonably robust defense against some CSRF attacks
ret.append("SameSite=Lax; ");
}
}
if ((secure_ && sameSite_ != SameSite::kNone) || partitioned_)
{
ret.append("Secure; ");
}
if (httpOnly_)
{
ret.append("HttpOnly; ");
}
if (partitioned_)
{
ret.append("Partitioned; ");
}
ret.resize(ret.length() - 2); // delete last semicolon
ret.append("\r\n");
return ret;
}
+66
View File
@@ -0,0 +1,66 @@
/**
*
* @file DbClientManager.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/orm/DbClient.h>
#include <drogon/orm/DbConfig.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/IOThreadStorage.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <string>
#include <memory>
namespace drogon
{
namespace orm
{
class DbClientManager : public trantor::NonCopyable
{
public:
void createDbClients(const std::vector<trantor::EventLoop *> &ioLoops);
DbClientPtr getDbClient(const std::string &name)
{
assert(dbClientsMap_.find(name) != dbClientsMap_.end());
return dbClientsMap_[name];
}
~DbClientManager();
DbClientPtr getFastDbClient(const std::string &name)
{
auto iter = dbFastClientsMap_.find(name);
assert(iter != dbFastClientsMap_.end());
return iter->second.getThreadData();
}
void addDbClient(const DbConfig &config);
bool areAllDbClientsAvailable() const noexcept;
private:
std::map<std::string, DbClientPtr> dbClientsMap_;
struct DbInfo
{
std::string connectionInfo_;
DbConfig config_;
};
std::vector<DbInfo> dbInfos_;
std::map<std::string, IOThreadStorage<orm::DbClientPtr>> dbFastClientsMap_;
};
} // namespace orm
} // namespace drogon
@@ -0,0 +1,44 @@
/**
*
* DbClientManagerSkipped.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "DbClientManager.h"
#include <algorithm>
#include <stdlib.h>
using namespace drogon::orm;
using namespace drogon;
void DbClientManager::createDbClients(
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
{
return;
}
void DbClientManager::addDbClient(const DbConfig &)
{
LOG_FATAL << "No database is supported by drogon, please install the "
"database development library first.";
abort();
}
bool DbClientManager::areAllDbClientsAvailable() const noexcept
{
LOG_FATAL << "No database is supported by drogon, please install the "
"database development library first.";
abort();
}
DbClientManager::~DbClientManager()
{
}
+122
View File
@@ -0,0 +1,122 @@
/**
*
* DrClassMap.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/DrClassMap.h>
#include <drogon/DrObject.h>
#include <trantor/utils/Logger.h>
using namespace drogon;
namespace drogon
{
namespace internal
{
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>> &
getObjsMap()
{
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>>
singleInstanceMap;
return singleInstanceMap;
}
static std::mutex &getMapMutex()
{
static std::mutex mtx;
return mtx;
}
} // namespace internal
} // namespace drogon
void DrClassMap::registerClass(const std::string &className,
const DrAllocFunc &func,
const DrSharedAllocFunc &sharedFunc)
{
LOG_TRACE << "Register class:" << className;
getMap().insert(
std::make_pair(className, std::make_pair(func, sharedFunc)));
}
DrObjectBase *DrClassMap::newObject(const std::string &className)
{
auto iter = getMap().find(className);
if (iter != getMap().end())
{
return iter->second.first();
}
else
return nullptr;
}
std::shared_ptr<DrObjectBase> DrClassMap::newSharedObject(
const std::string &className)
{
auto iter = getMap().find(className);
if (iter != getMap().end())
{
if (iter->second.second)
return iter->second.second();
else
return std::shared_ptr<DrObjectBase>(iter->second.first());
}
else
return nullptr;
}
const std::shared_ptr<DrObjectBase> &DrClassMap::getSingleInstance(
const std::string &className)
{
auto &mtx = internal::getMapMutex();
auto &singleInstanceMap = internal::getObjsMap();
{
std::lock_guard<std::mutex> lock(mtx);
auto iter = singleInstanceMap.find(className);
if (iter != singleInstanceMap.end())
return iter->second;
}
auto newObj = newSharedObject(className);
{
std::lock_guard<std::mutex> lock(mtx);
auto ret = singleInstanceMap.insert(
std::make_pair(className, std::move(newObj)));
return ret.first->second;
}
}
void DrClassMap::setSingleInstance(const std::shared_ptr<DrObjectBase> &ins)
{
auto &mtx = internal::getMapMutex();
auto &singleInstanceMap = internal::getObjsMap();
std::lock_guard<std::mutex> lock(mtx);
singleInstanceMap[ins->className()] = ins;
}
std::vector<std::string> DrClassMap::getAllClassName()
{
std::vector<std::string> ret;
for (auto const &iter : getMap())
{
ret.push_back(iter.first);
}
return ret;
}
std::unordered_map<std::string, std::pair<DrAllocFunc, DrSharedAllocFunc>> &
DrClassMap::getMap()
{
static std::unordered_map<std::string,
std::pair<DrAllocFunc, DrSharedAllocFunc>>
map;
return map;
}
+63
View File
@@ -0,0 +1,63 @@
/**
*
* @file DrTemplateBase.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/DrClassMap.h>
#include <drogon/DrTemplateBase.h>
#include <trantor/utils/Logger.h>
#include <memory>
#include <regex>
using namespace drogon;
std::shared_ptr<DrTemplateBase> DrTemplateBase::newTemplate(
const std::string &templateName)
{
LOG_TRACE << "http view name=" << templateName;
auto l = templateName.length();
if (l >= 4 && templateName[l - 4] == '.' && templateName[l - 3] == 'c' &&
templateName[l - 2] == 's' && templateName[l - 1] == 'p')
{
std::string::size_type pos = 0;
std::string newName;
newName.reserve(templateName.size());
if (templateName[0] == '/' || templateName[0] == '\\')
{
pos = 1;
}
else if (templateName[0] == '.' &&
(templateName[1] == '/' || templateName[1] == '\\'))
{
pos = 2;
}
while (pos < l - 4)
{
if (templateName[pos] == '/' || templateName[pos] == '\\')
{
newName.append("::");
}
else
{
newName.append(1, templateName[pos]);
}
++pos;
}
return std::shared_ptr<DrTemplateBase>(dynamic_cast<DrTemplateBase *>(
drogon::DrClassMap::newObject(newName)));
}
else
{
return std::shared_ptr<DrTemplateBase>(dynamic_cast<DrTemplateBase *>(
drogon::DrClassMap::newObject(templateName)));
}
}
@@ -0,0 +1,32 @@
#include "FixedWindowRateLimiter.h"
using namespace drogon;
FixedWindowRateLimiter::FixedWindowRateLimiter(
size_t capacity,
std::chrono::duration<double> timeUnit)
: capacity_(capacity),
lastTime_(std::chrono::steady_clock::now()),
timeUnit_(timeUnit)
{
}
// implementation of the fixed window algorithm
bool FixedWindowRateLimiter::isAllowed()
{
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(
now - lastTime_);
if (duration >= timeUnit_)
{
currentRequests_ = 0;
lastTime_ = now;
}
if (currentRequests_ < capacity_)
{
currentRequests_++;
return true;
}
return false;
}
@@ -0,0 +1,22 @@
#pragma once
#include <drogon/RateLimiter.h>
#include <chrono>
namespace drogon
{
class FixedWindowRateLimiter : public RateLimiter
{
public:
FixedWindowRateLimiter(size_t capacity,
std::chrono::duration<double> timeUnit);
bool isAllowed() override;
~FixedWindowRateLimiter() noexcept override = default;
private:
size_t capacity_;
size_t currentRequests_{0};
std::chrono::steady_clock::time_point lastTime_;
std::chrono::duration<double> timeUnit_;
};
} // namespace drogon
+108
View File
@@ -0,0 +1,108 @@
#include <drogon/plugins/GlobalFilters.h>
#include <drogon/DrClassMap.h>
#include <drogon/HttpAppFramework.h>
#include "MiddlewaresFunction.h"
#include "HttpRequestImpl.h"
#include "HttpAppFrameworkImpl.h"
using namespace drogon::plugin;
void GlobalFilters::initAndStart(const Json::Value &config)
{
if (config.isMember("filters") && config["filters"].isArray())
{
auto &filters = config["filters"];
for (auto const &filter : filters)
{
if (filter.isString())
{
auto filterPtr = std::dynamic_pointer_cast<HttpFilterBase>(
drogon::DrClassMap::getSingleInstance(filter.asString()));
if (filterPtr)
{
filters_.push_back(filterPtr);
}
else
{
LOG_ERROR << "Filter " << filter.asString()
<< " not found!";
}
}
}
}
if (config.isMember("exempt"))
{
auto exempt = config["exempt"];
if (exempt.isArray())
{
std::string regexStr;
for (auto const &ex : exempt)
{
if (ex.isString())
{
regexStr.append("(").append(ex.asString()).append(")|");
}
else
{
LOG_ERROR << "exempt must be a string array!";
}
}
if (!regexStr.empty())
{
regexStr.pop_back();
exemptPegex_ = std::regex(regexStr);
regexFlag_ = true;
}
}
else if (exempt.isString())
{
exemptPegex_ = std::regex(exempt.asString());
regexFlag_ = true;
}
else
{
LOG_ERROR << "exempt must be a string or string array!";
}
}
std::weak_ptr<GlobalFilters> weakPtr = shared_from_this();
drogon::app().registerPreRoutingAdvice(
[weakPtr](const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&acb,
drogon::AdviceChainCallback &&accb) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
accb();
return;
}
if (thisPtr->regexFlag_)
{
if (std::regex_match(req->path(), thisPtr->exemptPegex_))
{
accb();
return;
}
}
drogon::middlewares_function::doFilters(
thisPtr->filters_,
std::static_pointer_cast<HttpRequestImpl>(req),
[acb = std::move(acb),
accb = std::move(accb)](const HttpResponsePtr &resp) {
if (resp)
{
acb(resp);
}
else
{
accb();
}
});
});
}
void GlobalFilters::shutdown()
{
filters_.clear();
}
+80
View File
@@ -0,0 +1,80 @@
#include <drogon/utils/monitoring/Histogram.h>
using namespace drogon;
using namespace drogon::monitoring;
void Histogram::observe(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
if (maxAge_ > std::chrono::seconds(0) &&
timerId_ == trantor::InvalidTimerId)
{
std::weak_ptr<Histogram> weakPtr =
std::dynamic_pointer_cast<Histogram>(shared_from_this());
timerId_ = loopPtr_->runEvery(maxAge_ / timeBucketCount_, [weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
thisPtr->rotateTimeBuckets();
});
}
auto &currentBucket = timeBuckets_.back();
currentBucket.sum += value;
currentBucket.count += 1;
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
{
if (value <= bucketBoundaries_[i])
{
currentBucket.buckets[i] += 1;
break;
}
}
if (value > bucketBoundaries_.back())
{
currentBucket.buckets.back() += 1;
}
}
std::vector<Sample> Histogram::collect() const
{
std::vector<Sample> samples;
std::lock_guard<std::mutex> guard(mutex_);
size_t count{0};
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
{
Sample sample;
for (auto &bucket : timeBuckets_)
{
count += bucket.buckets[i];
}
sample.name = name_ + "_bucket";
sample.exLabels.emplace_back("le",
std::to_string(bucketBoundaries_[i]));
sample.value = count;
samples.emplace_back(std::move(sample));
}
Sample sample;
for (auto &bucket : timeBuckets_)
{
count += bucket.buckets.back();
}
sample.name = name_ + "_bucket";
sample.exLabels.emplace_back("le", "+Inf");
sample.value = count;
samples.emplace_back(std::move(sample));
double sum{0};
uint64_t totalCount{0};
for (auto &bucket : timeBuckets_)
{
sum += bucket.sum;
totalCount += bucket.count;
}
Sample sumSample;
sumSample.name = name_ + "_sum";
sumSample.value = sum;
samples.emplace_back(std::move(sumSample));
Sample countSample;
countSample.name = name_ + "_count";
countSample.value = totalCount;
samples.emplace_back(std::move(countSample));
return samples;
}
+249
View File
@@ -0,0 +1,249 @@
#include <drogon/plugins/Hodor.h>
#include <drogon/plugins/RealIpResolver.h>
using namespace drogon::plugin;
Hodor::LimitStrategy Hodor::makeLimitStrategy(const Json::Value &config)
{
LimitStrategy strategy;
strategy.capacity = config.get("capacity", 0).asUInt();
if (config.isMember("urls") && config["urls"].isArray())
{
std::string regexString;
for (auto &str : config["urls"])
{
assert(str.isString());
regexString.append("(").append(str.asString()).append(")|");
}
if (!regexString.empty())
{
regexString.resize(regexString.length() - 1);
strategy.urlsRegex = std::regex(regexString);
strategy.regexFlag = true;
}
}
if (strategy.capacity > 0)
{
if (multiThreads_)
{
strategy.globalLimiterPtr = std::make_shared<SafeRateLimiter>(
RateLimiter::newRateLimiter(algorithm_,
strategy.capacity,
timeUnit_));
}
else
{
strategy.globalLimiterPtr =
RateLimiter::newRateLimiter(algorithm_,
strategy.capacity,
timeUnit_);
}
}
strategy.ipCapacity = config.get("ip_capacity", 0).asUInt();
if (strategy.ipCapacity > 0)
{
strategy.ipLimiterMapPtr =
std::make_unique<CacheMap<std::string, RateLimiterPtr>>(
drogon::app().getLoop(),
float(timeUnit_.count() / 60 < 1 ? 1 : timeUnit_.count() / 60),
2,
100);
}
strategy.userCapacity = config.get("user_capacity", 0).asUInt();
if (strategy.userCapacity > 0)
{
strategy.userLimiterMapPtr =
std::make_unique<CacheMap<std::string, RateLimiterPtr>>(
drogon::app().getLoop(),
float(timeUnit_.count() / 60 < 1 ? 1 : timeUnit_.count() / 60),
2,
100);
}
return strategy;
}
void Hodor::initAndStart(const Json::Value &config)
{
algorithm_ = stringToRateLimiterType(
config.get("algorithm", "token_bucket").asString());
timeUnit_ = std::chrono::seconds(config.get("time_unit", 60).asUInt());
multiThreads_ = config.get("multi_threads", true).asBool();
useRealIpResolver_ = config.get("use_real_ip_resolver", false).asBool();
rejectResponse_ = HttpResponse::newHttpResponse();
rejectResponse_->setStatusCode(k429TooManyRequests);
rejectResponse_->setBody(
config.get("rejection_message", "Too many requests").asString());
rejectResponse_->setCloseConnection(true);
limiterExpireTime_ =
(std::max)(static_cast<size_t>(
config.get("limiter_expire_time", 600).asUInt()),
static_cast<size_t>(timeUnit_.count() * 3));
limitStrategies_.emplace_back(makeLimitStrategy(config));
if (config.isMember("sub_limits") && config["sub_limits"].isArray())
{
for (auto &subLimit : config["sub_limits"])
{
assert(subLimit.isObject());
if (!subLimit["urls"].isArray() || subLimit["urls"].size() == 0)
{
LOG_ERROR
<< "The urls of sub_limits must be an array and not empty!";
continue;
}
if (subLimit["capacity"].asUInt() == 0 &&
subLimit["ip_capacity"].asUInt() == 0 &&
subLimit["user_capacity"].asUInt() == 0)
{
LOG_ERROR << "At least one capacity of sub_limits must be "
"greater than 0!";
continue;
}
limitStrategies_.emplace_back(makeLimitStrategy(subLimit));
}
}
const Json::Value &trustIps = config["trust_ips"];
if (!trustIps.isNull() && !trustIps.isArray())
{
throw std::runtime_error("Invalid trusted_ips. Should be array.");
}
for (const auto &ipOrCidr : trustIps)
{
trustCIDRs_.emplace_back(ipOrCidr.asString());
}
app().registerPreHandlingAdvice([this](const drogon::HttpRequestPtr &req,
AdviceCallback &&acb,
AdviceChainCallback &&accb) {
onHttpRequest(req, std::move(acb), std::move(accb));
});
}
void Hodor::shutdown()
{
LOG_TRACE << "Hodor plugin is shutdown!";
}
bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
const LimitStrategy &strategy,
const trantor::InetAddress &ip,
const std::optional<std::string> &userId)
{
if (RealIpResolver::matchCidr(ip, trustCIDRs_))
{
return true;
}
if (strategy.regexFlag)
{
if (!std::regex_match(req->path(), strategy.urlsRegex))
{
return true;
}
}
if (strategy.globalLimiterPtr)
{
if (!strategy.globalLimiterPtr->isAllowed())
{
return false;
}
}
if (strategy.ipCapacity > 0)
{
RateLimiterPtr limiterPtr;
strategy.ipLimiterMapPtr->modify(
ip.toIpNetEndian(),
[this, &limiterPtr, &strategy](RateLimiterPtr &ptr) {
if (!ptr)
{
if (multiThreads_)
{
ptr = std::make_shared<SafeRateLimiter>(
RateLimiter::newRateLimiter(algorithm_,
strategy.ipCapacity,
timeUnit_));
}
else
{
ptr = RateLimiter::newRateLimiter(algorithm_,
strategy.ipCapacity,
timeUnit_);
}
}
limiterPtr = ptr;
},
limiterExpireTime_);
if (!limiterPtr->isAllowed())
{
return false;
}
}
if (strategy.userCapacity > 0)
{
if (!userId.has_value())
{
return true;
}
RateLimiterPtr limiterPtr;
strategy.userLimiterMapPtr->modify(
*userId,
[this, &strategy, &limiterPtr](RateLimiterPtr &ptr) {
if (!ptr)
{
if (multiThreads_)
{
ptr = std::make_shared<SafeRateLimiter>(
RateLimiter::newRateLimiter(algorithm_,
strategy.userCapacity,
timeUnit_));
}
else
{
ptr = RateLimiter::newRateLimiter(algorithm_,
strategy.userCapacity,
timeUnit_);
}
}
limiterPtr = ptr;
},
limiterExpireTime_);
if (!limiterPtr->isAllowed())
{
return false;
}
}
return true;
}
void Hodor::onHttpRequest(const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&adviceCallback,
drogon::AdviceChainCallback &&chainCallback)
{
const trantor::InetAddress &ip =
useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
: req->peerAddr();
std::optional<std::string> userId;
if (userIdGetter_)
{
userId = userIdGetter_(req);
}
for (auto &strategy : limitStrategies_)
{
if (!checkLimit(req, strategy, ip, userId))
{
if (rejectResponseFactory_)
{
adviceCallback(rejectResponseFactory_(req));
}
else
{
adviceCallback(rejectResponse_);
}
return;
}
}
chainCallback();
}
File diff suppressed because it is too large Load Diff
+768
View File
@@ -0,0 +1,768 @@
/**
*
* @file HttpAppFrameworkImpl.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/HttpAppFramework.h>
#include <drogon/config.h>
#include <json/json.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "SessionManager.h"
#include "drogon/utils/Utilities.h"
#include "impl_forwards.h"
namespace trantor
{
class EventLoopThreadPool;
}
namespace drogon
{
HttpResponsePtr defaultErrorHandler(HttpStatusCode code,
const HttpRequestPtr &req);
void defaultExceptionHandler(const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
struct InitBeforeMainFunction
{
explicit InitBeforeMainFunction(const std::function<void()> &func)
{
func();
}
};
class HttpAppFrameworkImpl final : public HttpAppFramework
{
public:
HttpAppFrameworkImpl();
const Json::Value &getCustomConfig() const override
{
return jsonConfig_["custom_config"];
}
PluginBase *getPlugin(const std::string &name) override;
std::shared_ptr<PluginBase> getSharedPlugin(
const std::string &name) override;
void addPlugins(const Json::Value &configs) override;
void addPlugin(const std::string &name,
const std::vector<std::string> &dependencies,
const Json::Value &config) override;
HttpAppFramework &addListener(
const std::string &ip,
uint16_t port,
bool useSSL,
const std::string &certFile,
const std::string &keyFile,
bool useOldTLS,
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
override;
HttpAppFramework &setThreadNum(size_t threadNum) override;
size_t getThreadNum() const override
{
return threadNum_;
}
HttpAppFramework &setSSLConfigCommands(
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
override;
HttpAppFramework &setSSLFiles(const std::string &certPath,
const std::string &keyPath) override;
HttpAppFramework &reloadSSLFiles() override;
void run() override;
HttpAppFramework &registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints) override;
HttpAppFramework &registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints) override;
HttpAppFramework &registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints) override;
HttpAppFramework &setCustom404Page(const HttpResponsePtr &resp,
bool set404) override
{
if (set404)
{
resp->setStatusCode(k404NotFound);
}
custom404_ = resp;
return *this;
}
HttpAppFramework &setCustomErrorHandler(
std::function<HttpResponsePtr(HttpStatusCode,
const HttpRequestPtr &req)>
&&resp_generator) override;
const HttpResponsePtr &getCustom404Page();
void forward(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString,
double timeout) override;
void forward(const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString,
double timeout = 0);
HttpAppFramework &registerBeginningAdvice(
const std::function<void()> &advice) override
{
beginningAdvices_.emplace_back(advice);
return *this;
}
HttpAppFramework &registerNewConnectionAdvice(
const std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)> &advice)
override;
HttpAppFramework &registerHttpResponseCreationAdvice(
const std::function<void(const HttpResponsePtr &)> &advice) override;
HttpAppFramework &registerSyncAdvice(
const std::function<HttpResponsePtr(const HttpRequestPtr &)> &advice)
override;
HttpAppFramework &registerPreRoutingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override;
HttpAppFramework &registerPostRoutingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override;
HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override;
HttpAppFramework &registerPreRoutingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override;
HttpAppFramework &registerPostRoutingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override;
HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override;
HttpAppFramework &registerPostHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
const HttpResponsePtr &)> &advice) override;
HttpAppFramework &registerPreSendingAdvice(
const std::function<void(const HttpRequestPtr &,
const HttpResponsePtr &)> &advice) override;
HttpAppFramework &setDefaultHandler(DefaultHandler handler) override;
HttpAppFramework &setupFileLogger() override;
HttpAppFramework &enableSession(
const size_t timeout,
Cookie::SameSite sameSite = Cookie::SameSite::kNull,
const std::string &cookieKey = "JSESSIONID",
int maxAge = -1,
SessionManager::IdGeneratorCallback idGeneratorCallback =
nullptr) override
{
useSession_ = true;
sessionTimeout_ = timeout;
sessionSameSite_ = sameSite;
sessionCookieKey_ = cookieKey;
sessionMaxAge_ = maxAge;
return setSessionIdGenerator(idGeneratorCallback);
}
HttpAppFramework &setSessionIdGenerator(
SessionManager::IdGeneratorCallback idGeneratorCallback = nullptr)
{
sessionIdGeneratorCallback_ =
idGeneratorCallback ? idGeneratorCallback
: []() { return utils::getUuid(true); };
return *this;
}
HttpAppFramework &disableSession() override
{
useSession_ = false;
return *this;
}
HttpAppFramework &registerSessionStartAdvice(
const AdviceStartSessionCallback &advice) override
{
sessionStartAdvices_.emplace_back(advice);
return *this;
}
HttpAppFramework &registerSessionDestroyAdvice(
const AdviceDestroySessionCallback &advice) override
{
sessionDestroyAdvices_.emplace_back(advice);
return *this;
}
const std::string &getDocumentRoot() const override
{
return rootPath_;
}
HttpAppFramework &setDocumentRoot(const std::string &rootPath) override
{
rootPath_ = rootPath;
return *this;
}
HttpAppFramework &setStaticFileHeaders(
const std::vector<std::pair<std::string, std::string>> &headers)
override;
HttpAppFramework &addALocation(
const std::string &uriPrefix,
const std::string &defaultContentType,
const std::string &alias,
bool isCaseSensitive,
bool allowAll,
bool isRecursive,
const std::vector<std::string> &middlewareNames) override;
const std::string &getUploadPath() const override
{
return uploadPath_;
}
const std::shared_ptr<trantor::Resolver> &getResolver() const override
{
static auto resolver = trantor::Resolver::newResolver(getLoop());
return resolver;
}
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
HttpAppFramework &setFileTypes(
const std::vector<std::string> &types) override;
#if !defined(_WIN32) && !TARGET_OS_IOS
HttpAppFramework &enableDynamicViewsLoading(
const std::vector<std::string> &libPaths,
const std::string &outputPath) override;
#endif
HttpAppFramework &setMaxConnectionNum(size_t maxConnections) override;
HttpAppFramework &setMaxConnectionNumPerIP(
size_t maxConnectionsPerIP) override;
HttpAppFramework &loadConfigFile(const std::string &fileName) noexcept(
false) override;
HttpAppFramework &loadConfigJson(const Json::Value &data) noexcept(
false) override;
HttpAppFramework &loadConfigJson(Json::Value &&data) noexcept(
false) override;
HttpAppFramework &enableRunAsDaemon() override
{
runAsDaemon_ = true;
return *this;
}
HttpAppFramework &disableSigtermHandling() override
{
handleSigterm_ = false;
return *this;
}
HttpAppFramework &enableRelaunchOnError() override
{
relaunchOnError_ = true;
return *this;
}
HttpAppFramework &setLogPath(const std::string &logPath,
const std::string &logfileBaseName,
size_t logfileSize,
size_t maxFiles,
bool useSpdlog) override;
HttpAppFramework &setLogLevel(trantor::Logger::LogLevel level) override;
HttpAppFramework &setLogLocalTime(bool on) override;
HttpAppFramework &enableSendfile(bool sendFile) override
{
useSendfile_ = sendFile;
return *this;
}
HttpAppFramework &enableGzip(bool useGzip) override
{
useGzip_ = useGzip;
return *this;
}
bool isGzipEnabled() const override
{
return useGzip_;
}
HttpAppFramework &enableBrotli(bool useBrotli) override
{
useBrotli_ = useBrotli;
return *this;
}
bool isBrotliEnabled() const override
{
return useBrotli_;
}
HttpAppFramework &setStaticFilesCacheTime(int cacheTime) override;
int staticFilesCacheTime() const override;
HttpAppFramework &setIdleConnectionTimeout(size_t timeout) override
{
idleConnectionTimeout_ = timeout;
return *this;
}
size_t getIdleConnectionTimeout() const // could expose in base class
{
return idleConnectionTimeout_;
}
HttpAppFramework &setKeepaliveRequestsNumber(const size_t number) override
{
keepaliveRequestsNumber_ = number;
return *this;
}
HttpAppFramework &setPipeliningRequestsNumber(const size_t number) override
{
pipeliningRequestsNumber_ = number;
return *this;
}
HttpAppFramework &setGzipStatic(bool useGzipStatic) override;
HttpAppFramework &setBrStatic(bool useGzipStatic) override;
HttpAppFramework &setClientMaxBodySize(size_t maxSize) override
{
clientMaxBodySize_ = maxSize;
return *this;
}
HttpAppFramework &setClientMaxMemoryBodySize(size_t maxSize) override
{
clientMaxMemoryBodySize_ = maxSize;
return *this;
}
HttpAppFramework &setClientMaxWebSocketMessageSize(size_t maxSize) override
{
clientMaxWebSocketMessageSize_ = maxSize;
return *this;
}
HttpAppFramework &setHomePage(const std::string &homePageFile) override
{
homePageFile_ = homePageFile;
return *this;
}
const std::string &getHomePage() const override
{
return homePageFile_;
}
HttpAppFramework &setTermSignalHandler(
const std::function<void()> &handler) override
{
termSignalHandler_ = handler;
return *this;
}
const std::function<void()> &getTermSignalHandler() const
{
return termSignalHandler_;
}
HttpAppFramework &setIntSignalHandler(
const std::function<void()> &handler) override
{
intSignalHandler_ = handler;
return *this;
}
const std::function<void()> &getIntSignalHandler() const
{
return intSignalHandler_;
}
HttpAppFramework &setImplicitPageEnable(bool useImplicitPage) override;
bool isImplicitPageEnabled() const override;
HttpAppFramework &setImplicitPage(
const std::string &implicitPageFile) override;
const std::string &getImplicitPage() const override;
size_t getClientMaxBodySize() const
{
return clientMaxBodySize_;
}
size_t getClientMaxMemoryBodySize() const
{
return clientMaxMemoryBodySize_;
}
size_t getClientMaxWebSocketMessageSize() const
{
return clientMaxWebSocketMessageSize_;
}
std::vector<HttpHandlerInfo> getHandlersInfo() const override;
size_t keepaliveRequestsNumber() const
{
return keepaliveRequestsNumber_;
}
size_t pipeliningRequestsNumber() const
{
return pipeliningRequestsNumber_;
}
~HttpAppFrameworkImpl() noexcept override;
bool isRunning() override
{
return running_;
}
HttpAppFramework &setJsonParserStackLimit(size_t limit) noexcept override
{
jsonStackLimit_ = limit;
return *this;
}
size_t getJsonParserStackLimit() const noexcept override
{
return jsonStackLimit_;
}
HttpAppFramework &setUnicodeEscapingInJson(bool enable) noexcept override
{
usingUnicodeEscaping_ = enable;
return *this;
}
bool isUnicodeEscapingUsedInJson() const noexcept override
{
return usingUnicodeEscaping_;
}
HttpAppFramework &setFloatPrecisionInJson(
unsigned int precision,
const std::string &precisionType) noexcept override
{
floatPrecisionInJson_ = std::make_pair(precision, precisionType);
return *this;
}
const std::pair<unsigned int, std::string> &getFloatPrecisionInJson()
const noexcept override
{
return floatPrecisionInJson_;
}
trantor::EventLoop *getLoop() const override;
trantor::EventLoop *getIOLoop(size_t id) const override;
void quit() override;
HttpAppFramework &setServerHeaderField(const std::string &server) override
{
assert(!running_);
assert(server.find("\r\n") == std::string::npos);
serverHeader_ = "server: " + server + "\r\n";
return *this;
}
HttpAppFramework &enableServerHeader(bool flag) override
{
enableServerHeader_ = flag;
return *this;
}
HttpAppFramework &enableDateHeader(bool flag) override
{
enableDateHeader_ = flag;
return *this;
}
bool sendServerHeader() const
{
return enableServerHeader_;
}
bool sendDateHeader() const
{
return enableDateHeader_;
}
const std::string &getServerHeaderString() const
{
return serverHeader_;
}
orm::DbClientPtr getDbClient(const std::string &name) override;
orm::DbClientPtr getFastDbClient(const std::string &name) override;
HttpAppFramework &createDbClient(const std::string &dbType,
const std::string &host,
unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
size_t connectionNum,
const std::string &filename,
const std::string &name,
bool isFast,
const std::string &characterSet,
double timeout,
bool autoBatch) override;
// a helper method
void addDbClient(const std::string &dbType,
const std::string &host,
unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
size_t connectionNum,
const std::string &filename,
const std::string &name,
bool isFast,
const std::string &characterSet,
double timeout,
bool autoBatch,
std::unordered_map<std::string, std::string> options);
HttpAppFramework &addDbClient(const orm::DbConfig &config) override;
HttpAppFramework &createRedisClient(const std::string &ip,
unsigned short port,
const std::string &name,
const std::string &password,
size_t connectionNum,
bool isFast,
double timeout,
unsigned int db,
const std::string &username) override;
nosql::RedisClientPtr getRedisClient(const std::string &name) override;
nosql::RedisClientPtr getFastRedisClient(const std::string &name) override;
std::vector<trantor::InetAddress> getListeners() const override;
inline static HttpAppFrameworkImpl &instance()
{
static HttpAppFrameworkImpl instance;
return instance;
}
bool useSendfile() const
{
return useSendfile_;
}
bool supportSSL() const override
{
return trantor::utils::tlsBackend() != "None";
}
size_t getCurrentThreadIndex() const override
{
auto *loop = trantor::EventLoop::getEventLoopOfCurrentThread();
if (loop)
{
return loop->index();
}
return (std::numeric_limits<size_t>::max)();
}
bool areAllDbClientsAvailable() const noexcept override;
const std::function<HttpResponsePtr(HttpStatusCode,
const HttpRequestPtr &req)> &
getCustomErrorHandler() const override;
bool isUsingCustomErrorHandler() const
{
return usingCustomErrorHandler_;
}
void enableReusePort(bool enable) override
{
reusePort_ = enable;
}
bool reusePort() const override
{
return reusePort_;
}
HttpAppFramework &setExceptionHandler(ExceptionHandler handler) override
{
exceptionHandler_ = std::move(handler);
return *this;
}
const ExceptionHandler &getExceptionHandler() const override
{
return exceptionHandler_;
}
HttpAppFramework &enableCompressedRequest(bool enable) override
{
enableCompressedRequest_ = enable;
return *this;
}
bool isCompressedRequestEnabled() const override
{
return enableCompressedRequest_;
}
HttpAppFramework &registerCustomExtensionMime(
const std::string &ext,
const std::string &mime) override;
// should return unsigned type!
int64_t getConnectionCount() const override;
// TODO: move session related codes to its own singleton class
void findSessionForRequest(const HttpRequestImplPtr &req);
HttpResponsePtr handleSessionForResponse(const HttpRequestImplPtr &req,
const HttpResponsePtr &resp);
HttpAppFramework &setBeforeListenSockOptCallback(
std::function<void(int)> cb) override;
HttpAppFramework &setAfterAcceptSockOptCallback(
std::function<void(int)> cb) override;
HttpAppFramework &setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb) override;
HttpAppFramework &enableRequestStream(bool enable) override;
bool isRequestStreamEnabled() const override;
private:
void registerHttpController(const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName) override;
void registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName) override;
// We use an uuid string as session id;
// set sessionTimeout_=0 to make location session valid forever based on
// cookies;
size_t sessionTimeout_{0};
Cookie::SameSite sessionSameSite_{Cookie::SameSite::kNull};
std::string sessionCookieKey_{"JSESSIONID"};
int sessionMaxAge_{-1};
size_t idleConnectionTimeout_{60};
bool useSession_{false};
std::string serverHeader_{"server: drogon/" + drogon::getVersion() +
"\r\n"};
std::unique_ptr<ListenerManager> listenerManagerPtr_;
std::unique_ptr<PluginsManager> pluginsManagerPtr_;
std::unique_ptr<orm::DbClientManager> dbClientManagerPtr_;
std::unique_ptr<nosql::RedisClientManager> redisClientManagerPtr_;
std::string rootPath_{"./"};
std::string uploadPath_;
std::atomic_bool running_{false};
std::atomic_bool routersInit_{false};
size_t threadNum_{1};
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
#if !defined(_WIN32) && !TARGET_OS_IOS
std::vector<std::string> libFilePaths_;
std::string libFileOutputPath_;
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;
#endif
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
std::string sslCertPath_;
std::string sslKeyPath_;
bool runAsDaemon_{false};
bool handleSigterm_{true};
bool relaunchOnError_{false};
bool logWithSpdlog_{false};
std::string logPath_;
std::string logfileBaseName_;
size_t logfileSize_{100000000};
size_t logfileMaxNum_{0};
size_t keepaliveRequestsNumber_{0};
size_t pipeliningRequestsNumber_{0};
size_t jsonStackLimit_{1000};
bool useSendfile_{true};
bool useGzip_{true};
bool useBrotli_{false};
bool usingUnicodeEscaping_{true};
std::pair<unsigned int, std::string> floatPrecisionInJson_{0,
"significant"};
bool usingCustomErrorHandler_{false};
size_t clientMaxBodySize_{1024 * 1024};
size_t clientMaxMemoryBodySize_{64 * 1024};
size_t clientMaxWebSocketMessageSize_{128 * 1024};
std::string homePageFile_{"index.html"};
std::function<void()> termSignalHandler_{[]() { app().quit(); }};
std::function<void()> intSignalHandler_{[]() { app().quit(); }};
std::unique_ptr<SessionManager> sessionManagerPtr_;
std::vector<AdviceStartSessionCallback> sessionStartAdvices_;
std::vector<AdviceDestroySessionCallback> sessionDestroyAdvices_;
SessionManager::IdGeneratorCallback sessionIdGeneratorCallback_;
std::shared_ptr<trantor::AsyncFileLogger> asyncFileLoggerPtr_;
Json::Value jsonConfig_;
Json::Value jsonRuntimeConfig_;
HttpResponsePtr custom404_;
std::function<HttpResponsePtr(HttpStatusCode, const HttpRequestPtr &req)>
customErrorHandler_ = &defaultErrorHandler;
static InitBeforeMainFunction initFirst_;
bool enableServerHeader_{true};
bool enableDateHeader_{true};
bool reusePort_{false};
std::vector<std::function<void()>> beginningAdvices_;
ExceptionHandler exceptionHandler_{defaultExceptionHandler};
bool enableCompressedRequest_{false};
bool enableRequestStream_{false};
};
} // namespace drogon
+29
View File
@@ -0,0 +1,29 @@
/**
*
* HttpBinder.h
* Martin Chang
*
* Copyright 2021, Martin Chang. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/HttpBinder.h>
#include <drogon/HttpAppFramework.h>
namespace drogon
{
namespace internal
{
void handleException(const std::exception &e,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
app().getExceptionHandler()(e, req, std::move(callback));
}
} // namespace internal
} // namespace drogon
+728
View File
@@ -0,0 +1,728 @@
/**
*
* @file HttpClientImpl.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpClientImpl.h"
#include "HttpAppFrameworkImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "HttpResponseParser.h"
#include <drogon/config.h>
#include <stdlib.h>
#include <algorithm>
using namespace trantor;
using namespace drogon;
using namespace std::placeholders;
namespace trantor
{
static const size_t kDefaultDNSTimeout{600};
}
void HttpClientImpl::createTcpClient()
{
LOG_TRACE << "New TcpClient," << serverAddr_.toIpPort();
tcpClientPtr_ =
std::make_shared<trantor::TcpClient>(loop_, serverAddr_, "httpClient");
if (useSSL_ && utils::supportsTls())
{
LOG_TRACE << "useOldTLS=" << useOldTLS_;
LOG_TRACE << "domain=" << domain_;
auto policy = trantor::TLSPolicy::defaultClientPolicy();
policy->setUseOldTLS(useOldTLS_)
.setValidate(validateCert_)
.setHostname(domain_)
.setConfCmds(sslConfCmds_)
.setCertPath(clientCertPath_)
.setKeyPath(clientKeyPath_);
tcpClientPtr_->enableSSL(std::move(policy));
}
auto thisPtr = shared_from_this();
std::weak_ptr<HttpClientImpl> weakPtr = thisPtr;
tcpClientPtr_->setSockOptCallback([weakPtr](int fd) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (thisPtr->sockOptCallback_)
thisPtr->sockOptCallback_(fd);
});
tcpClientPtr_->setConnectionCallback(
[weakPtr](const trantor::TcpConnectionPtr &connPtr) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (connPtr->connected())
{
connPtr->setContext(
std::make_shared<HttpResponseParser>(connPtr));
// send request;
LOG_TRACE << "Connection established!";
while (thisPtr->pipeliningCallbacks_.size() <=
thisPtr->pipeliningDepth_ &&
!thisPtr->requestsBuffer_.empty())
{
thisPtr->sendReq(connPtr,
thisPtr->requestsBuffer_.front().first);
thisPtr->pipeliningCallbacks_.push(
std::move(thisPtr->requestsBuffer_.front()));
thisPtr->requestsBuffer_.pop_front();
}
}
else
{
LOG_TRACE << "connection disconnect";
auto responseParser = connPtr->getContext<HttpResponseParser>();
if (responseParser && responseParser->parseResponseOnClose() &&
responseParser->gotAll())
{
auto &firstReq = thisPtr->pipeliningCallbacks_.front();
if (firstReq.first->method() == Head)
{
responseParser->setForHeadMethod();
}
auto resp = responseParser->responseImpl();
responseParser->reset();
// temporary fix of dead tcpClientPtr_
// TODO: fix HttpResponseParser when content-length absence
thisPtr->tcpClientPtr_.reset();
thisPtr->handleResponse(resp, std::move(firstReq), connPtr);
if (!thisPtr->requestsBuffer_.empty())
{
thisPtr->createTcpClient();
}
return;
}
thisPtr->onError(ReqResult::NetworkFailure);
}
});
tcpClientPtr_->setConnectionErrorCallback([weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
// can't connect to server
thisPtr->onError(ReqResult::BadServerAddress);
});
tcpClientPtr_->setMessageCallback(
[weakPtr](const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
thisPtr->onRecvMessage(connPtr, msg);
}
});
tcpClientPtr_->setSSLErrorCallback([weakPtr](SSLError err) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (err == trantor::SSLError::kSSLHandshakeError)
thisPtr->onError(ReqResult::HandshakeError);
else if (err == trantor::SSLError::kSSLInvalidCertificate)
thisPtr->onError(ReqResult::InvalidCertificate);
else if (err == trantor::SSLError::kSSLProtocolError)
thisPtr->onError(ReqResult::EncryptionFailure);
else
{
LOG_FATAL << "Invalid value for SSLError";
abort();
}
});
tcpClientPtr_->connect();
}
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
const trantor::InetAddress &addr,
bool useSSL,
bool useOldTLS,
bool validateCert)
: loop_(loop),
serverAddr_(addr),
useSSL_(useSSL),
validateCert_(validateCert),
useOldTLS_(useOldTLS)
{
}
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
const std::string &hostString,
bool useOldTLS,
bool validateCert)
: loop_(loop), validateCert_(validateCert), useOldTLS_(useOldTLS)
{
auto lowerHost = hostString;
std::transform(lowerHost.begin(),
lowerHost.end(),
lowerHost.begin(),
[](unsigned char c) { return tolower(c); });
if (lowerHost.find("https://") == 0)
{
useSSL_ = true;
lowerHost = lowerHost.substr(8);
}
else if (lowerHost.find("http://") == 0)
{
useSSL_ = false;
lowerHost = lowerHost.substr(7);
}
else
{
return;
}
auto pos = lowerHost.find(']');
if (lowerHost[0] == '[' && pos != std::string::npos)
{
// ipv6
domain_ = lowerHost.substr(1, pos - 1);
if (lowerHost[pos + 1] == ':')
{
auto portStr = lowerHost.substr(pos + 2);
pos = portStr.find('/');
if (pos != std::string::npos)
{
portStr = portStr.substr(0, pos);
}
auto port = atoi(portStr.c_str());
if (port > 0 && port < 65536)
{
serverAddr_ = InetAddress(domain_, port, true);
}
}
else
{
if (useSSL_)
{
serverAddr_ = InetAddress(domain_, 443, true);
}
else
{
serverAddr_ = InetAddress(domain_, 80, true);
}
}
}
else
{
auto pos = lowerHost.find(':');
if (pos != std::string::npos)
{
domain_ = lowerHost.substr(0, pos);
auto portStr = lowerHost.substr(pos + 1);
pos = portStr.find('/');
if (pos != std::string::npos)
{
portStr = portStr.substr(0, pos);
}
auto port = atoi(portStr.c_str());
if (port > 0 && port < 65536)
{
serverAddr_ = InetAddress(domain_, port);
}
}
else
{
domain_ = lowerHost;
pos = domain_.find('/');
if (pos != std::string::npos)
{
domain_ = domain_.substr(0, pos);
}
if (useSSL_)
{
serverAddr_ = InetAddress(domain_, 443);
}
else
{
serverAddr_ = InetAddress(domain_, 80);
}
}
}
if (serverAddr_.isUnspecified())
{
isDomainName_ = true;
}
LOG_TRACE << "userSSL=" << useSSL_ << " domain=" << domain_;
}
HttpClientImpl::~HttpClientImpl()
{
LOG_TRACE << "Deconstruction HttpClient";
if (resolverPtr_ && !(loop_->isInLoopThread()))
{
// Make sure the resolverPtr_ is destroyed in the correct thread.
loop_->queueInLoop([resolverPtr = std::move(resolverPtr_)]() {});
}
}
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req,
const drogon::HttpReqCallback &callback,
double timeout)
{
auto thisPtr = shared_from_this();
loop_->runInLoop([thisPtr, req, callback = callback, timeout]() mutable {
thisPtr->sendRequestInLoop(req, std::move(callback), timeout);
});
}
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req,
drogon::HttpReqCallback &&callback,
double timeout)
{
auto thisPtr = shared_from_this();
loop_->runInLoop(
[thisPtr, req, callback = std::move(callback), timeout]() mutable {
thisPtr->sendRequestInLoop(req, std::move(callback), timeout);
});
}
struct RequestCallbackParams
{
RequestCallbackParams(HttpReqCallback &&cb,
HttpClientImplPtr client,
HttpRequestPtr req)
: callback(std::move(cb)),
clientPtr(std::move(client)),
requestPtr(std::move(req))
{
}
const drogon::HttpReqCallback callback;
const HttpClientImplPtr clientPtr;
const HttpRequestPtr requestPtr;
bool timeoutFlag{false};
};
void HttpClientImpl::sendRequestInLoop(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout)
{
if (timeout <= 0)
{
sendRequestInLoop(req, std::move(callback));
return;
}
auto callbackParamsPtr =
std::make_shared<RequestCallbackParams>(std::move(callback),
shared_from_this(),
req);
loop_->runAfter(
timeout,
[weakCallbackBackPtr =
std::weak_ptr<RequestCallbackParams>(callbackParamsPtr)] {
auto callbackParamsPtr = weakCallbackBackPtr.lock();
if (callbackParamsPtr != nullptr)
{
auto &thisPtr = callbackParamsPtr->clientPtr;
if (callbackParamsPtr->timeoutFlag)
{
return;
}
callbackParamsPtr->timeoutFlag = true;
for (auto iter = thisPtr->requestsBuffer_.begin();
iter != thisPtr->requestsBuffer_.end();
++iter)
{
if (iter->first == callbackParamsPtr->requestPtr)
{
thisPtr->requestsBuffer_.erase(iter);
break;
}
}
(callbackParamsPtr->callback)(ReqResult::Timeout, nullptr);
}
});
sendRequestInLoop(req,
[callbackParamsPtr](ReqResult r,
const HttpResponsePtr &resp) {
if (callbackParamsPtr->timeoutFlag)
{
return;
}
callbackParamsPtr->timeoutFlag = true;
(callbackParamsPtr->callback)(r, resp);
});
}
static bool isValidIpAddr(const trantor::InetAddress &addr)
{
if (addr.portNetEndian() == 0)
{
return false;
}
if (!addr.isIpV6())
{
return addr.ipNetEndian() != 0;
}
// Is ipv6
auto ipaddr = addr.ip6NetEndian();
for (int i = 0; i < 4; ++i)
{
if (ipaddr[i] != 0)
{
return true;
}
}
return false;
}
void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
drogon::HttpReqCallback &&callback)
{
loop_->assertInLoopThread();
if (!static_cast<drogon::HttpRequestImpl *>(req.get())->passThrough())
{
req->addHeader("connection", "Keep-Alive");
if (!userAgent_.empty())
req->addHeader("user-agent", userAgent_);
}
// Set the host header if not already set
if (req->getHeader("host").empty())
{
if (onDefaultPort())
{
req->addHeader("host", host());
}
else
{
req->addHeader("host", host() + ":" + std::to_string(port()));
}
}
for (auto &cookie : validCookies_)
{
if ((cookie.expiresDate().microSecondsSinceEpoch() == 0 ||
cookie.expiresDate() > trantor::Date::now()) &&
(cookie.path().empty() || req->path().find(cookie.path()) == 0))
{
req->addCookie(cookie.key(), cookie.value());
}
}
if (!tcpClientPtr_)
{
auto callbackPtr =
std::make_shared<drogon::HttpReqCallback>(std::move(callback));
requestsBuffer_.push_back(
{req,
[thisPtr = shared_from_this(),
callbackPtr](ReqResult result, const HttpResponsePtr &response) {
(*callbackPtr)(result, response);
}});
if (domain_.empty() || !isDomainName_)
{
// Valid ip address, no domain, connect directly
if (isValidIpAddr(serverAddr_))
{
createTcpClient();
}
// No ip address and no domain, respond with BadServerAddress
else
{
requestsBuffer_.pop_front();
(*callbackPtr)(ReqResult::BadServerAddress, nullptr);
assert(requestsBuffer_.empty());
}
return;
}
// A dns query is on going.
if (dns_)
{
return;
}
// Always do dns query when (re)connects a domain.
dns_ = true;
if (!resolverPtr_)
{
resolverPtr_ =
trantor::Resolver::newResolver(loop_, kDefaultDNSTimeout);
}
auto thisPtr = shared_from_this();
resolverPtr_->resolve(
domain_, [thisPtr](const trantor::InetAddress &addr) {
thisPtr->loop_->runInLoop([thisPtr, addr]() {
// Retrieve port from old serverAddr_
auto port = thisPtr->serverAddr_.portNetEndian();
thisPtr->serverAddr_ = addr;
thisPtr->serverAddr_.setPortNetEndian(port);
LOG_TRACE << "dns:domain=" << thisPtr->domain_
<< ";ip=" << thisPtr->serverAddr_.toIp();
thisPtr->dns_ = false;
if (isValidIpAddr(thisPtr->serverAddr_))
{
thisPtr->createTcpClient();
return;
}
// DNS fail to get valid ip address,
// respond all requests with BadServerAddress
while (!(thisPtr->requestsBuffer_).empty())
{
auto &reqAndCb = (thisPtr->requestsBuffer_).front();
reqAndCb.second(ReqResult::BadServerAddress, nullptr);
(thisPtr->requestsBuffer_).pop_front();
}
});
});
return;
}
// send request;
auto connPtr = tcpClientPtr_->connection();
auto thisPtr = shared_from_this();
// Not connected, push request to buffer and wait for connection
if (!connPtr || connPtr->disconnected())
{
requestsBuffer_.push_back(
{req,
[thisPtr,
callback = std::move(callback)](ReqResult result,
const HttpResponsePtr &response) {
callback(result, response);
}});
return;
}
// Connected, send request now
if (pipeliningCallbacks_.size() <= pipeliningDepth_ &&
requestsBuffer_.empty())
{
sendReq(connPtr, req);
pipeliningCallbacks_.push(
{req,
[thisPtr,
callback = std::move(callback)](ReqResult result,
const HttpResponsePtr &response) {
callback(result, response);
}});
}
else
{
requestsBuffer_.push_back(
{req,
[thisPtr,
callback = std::move(callback)](ReqResult result,
const HttpResponsePtr &response) {
callback(result, response);
}});
}
}
void HttpClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr,
const HttpRequestPtr &req)
{
trantor::MsgBuffer buffer;
assert(req);
auto implPtr = static_cast<HttpRequestImpl *>(req.get());
implPtr->appendToBuffer(&buffer);
LOG_TRACE << "Send request:"
<< std::string(buffer.peek(), buffer.readableBytes());
bytesSent_ += buffer.readableBytes();
connPtr->send(std::move(buffer));
}
void HttpClientImpl::handleResponse(
const HttpResponseImplPtr &resp,
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
const trantor::TcpConnectionPtr &connPtr)
{
assert(!pipeliningCallbacks_.empty());
auto &coding = resp->getHeaderBy("content-encoding");
if (coding == "gzip")
{
resp->gunzip();
}
#ifdef USE_BROTLI
else if (coding == "br")
{
resp->brDecompress();
}
#endif
auto cb = std::move(reqAndCb);
pipeliningCallbacks_.pop();
handleCookies(resp);
cb.second(ReqResult::Ok, resp);
// LOG_TRACE << "pipelining buffer size=" <<
// pipeliningCallbacks_.size(); LOG_TRACE << "requests buffer size="
// << requestsBuffer_.size();
if (connPtr->connected())
{
if (!requestsBuffer_.empty())
{
auto &reqAndCallback = requestsBuffer_.front();
sendReq(connPtr, reqAndCallback.first);
pipeliningCallbacks_.push(std::move(reqAndCallback));
requestsBuffer_.pop_front();
}
else
{
if (resp->ifCloseConnection() && pipeliningCallbacks_.empty())
{
tcpClientPtr_.reset();
}
}
}
else
{
while (!pipeliningCallbacks_.empty())
{
auto cb = std::move(pipeliningCallbacks_.front());
pipeliningCallbacks_.pop();
cb.second(ReqResult::NetworkFailure, nullptr);
}
}
}
void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg)
{
auto responseParser = connPtr->getContext<HttpResponseParser>();
// LOG_TRACE << "###:" << msg->readableBytes();
auto msgSize = msg->readableBytes();
while (msg->readableBytes() > 0)
{
if (pipeliningCallbacks_.empty())
{
LOG_ERROR << "More responses than expected!";
connPtr->shutdown();
return;
}
auto &firstReq = pipeliningCallbacks_.front();
if (firstReq.first->method() == Head)
{
responseParser->setForHeadMethod();
}
if (!responseParser->parseResponse(msg))
{
onError(ReqResult::BadResponse);
bytesReceived_ += (msgSize - msg->readableBytes());
return;
}
if (responseParser->gotAll())
{
auto resp = responseParser->responseImpl();
resp->setPeerCertificate(connPtr->peerCertificate());
responseParser->reset();
bytesReceived_ += (msgSize - msg->readableBytes());
msgSize = msg->readableBytes();
handleResponse(resp, std::move(firstReq), connPtr);
}
else
{
bytesReceived_ += (msgSize - msg->readableBytes());
break;
}
}
}
HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
uint16_t port,
bool useSSL,
trantor::EventLoop *loop,
bool useOldTLS,
bool validateCert)
{
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
return std::make_shared<HttpClientImpl>(
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
trantor::InetAddress(ip, port, isIpv6),
useSSL,
useOldTLS,
validateCert);
}
HttpClientPtr HttpClient::newHttpClient(const std::string &hostString,
trantor::EventLoop *loop,
bool useOldTLS,
bool validateCert)
{
return std::make_shared<HttpClientImpl>(
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
hostString,
useOldTLS,
validateCert);
}
void HttpClientImpl::onError(ReqResult result)
{
while (!pipeliningCallbacks_.empty())
{
auto cb = std::move(pipeliningCallbacks_.front());
pipeliningCallbacks_.pop();
cb.second(result, nullptr);
}
while (!requestsBuffer_.empty())
{
auto cb = std::move(requestsBuffer_.front().second);
requestsBuffer_.pop_front();
cb(result, nullptr);
}
tcpClientPtr_.reset();
}
void HttpClientImpl::handleCookies(const HttpResponseImplPtr &resp)
{
loop_->assertInLoopThread();
if (!enableCookies_)
return;
for (auto &iter : resp->getCookies())
{
auto &cookie = iter.second;
if (!cookie.domain().empty() && cookie.domain() != domain_)
{
continue;
}
if (cookie.isSecure())
{
if (useSSL_)
{
validCookies_.emplace_back(cookie);
}
}
else
{
validCookies_.emplace_back(cookie);
}
}
}
void HttpClientImpl::setCertPath(const std::string &cert,
const std::string &key)
{
clientCertPath_ = cert;
clientKeyPath_ = key;
}
void HttpClientImpl::addSSLConfigs(
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
{
for (const auto &cmd : sslConfCmds)
{
sslConfCmds_.push_back(cmd);
}
}
+176
View File
@@ -0,0 +1,176 @@
/**
*
* @file HttpClientImpl.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by the MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/Cookie.h>
#include <drogon/HttpClient.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/Resolver.h>
#include <trantor/net/TcpClient.h>
#include <cstddef>
#include <functional>
#include <future>
#include <list>
#include <mutex>
#include <queue>
#include <vector>
#include "impl_forwards.h"
namespace drogon
{
class HttpClientImpl final : public HttpClient,
public std::enable_shared_from_this<HttpClientImpl>
{
public:
HttpClientImpl(trantor::EventLoop *loop,
const trantor::InetAddress &addr,
bool useSSL = false,
bool useOldTLS = false,
bool validateCert = true);
HttpClientImpl(trantor::EventLoop *loop,
const std::string &hostString,
bool useOldTLS = false,
bool validateCert = true);
void sendRequest(const HttpRequestPtr &req,
const HttpReqCallback &callback,
double timeout = 0) override;
void sendRequest(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout = 0) override;
trantor::EventLoop *getLoop() override
{
return loop_;
}
void setPipeliningDepth(size_t depth) override
{
pipeliningDepth_ = depth;
}
~HttpClientImpl();
void enableCookies(bool flag = true) override
{
enableCookies_ = flag;
}
void addCookie(const std::string &key, const std::string &value) override
{
validCookies_.emplace_back(Cookie(key, value));
}
void addCookie(const Cookie &cookie) override
{
validCookies_.emplace_back(cookie);
}
size_t bytesSent() const override
{
return bytesSent_;
}
size_t bytesReceived() const override
{
return bytesReceived_;
}
void setUserAgent(const std::string &userAgent) override
{
userAgent_ = userAgent;
}
uint16_t port() const override
{
return serverAddr_.toPort();
}
std::string host() const override
{
if (domain_.empty())
return serverAddr_.toIp();
return domain_;
}
bool secure() const override
{
return useSSL_;
}
void setCertPath(const std::string &cert, const std::string &key) override;
void addSSLConfigs(const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) override;
void setSockOptCallback(std::function<void(int)> cb) override
{
sockOptCallback_ = std::move(cb);
}
std::size_t requestsBufferSize() override
{
if (loop_->isInLoopThread())
{
return requestsBuffer_.size();
}
else
{
std::promise<std::size_t> bufferSize;
loop_->queueInLoop(
[&] { bufferSize.set_value(requestsBuffer_.size()); });
return bufferSize.get_future().get();
}
}
private:
std::shared_ptr<trantor::TcpClient> tcpClientPtr_;
trantor::EventLoop *loop_;
trantor::InetAddress serverAddr_;
bool useSSL_;
bool validateCert_;
void sendReq(const trantor::TcpConnectionPtr &connPtr,
const HttpRequestPtr &req);
void sendRequestInLoop(const HttpRequestPtr &req,
HttpReqCallback &&callback);
void sendRequestInLoop(const HttpRequestPtr &req,
HttpReqCallback &&callback,
double timeout);
void handleCookies(const HttpResponseImplPtr &resp);
void handleResponse(const HttpResponseImplPtr &resp,
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
const trantor::TcpConnectionPtr &connPtr);
void createTcpClient();
std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> pipeliningCallbacks_;
std::list<std::pair<HttpRequestPtr, HttpReqCallback>> requestsBuffer_;
void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
void onError(ReqResult result);
std::string domain_;
bool isDomainName_{true}; // true if domain_ is name
size_t pipeliningDepth_{0};
bool enableCookies_{false};
std::vector<Cookie> validCookies_;
size_t bytesSent_{0};
size_t bytesReceived_{0};
bool dns_{false};
std::shared_ptr<trantor::Resolver> resolverPtr_;
bool useOldTLS_{false};
std::string userAgent_{"DrogonClient"};
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
std::string clientCertPath_;
std::string clientKeyPath_;
std::function<void(int)> sockOptCallback_;
};
using HttpClientImplPtr = std::shared_ptr<HttpClientImpl>;
} // namespace drogon
+80
View File
@@ -0,0 +1,80 @@
/**
*
* @file HttpConnectionLimit.cc
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpConnectionLimit.h"
#include <trantor/utils/Logger.h>
using namespace drogon;
void HttpConnectionLimit::setMaxConnectionNum(size_t num)
{
maxConnectionNum_ = num;
}
void HttpConnectionLimit::setMaxConnectionNumPerIP(size_t num)
{
maxConnectionNumPerIP_ = num;
}
bool HttpConnectionLimit::tryAddConnection(
const trantor::TcpConnectionPtr &conn)
{
assert(conn->connected());
if (connectionNum_.fetch_add(1, std::memory_order_relaxed) >
maxConnectionNum_)
{
return false;
}
if (maxConnectionNumPerIP_ > 0)
{
std::string ip = conn->peerAddr().toIp();
size_t numOnThisIp;
{
std::lock_guard<std::mutex> lock(mutex_);
numOnThisIp = (++ipConnectionsMap_[ip]);
}
if (numOnThisIp > maxConnectionNumPerIP_)
{
return false;
}
}
return true;
}
void HttpConnectionLimit::releaseConnection(
const trantor::TcpConnectionPtr &conn)
{
assert(!conn->connected());
if (!conn->hasContext())
{
// If the connection is connected to the SSL port and then
// disconnected before the SSL handshake.
return;
}
connectionNum_.fetch_sub(1, std::memory_order_relaxed);
if (maxConnectionNumPerIP_ > 0)
{
std::string ip = conn->peerAddr().toIp();
std::lock_guard<std::mutex> lock(mutex_);
auto iter = ipConnectionsMap_.find(ip);
if (iter != ipConnectionsMap_.end())
{
if (--iter->second <= 0)
{
ipConnectionsMap_.erase(iter);
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
/**
*
* @file HttpConnectionLimit.h
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string>
#include <unordered_map>
#include <atomic>
#include <cstddef>
#include <mutex>
#include <trantor/net/TcpConnection.h>
namespace drogon
{
class HttpConnectionLimit
{
public:
static HttpConnectionLimit &instance()
{
static HttpConnectionLimit inst;
return inst;
}
size_t getConnectionNum() const
{
return connectionNum_.load(std::memory_order_relaxed);
}
// don't set after start
void setMaxConnectionNum(size_t num);
void setMaxConnectionNumPerIP(size_t num);
bool tryAddConnection(const trantor::TcpConnectionPtr &conn);
void releaseConnection(const trantor::TcpConnectionPtr &conn);
private:
std::mutex mutex_;
size_t maxConnectionNum_{100000};
std::atomic<size_t> connectionNum_{0};
size_t maxConnectionNumPerIP_{0};
std::unordered_map<std::string, size_t> ipConnectionsMap_;
};
} // namespace drogon
+93
View File
@@ -0,0 +1,93 @@
/**
*
* @file HttpControllerBinder.cc
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpControllerBinder.h"
#include "HttpResponseImpl.h"
#include <drogon/HttpSimpleController.h>
#include <drogon/WebSocketController.h>
namespace drogon
{
void HttpSimpleControllerBinder::handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
// Binders without controller should be removed after run()
assert(controller_);
try
{
auto cb = callback; // copy
controller_->asyncHandleHttpRequest(req, std::move(cb));
}
catch (const std::exception &e)
{
app().getExceptionHandler()(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
void HttpControllerBinder::handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
auto &paramsVector = req->getRoutingParameters();
std::deque<std::string> params(paramsVector.begin(), paramsVector.end());
binderPtr_->handleHttpRequest(params, req, std::move(callback));
}
void WebsocketControllerBinder::handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
std::string wsKey = req->getHeaderBy("sec-websocket-key");
wsKey.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
unsigned char accKey[20];
auto sha1 = trantor::utils::sha1(wsKey.c_str(), wsKey.length());
memcpy(accKey, &sha1, sizeof(sha1));
auto base64Key = utils::base64Encode(accKey, sizeof(accKey));
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k101SwitchingProtocols);
resp->addHeader("Upgrade", "websocket");
resp->addHeader("Connection", "Upgrade");
resp->addHeader("Sec-WebSocket-Accept", base64Key);
callback(resp);
}
void WebsocketControllerBinder::handleNewConnection(
const HttpRequestImplPtr &req,
const WebSocketConnectionImplPtr &wsConnPtr) const
{
auto ctrlPtr = controller_;
assert(ctrlPtr);
wsConnPtr->setMessageCallback(
[ctrlPtr](std::string &&message,
const WebSocketConnectionImplPtr &connPtr,
const WebSocketMessageType &type) {
ctrlPtr->handleNewMessage(connPtr, std::move(message), type);
});
wsConnPtr->setCloseCallback(
[ctrlPtr](const WebSocketConnectionImplPtr &connPtr) {
ctrlPtr->handleConnectionClosed(connPtr);
});
ctrlPtr->handleNewConnection(req, wsConnPtr);
}
} // namespace drogon
+65
View File
@@ -0,0 +1,65 @@
/**
*
* @file HttpControllerBinder.h
* @author Nitromelon
*
* Copyright 2023, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "ControllerBinderBase.h"
#include "HttpRequestImpl.h"
#include "WebSocketConnectionImpl.h"
#include <drogon/HttpBinder.h>
namespace drogon
{
class HttpSimpleControllerBinder : public ControllerBinderBase
{
public:
void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const override;
std::shared_ptr<HttpSimpleControllerBase> controller_;
};
class HttpControllerBinder : public ControllerBinderBase
{
public:
void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const override;
bool isStreamHandler() const override
{
assert(binderPtr_);
return binderPtr_->isStreamHandler();
}
internal::HttpBinderBasePtr binderPtr_;
std::vector<size_t> parameterPlaces_;
std::vector<std::pair<std::string, size_t>> queryParametersPlaces_;
};
struct WebsocketControllerBinder : public ControllerBinderBase
{
std::shared_ptr<WebSocketControllerBase> controller_;
void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const override;
void handleNewConnection(const HttpRequestImplPtr &req,
const WebSocketConnectionImplPtr &wsConnPtr) const;
};
} // namespace drogon
+764
View File
@@ -0,0 +1,764 @@
/**
*
* @file HttpControllersRouter.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpControllersRouter.h"
#include "HttpControllerBinder.h"
#include "HttpRequestImpl.h"
#include "HttpAppFrameworkImpl.h"
#include "MiddlewaresFunction.h"
#include <drogon/HttpSimpleController.h>
#include <drogon/WebSocketController.h>
#include <algorithm>
using namespace drogon;
void HttpControllersRouter::init(
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
{
auto initMiddlewaresAndCorsMethods = [](const auto &item) {
auto corsMethods = std::make_shared<std::string>("OPTIONS,");
for (size_t i = 0; i < Invalid; ++i)
{
auto &binder = item.binders_[i];
if (binder)
{
binder->middlewares_ = middlewares_function::createMiddlewares(
binder->middlewareNames_);
binder->corsMethods_ = corsMethods;
if (binder->isCORS_)
{
if (i == Get)
{
corsMethods->append("GET,HEAD,");
}
else if (i != Options)
{
corsMethods->append(to_string_view((HttpMethod)i));
corsMethods->append(",");
}
}
}
}
corsMethods->pop_back(); // remove last comma
};
for (auto &iter : simpleCtrlMap_)
{
initMiddlewaresAndCorsMethods(iter.second);
}
for (auto &iter : wsCtrlMap_)
{
initMiddlewaresAndCorsMethods(iter.second);
}
for (auto &router : ctrlVector_)
{
router.regex_ = std::regex(router.pathParameterPattern_,
std::regex_constants::icase);
initMiddlewaresAndCorsMethods(router);
}
for (auto &p : ctrlMap_)
{
auto &router = p.second;
router.regex_ = std::regex(router.pathParameterPattern_,
std::regex_constants::icase);
initMiddlewaresAndCorsMethods(router);
}
}
void HttpControllersRouter::reset()
{
simpleCtrlMap_.clear();
ctrlMap_.clear();
ctrlVector_.clear();
wsCtrlMap_.clear();
}
std::vector<HttpHandlerInfo> HttpControllersRouter::getHandlersInfo() const
{
std::vector<HttpHandlerInfo> ret;
auto gatherInfo = [&ret](const std::string &path, const auto &item) {
for (size_t i = 0; i < Invalid; ++i)
{
if (item.binders_[i])
{
std::string description;
if constexpr (std::is_same_v<std::decay_t<decltype(item)>,
SimpleControllerRouterItem>)
{
description = std::string("HttpSimpleController: ") +
item.binders_[i]->handlerName_;
}
else if constexpr (std::is_same_v<
std::decay_t<decltype(item)>,
WebSocketControllerRouterItem> ||
std::is_same_v<
std::decay_t<decltype(item)>,
RegExWebSocketControllerRouterItem>)
{
description = std::string("WebsocketController: ") +
item.binders_[i]->handlerName_;
}
else
{
description =
item.binders_[i]->handlerName_.empty()
? std::string("Handler: ") +
item.binders_[i]->binderPtr_->handlerName()
: std::string("HttpController: ") +
item.binders_[i]->handlerName_;
}
ret.emplace_back(path, (HttpMethod)i, std::move(description));
}
}
};
for (auto &[path, item] : simpleCtrlMap_)
{
gatherInfo(path, item);
}
for (auto &item : ctrlVector_)
{
gatherInfo(item.pathPattern_, item);
}
for (auto &[key, item] : ctrlMap_)
{
gatherInfo(item.pathPattern_, item);
}
for (auto &[path, item] : wsCtrlMap_)
{
gatherInfo(path, item);
}
for (auto &item : wsCtrlVector_)
{
gatherInfo(item.pathPattern_, item);
}
return ret;
}
template <typename Binder, typename RouterItem>
static void addCtrlBinderToRouterItem(const std::shared_ptr<Binder> &binderPtr,
RouterItem &router,
const std::vector<HttpMethod> &methods)
{
if (!methods.empty())
{
for (const auto &method : methods)
{
router.binders_[method] = binderPtr;
if (method == Options)
{
binderPtr->isCORS_ = true;
}
}
}
else
{
// All HTTP methods are valid
binderPtr->isCORS_ = true;
for (int i = 0; i < Invalid; ++i)
{
router.binders_[i] = binderPtr;
}
}
}
struct SimpleControllerProcessResult
{
std::string lowerPath;
std::vector<HttpMethod> validMethods;
std::vector<std::string> middlewares;
};
static SimpleControllerProcessResult processSimpleControllerParams(
const std::string &pathName,
const std::vector<internal::HttpConstraint> &constraints)
{
std::string path(pathName);
std::transform(pathName.begin(),
pathName.end(),
path.begin(),
[](unsigned char c) { return tolower(c); });
std::vector<HttpMethod> validMethods;
std::vector<std::string> middlewareNames;
for (const auto &constraint : constraints)
{
if (constraint.type() == internal::ConstraintType::HttpMiddleware)
{
middlewareNames.push_back(constraint.getMiddlewareName());
}
else if (constraint.type() == internal::ConstraintType::HttpMethod)
{
validMethods.push_back(constraint.getHttpMethod());
}
else
{
LOG_ERROR << "Invalid controller constraint type";
// Used to call exit() here, but that's not very nice.
}
}
return {
std::move(path),
std::move(validMethods),
std::move(middlewareNames),
};
}
void HttpControllersRouter::registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints)
{
assert(!pathName.empty());
assert(!ctrlName.empty());
// Note: some compiler version failed to handle structural bindings with
// lambda capture
auto result = processSimpleControllerParams(pathName, constraints);
std::string path = std::move(result.lowerPath);
auto &item = simpleCtrlMap_[path];
auto binder = std::make_shared<HttpSimpleControllerBinder>();
binder->handlerName_ = ctrlName;
binder->middlewareNames_ = result.middlewares;
drogon::app().getLoop()->queueInLoop([this, binder, ctrlName, path]() {
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
auto controller =
std::dynamic_pointer_cast<HttpSimpleControllerBase>(object_);
if (!controller)
{
LOG_ERROR << "Controller class not found: " << ctrlName;
simpleCtrlMap_.erase(path);
return;
}
binder->controller_ = controller;
// Recreate this with the correct number of threads.
binder->responseCache_ = IOThreadStorage<HttpResponsePtr>();
});
addCtrlBinderToRouterItem(binder, item, result.validMethods);
}
void HttpControllersRouter::registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints)
{
assert(!pathName.empty());
assert(!ctrlName.empty());
auto result = processSimpleControllerParams(pathName, constraints);
std::string path = std::move(result.lowerPath);
auto &item = wsCtrlMap_[path];
auto binder = std::make_shared<WebsocketControllerBinder>();
binder->handlerName_ = ctrlName;
binder->middlewareNames_ = result.middlewares;
drogon::app().getLoop()->queueInLoop([this, binder, ctrlName, path]() {
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
auto controller =
std::dynamic_pointer_cast<WebSocketControllerBase>(object_);
if (!controller)
{
LOG_ERROR << "Websocket controller class not found: " << ctrlName;
wsCtrlMap_.erase(path);
return;
}
binder->controller_ = controller;
});
addCtrlBinderToRouterItem(binder, item, result.validMethods);
}
void HttpControllersRouter::registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints)
{
assert(!regExp.empty());
assert(!ctrlName.empty());
auto result = processSimpleControllerParams(regExp, constraints);
auto binder = std::make_shared<WebsocketControllerBinder>();
binder->handlerName_ = ctrlName;
binder->middlewareNames_ = result.middlewares;
drogon::app().getLoop()->queueInLoop([binder, ctrlName]() {
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
auto controller =
std::dynamic_pointer_cast<WebSocketControllerBase>(object_);
binder->controller_ = controller;
});
struct RegExWebSocketControllerRouterItem router;
router.pathPattern_ = regExp;
router.regex_ = regExp;
addCtrlBinderToRouterItem(binder, router, result.validMethods);
wsCtrlVector_.push_back(std::move(router));
}
void HttpControllersRouter::addHttpRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName)
{
auto binderInfo = std::make_shared<HttpControllerBinder>();
binderInfo->middlewareNames_ = middlewareNames;
binderInfo->handlerName_ = handlerName;
binderInfo->binderPtr_ = binder;
drogon::app().getLoop()->queueInLoop([binderInfo]() {
// Recreate this with the correct number of threads.
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
});
addRegexCtrlBinder(binderInfo, regExp, regExp, validMethods);
}
void HttpControllersRouter::addHttpPath(
const std::string &path,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName)
{
// Path is like /api/v1/service/method/{1}/{2}/xxx...
std::vector<size_t> places;
std::string tmpPath = path;
std::string paras;
static const std::regex regex("\\{([^/]*)\\}");
std::smatch results;
auto pos = tmpPath.find('?');
if (pos != std::string::npos)
{
paras = tmpPath.substr(pos + 1);
tmpPath = tmpPath.substr(0, pos);
}
std::string originPath = tmpPath;
size_t placeIndex = 1;
// Process path parameter placeholders
while (std::regex_search(tmpPath, results, regex))
{
if (results.size() > 1)
{
auto result = results[1].str();
if (!result.empty() &&
std::all_of(result.begin(), result.end(), [](const char c) {
return std::isdigit(c);
}))
{
auto place = (size_t)std::stoi(result);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to " << binder->paramCount()
<< ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) { return i != place; }))
{
LOG_ERROR << "Parameter placeholders are duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
places.push_back(place);
}
else
{
static const std::regex regNumberAndName("([0-9]+):.*");
std::smatch regexResult;
if (std::regex_match(result, regexResult, regNumberAndName))
{
assert(regexResult.size() == 2 && regexResult[1].matched);
auto num = regexResult[1].str();
auto place = (size_t)std::stoi(num);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to "
<< binder->paramCount() << ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) { return i != place; }))
{
LOG_ERROR
<< "Parameter placeholders are duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
places.push_back(place);
}
else
{
if (!std::all_of(places.begin(),
places.end(),
[placeIndex](size_t i) {
return i != placeIndex;
}))
{
LOG_ERROR
<< "Parameter placeholders are duplicated: index="
<< placeIndex;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
places.push_back(placeIndex);
}
}
++placeIndex;
}
tmpPath = results.suffix();
}
// Process query parameter placeholders
std::vector<std::pair<std::string, size_t>> parametersPlaces;
if (!paras.empty())
{
static const std::regex pregex("([^&]*)=\\{([^&]*)\\}&*");
while (std::regex_search(paras, results, pregex))
{
if (results.size() > 2)
{
auto result = results[2].str();
if (!result.empty() &&
std::all_of(result.begin(), result.end(), [](const char c) {
return std::isdigit(c);
}))
{
auto place = (size_t)std::stoi(result);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to "
<< binder->paramCount() << ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) {
return i != place;
}) ||
!all_of(parametersPlaces.begin(),
parametersPlaces.end(),
[place](const std::pair<std::string, size_t>
&item) {
return item.second != place;
}))
{
LOG_ERROR << "Parameter placeholders are "
"duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
parametersPlaces.emplace_back(results[1].str(), place);
}
else
{
std::regex regNumberAndName("([0-9]+):.*");
std::smatch regexResult;
if (std::regex_match(result, regexResult, regNumberAndName))
{
assert(regexResult.size() == 2 &&
regexResult[1].matched);
auto num = regexResult[1].str();
auto place = (size_t)std::stoi(num);
if (place > binder->paramCount() || place == 0)
{
LOG_ERROR << "Parameter placeholder(value=" << place
<< ") out of range (1 to "
<< binder->paramCount() << ")";
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
if (!std::all_of(places.begin(),
places.end(),
[place](size_t i) {
return i != place;
}) ||
!all_of(parametersPlaces.begin(),
parametersPlaces.end(),
[place](const std::pair<std::string, size_t>
&item) {
return item.second != place;
}))
{
LOG_ERROR << "Parameter placeholders are "
"duplicated: index="
<< place;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
parametersPlaces.emplace_back(results[1].str(), place);
}
else
{
if (!std::all_of(places.begin(),
places.end(),
[placeIndex](size_t i) {
return i != placeIndex;
}) ||
!all_of(parametersPlaces.begin(),
parametersPlaces.end(),
[placeIndex](
const std::pair<std::string, size_t>
&item) {
return item.second != placeIndex;
}))
{
LOG_ERROR << "Parameter placeholders are "
"duplicated: index="
<< placeIndex;
LOG_ERROR << "Path pattern: " << path;
exit(1);
}
parametersPlaces.emplace_back(results[1].str(),
placeIndex);
}
}
++placeIndex;
}
paras = results.suffix();
}
}
// Create new ControllerBinder
auto binderInfo = std::make_shared<HttpControllerBinder>();
binderInfo->middlewareNames_ = middlewareNames;
binderInfo->handlerName_ = handlerName;
binderInfo->binderPtr_ = binder;
binderInfo->parameterPlaces_ = std::move(places);
binderInfo->queryParametersPlaces_ = std::move(parametersPlaces);
drogon::app().getLoop()->queueInLoop([binderInfo]() {
// Recreate this with the correct number of threads.
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
});
// Create or update RouterItem
auto pathParameterPattern =
std::regex_replace(originPath, regex, "([^/]*)");
if (originPath != pathParameterPattern) // require regex
{
addRegexCtrlBinder(binderInfo,
path,
pathParameterPattern,
validMethods);
return;
}
std::string loweredPath;
std::transform(originPath.begin(),
originPath.end(),
std::back_inserter(loweredPath),
[](unsigned char c) { return tolower(c); });
HttpControllerRouterItem *routerItemPtr;
// If exists another controllers on the same route, update them
auto it = ctrlMap_.find(loweredPath);
if (it != ctrlMap_.end())
{
routerItemPtr = &it->second;
}
// Create new router item if not exists
else
{
struct HttpControllerRouterItem router;
router.pathParameterPattern_ = pathParameterPattern;
router.pathPattern_ = path;
routerItemPtr =
&ctrlMap_.emplace(loweredPath, std::move(router)).first->second;
}
addCtrlBinderToRouterItem(binderInfo, *routerItemPtr, validMethods);
}
RouteResult HttpControllersRouter::route(const HttpRequestImplPtr &req)
{
// Find simple controller
std::string loweredPath(req->path().length(), 0);
std::transform(req->path().begin(),
req->path().end(),
loweredPath.begin(),
[](unsigned char c) { return tolower(c); });
{
auto it = simpleCtrlMap_.find(loweredPath);
if (it != simpleCtrlMap_.end())
{
auto &ctrlInfo = it->second;
req->setMatchedPathPattern(it->first);
auto &binder = ctrlInfo.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
return {RouteResult::Success, binder};
}
}
// Find http controller
HttpControllerRouterItem *routerItemPtr = nullptr;
std::smatch result;
auto it = ctrlMap_.find(loweredPath);
// Try to find a controller in the hash map. If can't linear search
// with regex.
if (it != ctrlMap_.end())
{
routerItemPtr = &it->second;
}
else
{
for (auto &item : ctrlVector_)
{
const auto &ctrlRegex = item.regex_;
if (item.binders_[req->method()] &&
std::regex_match(req->path(), result, ctrlRegex))
{
routerItemPtr = &item;
break;
}
}
}
// No handler found
if (!routerItemPtr)
{
return {RouteResult::NotFound, nullptr};
}
HttpControllerRouterItem &routerItem = *routerItemPtr;
assert(Invalid > req->method());
req->setMatchedPathPattern(routerItem.pathPattern_);
auto &binder = routerItem.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
std::vector<std::string> params;
for (size_t j = 1; j < result.size(); ++j)
{
if (!result[j].matched)
continue;
size_t place = j;
if (j <= binder->parameterPlaces_.size())
{
place = binder->parameterPlaces_[j - 1];
}
if (place > params.size())
params.resize(place);
params[place - 1] = result[j].str();
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
}
if (!binder->queryParametersPlaces_.empty())
{
auto &queryPara = req->getParameters();
for (const auto &paraPlace : binder->queryParametersPlaces_)
{
auto place = paraPlace.second;
if (place > params.size())
params.resize(place);
auto iter = queryPara.find(paraPlace.first);
if (iter != queryPara.end())
{
params[place - 1] = iter->second;
}
else
{
params[place - 1] = std::string{};
}
}
}
req->setRoutingParameters(std::move(params));
return {RouteResult::Success, binder};
}
RouteResult HttpControllersRouter::routeWs(const HttpRequestImplPtr &req)
{
std::string wsKey = req->getHeaderBy("sec-websocket-key");
if (!wsKey.empty())
{
std::string pathLower(req->path().length(), 0);
std::transform(req->path().begin(),
req->path().end(),
pathLower.begin(),
[](unsigned char c) { return tolower(c); });
auto iter = wsCtrlMap_.find(pathLower);
if (iter != wsCtrlMap_.end())
{
auto &ctrlInfo = iter->second;
req->setMatchedPathPattern(iter->first);
auto &binder = ctrlInfo.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
return {RouteResult::Success, binder};
}
else
{
for (auto &ctrlInfo : wsCtrlVector_)
{
auto const &wsCtrlRegex = ctrlInfo.regex_;
std::smatch result;
if (std::regex_match(req->path(), result, wsCtrlRegex))
{
req->setMatchedPathPattern(ctrlInfo.pathPattern_);
auto &binder = ctrlInfo.binders_[req->method()];
if (!binder)
{
return {RouteResult::MethodNotAllowed, nullptr};
}
return {RouteResult::Success, binder};
}
}
}
}
return {RouteResult::NotFound, nullptr};
}
void HttpControllersRouter::addRegexCtrlBinder(
const std::shared_ptr<HttpControllerBinder> &binderPtr,
const std::string &pathPattern,
const std::string &pathParameterPattern,
const std::vector<HttpMethod> &methods)
{
HttpControllerRouterItem *routerItemPtr;
auto existRouter = std::find_if(ctrlVector_.begin(),
ctrlVector_.end(),
[&pathParameterPattern](const auto &item) {
return item.pathParameterPattern_ ==
pathParameterPattern;
});
if (existRouter == ctrlVector_.end())
{
struct HttpControllerRouterItem router;
router.pathParameterPattern_ = pathParameterPattern;
router.pathPattern_ = pathPattern;
ctrlVector_.push_back(std::move(router));
routerItemPtr = &ctrlVector_.back();
}
else
{
routerItemPtr = &(*existRouter);
}
addCtrlBinderToRouterItem(binderPtr, *routerItemPtr, methods);
}
+109
View File
@@ -0,0 +1,109 @@
/**
*
* HttpControllersRouter.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "impl_forwards.h"
#include "ControllerBinderBase.h"
#include <trantor/utils/NonCopyable.h>
#include <memory>
#include <regex>
#include <string>
#include <vector>
#include <unordered_map>
namespace drogon
{
class HttpControllerBinder;
class HttpSimpleControllerBinder;
struct WebsocketControllerBinder;
class HttpControllersRouter : public trantor::NonCopyable
{
public:
static HttpControllersRouter &instance()
{
static HttpControllersRouter inst;
return inst;
}
void init(const std::vector<trantor::EventLoop *> &ioLoops);
// clean all resources
void reset();
void registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints);
void registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints);
void registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints);
void addHttpPath(const std::string &path,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName = "");
void addHttpRegex(const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::string &handlerName = "");
RouteResult route(const HttpRequestImplPtr &req);
RouteResult routeWs(const HttpRequestImplPtr &req);
std::vector<HttpHandlerInfo> getHandlersInfo() const;
private:
void addRegexCtrlBinder(
const std::shared_ptr<HttpControllerBinder> &binderPtr,
const std::string &pathPattern,
const std::string &pathParameterPattern,
const std::vector<HttpMethod> &methods);
struct SimpleControllerRouterItem
{
std::shared_ptr<HttpSimpleControllerBinder> binders_[Invalid]{nullptr};
};
struct HttpControllerRouterItem
{
std::string pathParameterPattern_;
std::string pathPattern_;
std::regex regex_;
std::shared_ptr<HttpControllerBinder> binders_[Invalid]{nullptr};
};
struct WebSocketControllerRouterItem
{
std::shared_ptr<WebsocketControllerBinder> binders_[Invalid]{nullptr};
};
struct RegExWebSocketControllerRouterItem
{
std::string pathPattern_;
std::regex regex_;
std::shared_ptr<WebsocketControllerBinder> binders_[Invalid]{nullptr};
};
std::unordered_map<std::string, SimpleControllerRouterItem> simpleCtrlMap_;
std::unordered_map<std::string, HttpControllerRouterItem> ctrlMap_;
std::vector<HttpControllerRouterItem> ctrlVector_; // for regexp path
std::unordered_map<std::string, WebSocketControllerRouterItem> wsCtrlMap_;
std::vector<RegExWebSocketControllerRouterItem> wsCtrlVector_;
};
} // namespace drogon
+218
View File
@@ -0,0 +1,218 @@
/**
*
* @file HttpFileImpl.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpFileImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/MultiPart.h>
#include <drogon/utils/Utilities.h>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <filesystem>
using namespace drogon;
int HttpFileImpl::save() const noexcept
{
return save(HttpAppFrameworkImpl::instance().getUploadPath());
}
int HttpFileImpl::save(const std::string &path) const noexcept
{
assert(!path.empty());
if (fileName_.empty())
return -1;
std::filesystem::path fsUploadDir(utils::toNativePath(path));
if (fsUploadDir.is_absolute())
{ // do nothing
}
else if ((!fsUploadDir.has_parent_path() ||
(fsUploadDir.begin()->string() != "." &&
fsUploadDir.begin()->string() != "..")))
{
fsUploadDir = utils::toNativePath(
HttpAppFrameworkImpl::instance().getUploadPath()) /
fsUploadDir;
}
else
{
fsUploadDir = std::filesystem::current_path() / fsUploadDir;
}
fsUploadDir = std::filesystem::weakly_canonical(fsUploadDir);
if (!std::filesystem::exists(fsUploadDir))
{
LOG_TRACE << "create path:" << fsUploadDir;
std::error_code err;
std::filesystem::create_directories(fsUploadDir, err);
if (err)
{
LOG_SYSERR;
return -1;
}
}
std::filesystem::path fsSaveToPath(std::filesystem::weakly_canonical(
fsUploadDir / utils::toNativePath(fileName_)));
LOG_TRACE << "save to path:" << fsSaveToPath;
if (!std::equal(fsUploadDir.begin(),
fsUploadDir.end(),
fsSaveToPath.begin()))
{
LOG_ERROR
<< "Attempt writing outside of upload directory detected. Path: "
<< fileName_;
return -1;
}
return saveTo(fsSaveToPath);
}
int HttpFileImpl::saveAs(const std::string &fileName) const noexcept
{
assert(!fileName.empty());
std::filesystem::path fsFileName(utils::toNativePath(fileName));
if (!fsFileName.is_absolute() && (!fsFileName.has_parent_path() ||
(fsFileName.begin()->string() != "." &&
fsFileName.begin()->string() != "..")))
{
std::filesystem::path fsUploadPath(utils::toNativePath(
HttpAppFrameworkImpl::instance().getUploadPath()));
fsFileName = fsUploadPath / fsFileName;
}
if (fsFileName.has_parent_path() &&
!std::filesystem::exists(fsFileName.parent_path()))
{
LOG_TRACE << "create path:" << fsFileName.parent_path();
std::error_code err;
std::filesystem::create_directories(fsFileName.parent_path(), err);
if (err)
{
LOG_SYSERR;
return -1;
}
}
return saveTo(fsFileName);
}
int HttpFileImpl::saveTo(
const std::filesystem::path &pathAndFileName) const noexcept
{
LOG_TRACE << "save uploaded file:" << pathAndFileName;
auto wPath = utils::toNativePath(pathAndFileName.native());
std::ofstream file(wPath, std::ios::binary);
if (file.is_open())
{
file.write(fileContent_.data(), fileContent_.size());
file.close();
return 0;
}
else
{
LOG_ERROR << "save failed!";
return -1;
}
}
std::string HttpFileImpl::getMd5() const noexcept
{
return utils::getMd5(fileContent_.data(), fileContent_.size());
}
std::string HttpFileImpl::getSha256() const noexcept
{
return utils::getSha256(fileContent_.data(), fileContent_.size());
}
std::string HttpFileImpl::getSha3() const noexcept
{
return utils::getSha3(fileContent_.data(), fileContent_.size());
}
const std::string &HttpFile::getFileName() const noexcept
{
return implPtr_->getFileName();
}
void HttpFile::setFileName(const std::string &fileName) noexcept
{
implPtr_->setFileName(fileName);
}
std::string_view HttpFile::getFileExtension() const noexcept
{
return implPtr_->getFileExtension();
}
FileType HttpFile::getFileType() const noexcept
{
return implPtr_->getFileType();
}
void HttpFile::setFile(const char *data, size_t length) noexcept
{
implPtr_->setFile(data, length);
}
int HttpFile::save() const noexcept
{
return implPtr_->save();
}
int HttpFile::save(const std::string &path) const noexcept
{
return implPtr_->save(path);
}
int HttpFile::saveAs(const std::string &fileName) const noexcept
{
return implPtr_->saveAs(fileName);
}
size_t HttpFile::fileLength() const noexcept
{
return implPtr_->fileLength();
}
drogon::ContentType HttpFile::getContentType() const noexcept
{
return implPtr_->getContentType();
}
const char *HttpFile::fileData() const noexcept
{
return implPtr_->fileData();
}
std::string HttpFile::getMd5() const noexcept
{
return implPtr_->getMd5();
}
const std::string &HttpFile::getContentTransferEncoding() const noexcept
{
return implPtr_->getContentTransferEncoding();
}
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept
: implPtr_(std::move(implPtr))
{
}
const std::string &HttpFile::getItemName() const noexcept
{
return implPtr_->getItemName();
}
+175
View File
@@ -0,0 +1,175 @@
/**
*
* @file HttpFileImpl.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "HttpUtils.h"
#include <drogon/HttpRequest.h>
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <filesystem>
#include <string_view>
namespace drogon
{
class HttpFileImpl
{
public:
/// Return the file name;
const std::string &getFileName() const noexcept
{
return fileName_;
}
/// Set the file name, usually called by the MultiPartParser parser.
void setFileName(const std::string &fileName) noexcept
{
fileName_ = fileName;
}
void setFileName(std::string &&fileName) noexcept
{
fileName_ = std::move(fileName);
}
/// Return the file extension;
std::string_view getFileExtension() const noexcept
{
return drogon::getFileExtension(fileName_);
}
/// Set the contents of the file, usually called by the MultiPartParser
/// parser.
void setFile(const char *data, size_t length) noexcept
{
fileContent_ = std::string_view{data, length};
}
/// Save the file to the file system.
/**
* The folder saving the file is app().getUploadPath().
* The full path is app().getUploadPath()+"/"+this->getFileName()
*/
int save() const noexcept;
/// Save the file to @param path
/**
* @param path if the parameter is prefixed with "/", "./" or "../", or is
* "." or "..", the full path is path+"/"+this->getFileName(),
* otherwise the file is saved as
* app().getUploadPath()+"/"+path+"/"+this->getFileName()
*/
int save(const std::string &path) const noexcept;
/// Save the file to file system with a new name
/**
* @param fileName if the parameter isn't prefixed with "/", "./" or "../",
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
* is saved as the filename
*/
int saveAs(const std::string &fileName) const noexcept;
/// Return the file length.
size_t fileLength() const noexcept
{
return fileContent_.length();
}
const char *fileData() const noexcept
{
return fileContent_.data();
}
const std::string_view &fileContent() const noexcept
{
return fileContent_;
}
/// Return the name of the item in multiple parts.
const std::string &getItemName() const noexcept
{
return itemName_;
}
void setItemName(const std::string &itemName) noexcept
{
itemName_ = itemName;
}
void setItemName(std::string &&itemName) noexcept
{
itemName_ = std::move(itemName);
}
/// Return the type of file.
FileType getFileType() const noexcept
{
auto ft = drogon::getFileType(contentType_);
if ((ft != FT_UNKNOWN) && (ft != FT_CUSTOM))
return ft;
return parseFileType(getFileExtension());
}
/// Return md5 hash of the file
std::string getMd5() const noexcept;
// Return sha1 hash of the file
std::string getSha256() const noexcept;
// Return sha512 hash of the file
std::string getSha3() const noexcept;
// int saveTo(const std::string &pathAndFileName) const;
int saveTo(const std::filesystem::path &pathAndFileName) const noexcept;
void setRequest(const HttpRequestPtr &req) noexcept
{
requestPtr_ = req;
}
drogon::ContentType getContentType() const noexcept
{
return contentType_;
}
void setContentType(drogon::ContentType contentType) noexcept
{
contentType_ = contentType;
}
void setContentTransferEncoding(
const std::string &contentTransferEncoding) noexcept
{
transferEncoding_ = contentTransferEncoding;
}
void setContentTransferEncoding(
std::string &&contentTransferEncoding) noexcept
{
transferEncoding_ = std::move(contentTransferEncoding);
}
const std::string &getContentTransferEncoding() const noexcept
{
return transferEncoding_;
}
private:
std::string fileName_;
std::string itemName_;
std::string transferEncoding_;
std::string_view fileContent_;
HttpRequestPtr requestPtr_;
drogon::ContentType contentType_{drogon::CT_NONE};
};
} // namespace drogon
@@ -0,0 +1,31 @@
/**
*
* HttpFileUploadRequest.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpFileUploadRequest.h"
#include <drogon/UploadFile.h>
#include <drogon/utils/Utilities.h>
using namespace drogon;
HttpFileUploadRequest::HttpFileUploadRequest(
const std::vector<UploadFile> &files)
: HttpRequestImpl(nullptr),
boundary_(utils::genRandomString(32)),
files_(files)
{
setMethod(drogon::Post);
setVersion(drogon::Version::kHttp11);
setContentType("multipart/form-data; boundary=" + boundary_);
contentType_ = CT_MULTIPART_FORM_DATA;
}
+42
View File
@@ -0,0 +1,42 @@
/**
*
* HttpFileUploadRequest.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "HttpRequestImpl.h"
#include <string>
#include <vector>
namespace drogon
{
class HttpFileUploadRequest : public HttpRequestImpl
{
public:
const std::string &boundary() const
{
return boundary_;
}
const std::vector<UploadFile> &files() const
{
return files_;
}
explicit HttpFileUploadRequest(const std::vector<UploadFile> &files);
private:
std::string boundary_;
std::vector<UploadFile> files_;
};
} // namespace drogon
+145
View File
@@ -0,0 +1,145 @@
/**
*
* HttpMessageBody.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <string_view>
#include <memory>
#include <string>
namespace drogon
{
class HttpMessageBody
{
public:
enum class BodyType
{
kNone = 0,
kString,
kStringView
};
BodyType bodyType()
{
return type_;
}
virtual const char *data() const
{
return nullptr;
}
virtual char *data()
{
return nullptr;
}
virtual size_t length() const
{
return 0;
}
virtual std::string_view getString() const = 0;
virtual void append(const char * /*buf*/, size_t /*len*/)
{
}
virtual ~HttpMessageBody()
{
}
protected:
BodyType type_{BodyType::kNone};
};
class HttpMessageStringBody : public HttpMessageBody
{
public:
HttpMessageStringBody()
{
type_ = BodyType::kString;
}
HttpMessageStringBody(const std::string &body) : body_(body)
{
type_ = BodyType::kString;
}
HttpMessageStringBody(std::string &&body) : body_(std::move(body))
{
type_ = BodyType::kString;
}
const char *data() const override
{
return body_.data();
}
char *data() override
{
return const_cast<char *>(body_.data());
}
size_t length() const override
{
return body_.length();
}
std::string_view getString() const override
{
return std::string_view{body_.data(), body_.length()};
}
void append(const char *buf, size_t len) override
{
body_.append(buf, len);
}
private:
std::string body_;
};
class HttpMessageStringViewBody : public HttpMessageBody
{
public:
HttpMessageStringViewBody(const char *buf, size_t len) : body_(buf, len)
{
type_ = BodyType::kStringView;
}
const char *data() const override
{
return body_.data();
}
char *data() override
{
return const_cast<char *>(body_.data());
}
size_t length() const override
{
return body_.length();
}
std::string_view getString() const override
{
return body_;
}
private:
std::string_view body_;
};
} // namespace drogon
File diff suppressed because it is too large Load Diff
+750
View File
@@ -0,0 +1,750 @@
/**
*
* @file HttpRequestImpl.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "HttpUtils.h"
#include "CacheFile.h"
#include "impl_forwards.h"
#include <drogon/utils/Utilities.h>
#include <drogon/HttpRequest.h>
#include <drogon/RequestStream.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/InetAddress.h>
#include <trantor/net/Certificate.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/TcpConnection.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <future>
#include <unordered_map>
#include <assert.h>
#include <stdio.h>
namespace drogon
{
enum class StreamDecompressStatus
{
TooLarge,
DecompressError,
NotSupported,
Ok
};
enum class ReqStreamStatus
{
None = 0,
Open = 1,
Finish = 2,
Error = 3
};
class HttpRequestImpl : public HttpRequest
{
public:
friend class HttpRequestParser;
explicit HttpRequestImpl(trantor::EventLoop *loop)
: creationDate_(trantor::Date::now()), loop_(loop)
{
}
void reset()
{
method_ = Invalid;
previousMethod_ = Invalid;
version_ = Version::kUnknown;
flagForParsingJson_ = false;
headers_.clear();
cookies_.clear();
contentLengthHeaderValue_.reset();
realContentLength_ = 0;
flagForParsingParameters_ = false;
path_.clear();
originalPath_.clear();
pathEncode_ = true;
matchedPathPattern_ = "";
query_.clear();
parameters_.clear();
jsonPtr_.reset();
sessionPtr_.reset();
attributesPtr_.reset();
cacheFilePtr_.reset();
expectPtr_.reset();
content_.clear();
contentType_ = CT_TEXT_PLAIN;
flagForParsingContentType_ = false;
contentTypeString_.clear();
keepAlive_ = true;
jsonParsingErrorPtr_.reset();
peerCertificate_.reset();
routingParams_.clear();
// stream
streamStatus_ = ReqStreamStatus::None;
streamReaderPtr_.reset();
streamFinishCb_ = nullptr;
streamExceptionPtr_ = nullptr;
startProcessing_ = false;
connPtr_.reset();
}
trantor::EventLoop *getLoop()
{
return loop_;
}
void setVersion(Version v)
{
version_ = v;
if (version_ == Version::kHttp10)
{
keepAlive_ = false;
}
}
Version version() const override
{
return version_;
}
const char *versionString() const override;
bool setMethod(const char *start, const char *end);
void setSecure(bool secure)
{
isOnSecureConnection_ = secure;
}
void setMethod(const HttpMethod method) override
{
previousMethod_ = method_;
method_ = method;
return;
}
HttpMethod method() const override
{
return method_;
}
bool isHead() const override
{
return (method_ == HttpMethod::Head) ||
((method_ == HttpMethod::Get) &&
(previousMethod_ == HttpMethod::Head));
}
const char *methodString() const override;
void setPath(const char *start, const char *end)
{
if (utils::needUrlDecoding(start, end))
{
originalPath_.append(start, end);
path_ = utils::urlDecode(start, end);
}
else
{
path_.append(start, end);
}
}
const std::vector<std::string> &getRoutingParameters() const override
{
return routingParams_;
}
void setRoutingParameters(std::vector<std::string> &&params) override
{
routingParams_ = std::move(params);
}
void setPath(const std::string &path) override
{
path_ = path;
}
void setPath(std::string &&path) override
{
path_ = std::move(path);
}
void setPathEncode(bool pathEncode) override
{
pathEncode_ = pathEncode;
}
const SafeStringMap<std::string> &parameters() const override
{
parseParametersOnce();
return parameters_;
}
const std::string &getParameter(const std::string &key) const override
{
static const std::string defaultVal;
parseParametersOnce();
auto iter = parameters_.find(key);
if (iter != parameters_.end())
return iter->second;
return defaultVal;
}
const std::string &path() const override
{
return path_;
}
const std::string &getOriginalPath() const override
{
return originalPath_.empty() ? path_ : originalPath_;
}
void setQuery(const char *start, const char *end)
{
query_.assign(start, end);
}
void setQuery(const std::string &query)
{
query_ = query;
}
std::string_view bodyView() const
{
if (isStreamMode())
{
return emptySv_;
}
if (cacheFilePtr_)
{
return cacheFilePtr_->getStringView();
}
return content_;
}
const char *bodyData() const override
{
if (isStreamMode())
{
return emptySv_.data();
}
if (cacheFilePtr_)
{
return cacheFilePtr_->getStringView().data();
}
return content_.data();
}
size_t bodyLength() const override
{
if (isStreamMode())
{
return emptySv_.length();
}
if (cacheFilePtr_)
{
return cacheFilePtr_->getStringView().length();
}
return content_.length();
}
void appendToBody(const char *data, size_t length);
void reserveBodySize(size_t length);
std::string_view queryView() const
{
return query_;
}
std::string_view contentView() const
{
if (isStreamMode())
{
return emptySv_;
}
if (cacheFilePtr_)
return cacheFilePtr_->getStringView();
return content_;
}
const std::string &query() const override
{
return query_;
}
const trantor::InetAddress &peerAddr() const override
{
return peer_;
}
const trantor::InetAddress &localAddr() const override
{
return local_;
}
const trantor::Date &creationDate() const override
{
return creationDate_;
}
const trantor::CertificatePtr &peerCertificate() const override
{
return peerCertificate_;
}
void setCreationDate(const trantor::Date &date)
{
creationDate_ = date;
}
void setPeerAddr(const trantor::InetAddress &peer)
{
peer_ = peer;
}
void setLocalAddr(const trantor::InetAddress &local)
{
local_ = local;
}
void setPeerCertificate(const trantor::CertificatePtr &cert)
{
peerCertificate_ = cert;
}
void setConnectionPtr(const std::shared_ptr<trantor::TcpConnection> &ptr)
{
connPtr_ = ptr;
}
void addHeader(const char *start, const char *colon, const char *end);
void removeHeader(std::string key) override
{
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
return tolower(c);
});
removeHeaderBy(key);
}
void removeHeaderBy(const std::string &lowerKey)
{
headers_.erase(lowerKey);
}
const std::string &getHeader(std::string field) const override
{
std::transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
return getHeaderBy(field);
}
const std::string &getHeaderBy(const std::string &lowerField) const
{
static const std::string defaultVal;
auto it = headers_.find(lowerField);
if (it != headers_.end())
{
return it->second;
}
return defaultVal;
}
const std::string &getCookie(const std::string &field) const override
{
static const std::string defaultVal;
auto it = cookies_.find(field);
if (it != cookies_.end())
{
return it->second;
}
return defaultVal;
}
const SafeStringMap<std::string> &headers() const override
{
return headers_;
}
const SafeStringMap<std::string> &cookies() const override
{
return cookies_;
}
std::optional<size_t> getContentLengthHeaderValue() const
{
return contentLengthHeaderValue_;
}
size_t realContentLength() const override
{
return realContentLength_;
}
void setParameter(const std::string &key, const std::string &value) override
{
flagForParsingParameters_ = true;
parameters_[key] = value;
}
const std::string &getContent() const
{
return content_;
}
void swap(HttpRequestImpl &that) noexcept;
void setContent(const std::string &content)
{
content_ = content;
}
void setBody(const std::string &body) override
{
content_ = body;
}
void setBody(std::string &&body) override
{
content_ = std::move(body);
}
void addHeader(std::string field, const std::string &value) override
{
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = value;
}
void addHeader(std::string field, std::string &&value) override
{
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = std::move(value);
}
void addCookie(std::string key, std::string value) override
{
cookies_[std::move(key)] = std::move(value);
}
void setPassThrough(bool flag) override
{
passThrough_ = flag;
}
bool passThrough() const
{
return passThrough_;
}
void appendToBuffer(trantor::MsgBuffer *output) const;
const SessionPtr &session() const override
{
return sessionPtr_;
}
void setSession(const SessionPtr &session)
{
sessionPtr_ = session;
}
const AttributesPtr &attributes() const override
{
if (!attributesPtr_)
{
attributesPtr_ = std::make_shared<Attributes>();
}
return attributesPtr_;
}
const std::shared_ptr<Json::Value> &jsonObject() const override
{
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingJson_)
{
flagForParsingJson_ = true;
parseJson();
}
return jsonPtr_;
}
void setCustomContentTypeString(const std::string &type) override
{
contentType_ = CT_NONE;
flagForParsingContentType_ = true;
bool haveHeader = type.find("content-type: ") == 0;
bool haveCRLF = type.rfind("\r\n") == type.size() - 2;
size_t endOffset = 0;
if (haveHeader)
endOffset += 14;
if (haveCRLF)
endOffset += 2;
contentTypeString_ = std::string(type.begin() + (haveHeader ? 14 : 0),
type.end() - endOffset);
}
void setContentTypeCode(const ContentType type) override
{
contentType_ = type;
flagForParsingContentType_ = true;
auto &typeStr = contentTypeToMime(type);
setContentType(std::string(typeStr.data(), typeStr.length()));
}
void setContentTypeString(const char *typeString,
size_t typeStringLength) override;
// void setContentTypeCodeAndCharacterSet(ContentType type, const
// std::string &charSet = "utf-8") override
// {
// contentType_ = type;
// setContentType(webContentTypeAndCharsetToString(type, charSet));
// }
ContentType contentType() const override
{
parseContentTypeAndString();
return contentType_;
}
const char *matchedPathPatternData() const override
{
return matchedPathPattern_.data();
}
size_t matchedPathPatternLength() const override
{
return matchedPathPattern_.length();
}
void setMatchedPathPattern(const std::string &pathPattern)
{
matchedPathPattern_ = pathPattern;
}
const std::string &expect() const
{
static const std::string none{""};
if (expectPtr_)
return *expectPtr_;
return none;
}
bool keepAlive() const
{
return keepAlive_;
}
bool connected() const noexcept override
{
if (auto conn = connPtr_.lock())
{
return conn->connected();
}
return false;
}
const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept override
{
return connPtr_;
}
bool isOnSecureConnection() const noexcept override
{
return isOnSecureConnection_;
}
const std::string &getJsonError() const override
{
static const std::string none{""};
if (jsonParsingErrorPtr_)
return *jsonParsingErrorPtr_;
return none;
}
StreamDecompressStatus decompressBody();
// Stream mode api
ReqStreamStatus streamStatus() const
{
return streamStatus_;
}
bool isStreamMode() const
{
return streamStatus_ > ReqStreamStatus::None;
}
void streamStart();
void streamFinish();
void streamError(std::exception_ptr ex);
void setStreamReader(RequestStreamReaderPtr reader);
void waitForStreamFinish(std::function<void()> &&cb);
void quitStreamMode();
void startProcessing()
{
startProcessing_ = true;
}
bool isProcessingStarted() const
{
return startProcessing_;
}
~HttpRequestImpl() override;
protected:
friend class HttpRequest;
void setContentType(const std::string &contentType)
{
contentTypeString_ = contentType;
}
void setContentType(std::string &&contentType)
{
contentTypeString_ = std::move(contentType);
}
void parseContentTypeAndString() const
{
if (!flagForParsingContentType_)
{
flagForParsingContentType_ = true;
auto &contentTypeString = getHeaderBy("content-type");
if (contentTypeString == "")
{
contentType_ = CT_NONE;
}
else
{
auto pos = contentTypeString.find(';');
if (pos != std::string::npos)
{
contentType_ = parseContentType(
std::string_view(contentTypeString.data(), pos));
}
else
{
contentType_ =
parseContentType(std::string_view(contentTypeString));
}
if (contentType_ == CT_NONE)
contentType_ = CT_CUSTOM;
contentTypeString_ = contentTypeString;
}
}
}
private:
void parseParameters() const;
void parseParametersOnce() const
{
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingParameters_)
{
flagForParsingParameters_ = true;
parseParameters();
}
}
void createTmpFile();
void parseJson() const;
#ifdef USE_BROTLI
StreamDecompressStatus decompressBodyBrotli() noexcept;
#endif
StreamDecompressStatus decompressBodyGzip() noexcept;
static constexpr const std::string_view emptySv_{""};
mutable bool flagForParsingParameters_{false};
mutable bool flagForParsingJson_{false};
HttpMethod method_{Invalid};
HttpMethod previousMethod_{Invalid};
Version version_{Version::kUnknown};
std::string path_;
/// Contains the encoded `path_` if and only if `path_` is set in encoded
/// form. If path is in a normal form and needed no decoding, then this will
/// be empty, as we do not need to store a duplicate.
std::string originalPath_;
bool pathEncode_{true};
std::string_view matchedPathPattern_{""};
std::string query_;
SafeStringMap<std::string> headers_;
SafeStringMap<std::string> cookies_;
std::optional<size_t> contentLengthHeaderValue_;
size_t realContentLength_{0};
mutable SafeStringMap<std::string> parameters_;
mutable std::shared_ptr<Json::Value> jsonPtr_;
SessionPtr sessionPtr_;
mutable AttributesPtr attributesPtr_;
trantor::InetAddress peer_;
trantor::InetAddress local_;
trantor::Date creationDate_;
trantor::CertificatePtr peerCertificate_;
std::unique_ptr<CacheFile> cacheFilePtr_;
mutable std::unique_ptr<std::string> jsonParsingErrorPtr_;
std::unique_ptr<std::string> expectPtr_;
bool keepAlive_{true};
bool isOnSecureConnection_{false};
bool passThrough_{false};
std::vector<std::string> routingParams_;
ReqStreamStatus streamStatus_{ReqStreamStatus::None};
std::function<void()> streamFinishCb_;
RequestStreamReaderPtr streamReaderPtr_;
std::exception_ptr streamExceptionPtr_;
bool startProcessing_{false};
std::weak_ptr<trantor::TcpConnection> connPtr_;
protected:
std::string content_;
trantor::EventLoop *loop_;
mutable ContentType contentType_{CT_TEXT_PLAIN};
mutable bool flagForParsingContentType_{false};
mutable std::string contentTypeString_;
};
using HttpRequestImplPtr = std::shared_ptr<HttpRequestImpl>;
inline void swap(HttpRequestImpl &one, HttpRequestImpl &two) noexcept
{
one.swap(two);
}
} // namespace drogon
+491
View File
@@ -0,0 +1,491 @@
/**
*
* HttpRequestParser.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpRequestParser.h"
#include <drogon/HttpTypes.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <iostream>
#include "HttpAppFrameworkImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "HttpUtils.h"
using namespace trantor;
using namespace drogon;
static constexpr size_t CRLF_LEN = 2; // strlen("crlf")
static constexpr size_t METHOD_MAX_LEN = 7; // strlen("OPTIONS")
static constexpr size_t TRUNK_LEN_MAX_LEN = 16; // 0xFFFFFFFF,FFFFFFFF
HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr)
: status_(HttpRequestParseStatus::kExpectMethod),
loop_(connPtr->getLoop()),
conn_(connPtr)
{
}
bool HttpRequestParser::processRequestLine(const char *begin, const char *end)
{
bool succeed = false;
const char *start = begin;
const char *space = std::find(start, end, ' ');
if (space != end)
{
const char *slash = std::find(start, space, '/');
if (slash != start && slash + 1 < space && *(slash + 1) == '/')
{
// scheme precedents
slash = std::find(slash + 2, space, '/');
}
const char *question = std::find(slash, space, '?');
if (slash != space)
{
request_->setPath(slash, question);
}
else
{
// An empty abs_path is equivalent to an abs_path of "/"
request_->setPath("/");
}
if (question != space)
{
request_->setQuery(question + 1, space);
}
start = space + 1;
succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1.");
if (succeed)
{
if (*(end - 1) == '1')
{
request_->setVersion(Version::kHttp11);
}
else if (*(end - 1) == '0')
{
request_->setVersion(Version::kHttp10);
}
else
{
succeed = false;
}
}
}
return succeed;
}
HttpRequestImplPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr)
{
return std::shared_ptr<HttpRequestImpl>(
ptr, [weakPtr = weak_from_this()](HttpRequestImpl *p) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
if (thisPtr->loop_->isInLoopThread())
{
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
}
else
{
auto &loop = thisPtr->loop_;
loop->queueInLoop([thisPtr = std::move(thisPtr), p]() {
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
});
}
}
else
{
delete p;
}
});
}
void HttpRequestParser::reset()
{
assert(loop_->isInLoopThread());
remainContentLength_ = 0;
status_ = HttpRequestParseStatus::kExpectMethod;
if (requestsPool_.empty())
{
request_ = makeRequestForPool(new HttpRequestImpl(loop_));
}
else
{
auto req = std::move(requestsPool_.back());
requestsPool_.pop_back();
request_ = std::move(req);
request_->setCreationDate(trantor::Date::now());
}
}
/**
* @return return -HttpStatusCode if encounters any http errors in request
* @return return -1 if encounters any other errors in request
* @return return 0 if request is not ready
* @return return 1 if request is ready
* @return return 2 if request is ready and entering stream mode
* @return return 3 if request header is ready and entering stream mode
*/
int HttpRequestParser::parseRequest(MsgBuffer *buf)
{
while (true)
{
switch (status_)
{
case (HttpRequestParseStatus::kExpectMethod):
{
auto *space = std::find(buf->peek(),
(const char *)buf->beginWrite(),
' ');
// no space in buffer
if (space == buf->beginWrite())
{
if (buf->readableBytes() > METHOD_MAX_LEN)
{
return -k400BadRequest;
}
return 0;
}
// try read method
if (!request_->setMethod(buf->peek(), space))
{
return -k405MethodNotAllowed;
}
status_ = HttpRequestParseStatus::kExpectRequestLine;
buf->retrieveUntil(space + 1);
continue;
}
case HttpRequestParseStatus::kExpectRequestLine:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for request line is 64K bytes. response
/// k414RequestURITooLarge
/// TODO: Make this configurable?
return -k414RequestURITooLarge;
}
return 0;
}
if (!processRequestLine(buf->peek(), crlf))
{
// error
return -k400BadRequest;
}
buf->retrieveUntil(crlf + CRLF_LEN);
status_ = HttpRequestParseStatus::kExpectHeaders;
continue;
}
case HttpRequestParseStatus::kExpectHeaders:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for every request header is 64K bytes;
/// TODO: Make this configurable?
return -k400BadRequest;
}
return 0;
}
const char *colon = std::find(buf->peek(), crlf, ':');
// found colon
if (colon != crlf)
{
request_->addHeader(buf->peek(), colon, crlf);
buf->retrieveUntil(crlf + CRLF_LEN);
continue;
}
buf->retrieveUntil(crlf + CRLF_LEN);
// end of headers
// We might want a kProcessHeaders status for code readability
// and maintainability.
// process header information
auto &len = request_->getHeaderBy("content-length");
if (!len.empty())
{
try
{
remainContentLength_ =
static_cast<size_t>(std::stoull(len));
}
catch (...)
{
return -k400BadRequest;
}
request_->contentLengthHeaderValue_ = remainContentLength_;
if (remainContentLength_ == 0)
{
// content-length = 0, request is over.
status_ = HttpRequestParseStatus::kGotAll;
}
else
{
status_ = HttpRequestParseStatus::kExpectBody;
}
}
else
{
const std::string &encode =
request_->getHeaderBy("transfer-encoding");
if (encode.empty())
{
// no content-length and no transfer-encoding,
// request is over.
status_ = HttpRequestParseStatus::kGotAll;
}
else if (encode == "chunked")
{
status_ = HttpRequestParseStatus::kExpectChunkLen;
}
else
{
return -k501NotImplemented;
}
}
// Check max body size
if (remainContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
return -k413RequestEntityTooLarge;
}
// Check expect:100-continue
auto &expect = request_->expect();
if (expect == "100-continue" &&
request_->getVersion() >= Version::kHttp11)
{
if (remainContentLength_ == 0)
{
// error
return -k400BadRequest;
}
else
{
// rfc2616-8.2.3
// TODO: consider adding an AOP for expect header
auto connPtr = conn_.lock(); // ugly
if (!connPtr)
{
return -1;
}
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k100Continue);
auto httpString =
static_cast<HttpResponseImpl *>(resp.get())
->renderToBuffer();
connPtr->send(std::move(*httpString));
}
}
else if (!expect.empty())
{
LOG_WARN << "417ExpectationFailed for \"" << expect << "\"";
return -k417ExpectationFailed;
}
assert(status_ == HttpRequestParseStatus::kGotAll ||
status_ == HttpRequestParseStatus::kExpectBody ||
status_ == HttpRequestParseStatus::kExpectChunkLen);
if (app().isRequestStreamEnabled())
{
request_->streamStart();
if (status_ == HttpRequestParseStatus::kGotAll)
{
++requestsCounter_;
return 2;
}
else
{
return 3;
}
}
// Reserve space for full body in non-stream mode.
// For stream mode requests that match a non-stream handler,
// we will reserve full body before waitForStreamFinish().
if (remainContentLength_)
{
request_->reserveBodySize(remainContentLength_);
}
continue;
}
case HttpRequestParseStatus::kExpectBody:
{
size_t bytesToConsume =
remainContentLength_ <= buf->readableBytes()
? remainContentLength_
: buf->readableBytes();
if (bytesToConsume)
{
request_->appendToBody(buf->peek(), bytesToConsume);
buf->retrieve(bytesToConsume);
remainContentLength_ -= bytesToConsume;
}
if (remainContentLength_ == 0)
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
return 1;
}
// readableBytes() == 0, function should return.
return 0;
}
case HttpRequestParseStatus::kExpectChunkLen:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() > TRUNK_LEN_MAX_LEN + CRLF_LEN)
{
return -k400BadRequest;
}
return 0;
}
// chunk length line
std::string len(buf->peek(), crlf - buf->peek());
char *end;
currentChunkLength_ = strtol(len.c_str(), &end, 16);
if (currentChunkLength_ != 0)
{
if (currentChunkLength_ + remainContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
return -k413RequestEntityTooLarge;
}
status_ = HttpRequestParseStatus::kExpectChunkBody;
}
else
{
status_ = HttpRequestParseStatus::kExpectLastEmptyChunk;
}
buf->retrieveUntil(crlf + CRLF_LEN);
continue;
}
case HttpRequestParseStatus::kExpectChunkBody:
{
if (buf->readableBytes() < (currentChunkLength_ + CRLF_LEN))
{
return 0;
}
if (*(buf->peek() + currentChunkLength_) != '\r' ||
*(buf->peek() + currentChunkLength_ + 1) != '\n')
{
// error!
return -k400BadRequest;
}
request_->appendToBody(buf->peek(), currentChunkLength_);
buf->retrieve(currentChunkLength_ + CRLF_LEN);
remainContentLength_ += currentChunkLength_;
currentChunkLength_ = 0;
status_ = HttpRequestParseStatus::kExpectChunkLen;
continue;
}
case HttpRequestParseStatus::kExpectLastEmptyChunk:
{
// last empty chunk
if (buf->readableBytes() < CRLF_LEN)
{
return 0;
}
if (*(buf->peek()) != '\r' || *(buf->peek() + 1) != '\n')
{
// error!
return -k400BadRequest;
}
buf->retrieve(CRLF_LEN);
if (!request_->isStreamMode())
{
// Previously we only have non-stream mode, drogon handled
// chunked encoding internally, and give user a regular
// request as if it has a content-length header.
//
// We have to keep compatibility for non-stream mode.
//
// But I don't think it's a good implementation. We should
// instead add an api to access real content-length of
// requests.
// Now HttpRequest::realContentLength() is added, and user
// should no longer parse content-length header by
// themselves.
//
// NOTE: request forward behavior may be infected in stream
// mode, we should check it out.
request_->addHeader("content-length",
std::to_string(
request_->realContentLength()));
request_->removeHeaderBy("transfer-encoding");
}
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
return 1;
}
case HttpRequestParseStatus::kGotAll:
{
++requestsCounter_;
return 1;
}
}
}
return -1; // won't reach here, just to make compiler happy
}
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req,
bool isHeadMethod)
{
assert(loop_->isInLoopThread());
requestPipelining_.push_back({req, {nullptr, isHeadMethod}});
}
/**
* @return returns true if the the response is the first in pipeline
*/
bool HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req,
HttpResponsePtr resp)
{
assert(loop_->isInLoopThread());
for (size_t i = 0; i != requestPipelining_.size(); ++i)
{
if (requestPipelining_[i].first == req)
{
requestPipelining_[i].second.first = std::move(resp);
return i == 0;
}
}
assert(false); // Should always find a match
return false;
}
void HttpRequestParser::popReadyResponses(
std::vector<std::pair<HttpResponsePtr, bool>> &buffer)
{
while (!requestPipelining_.empty() &&
requestPipelining_.front().second.first)
{
buffer.push_back(std::move(requestPipelining_.front().second));
requestPipelining_.pop_front();
}
}
+160
View File
@@ -0,0 +1,160 @@
/**
*
* HttpRequestParser.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/HttpTypes.h>
#include <trantor/net/TcpConnection.h>
#include <trantor/utils/MsgBuffer.h>
#include <trantor/utils/NonCopyable.h>
#include <deque>
#include <memory>
#include <mutex>
#include "impl_forwards.h"
namespace drogon
{
class HttpRequestParser : public trantor::NonCopyable,
public std::enable_shared_from_this<HttpRequestParser>
{
public:
enum class HttpRequestParseStatus
{
kExpectMethod,
kExpectRequestLine,
kExpectHeaders,
kExpectBody,
kExpectChunkLen,
kExpectChunkBody,
kExpectLastEmptyChunk,
kGotAll,
};
explicit HttpRequestParser(const trantor::TcpConnectionPtr &connPtr);
int parseRequest(trantor::MsgBuffer *buf);
bool gotAll() const
{
return status_ == HttpRequestParseStatus::kGotAll;
}
void reset();
const HttpRequestImplPtr &requestImpl() const
{
return request_;
}
bool firstReq()
{
if (firstRequest_)
{
firstRequest_ = false;
return true;
}
return false;
}
const WebSocketConnectionImplPtr &webSocketConn() const
{
return websockConnPtr_;
}
void setWebsockConnection(const WebSocketConnectionImplPtr &conn)
{
websockConnPtr_ = conn;
}
// to support request pipelining(rfc2616-8.1.2.2)
void pushRequestToPipelining(const HttpRequestPtr &, bool isHeadMethod);
bool pushResponseToPipelining(const HttpRequestPtr &, HttpResponsePtr);
void popReadyResponses(std::vector<std::pair<HttpResponsePtr, bool>> &);
size_t numberOfRequestsInPipelining() const
{
return requestPipelining_.size();
}
bool emptyPipelining()
{
return requestPipelining_.empty();
}
bool isStop() const
{
return stopWorking_;
}
void stop()
{
stopWorking_ = true;
}
size_t numberOfRequestsParsed() const
{
return requestsCounter_;
}
trantor::MsgBuffer &getBuffer()
{
return sendBuffer_;
}
std::vector<std::pair<HttpResponsePtr, bool>> &getResponseBuffer()
{
assert(loop_->isInLoopThread());
if (!responseBuffer_)
{
responseBuffer_ =
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>(
new std::vector<std::pair<HttpResponsePtr, bool>>);
}
return *responseBuffer_;
}
std::vector<HttpRequestImplPtr> &getRequestBuffer()
{
assert(loop_->isInLoopThread());
if (!requestBuffer_)
{
requestBuffer_ = std::unique_ptr<std::vector<HttpRequestImplPtr>>(
new std::vector<HttpRequestImplPtr>);
}
return *requestBuffer_;
}
private:
HttpRequestImplPtr makeRequestForPool(HttpRequestImpl *p);
bool processRequestLine(const char *begin, const char *end);
HttpRequestParseStatus status_;
trantor::EventLoop *loop_;
HttpRequestImplPtr request_;
bool firstRequest_{true};
WebSocketConnectionImplPtr websockConnPtr_;
std::deque<std::pair<HttpRequestPtr, std::pair<HttpResponsePtr, bool>>>
requestPipelining_;
size_t requestsCounter_{0};
std::weak_ptr<trantor::TcpConnection> conn_;
bool stopWorking_{false};
trantor::MsgBuffer sendBuffer_;
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>
responseBuffer_;
std::unique_ptr<std::vector<HttpRequestImplPtr>> requestBuffer_;
std::vector<HttpRequestImplPtr> requestsPool_;
size_t currentChunkLength_{0};
size_t remainContentLength_{0};
};
} // namespace drogon
+983
View File
@@ -0,0 +1,983 @@
/**
*
* @file HttpResponseImpl.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpResponseImpl.h"
#include "AOPAdvice.h"
#include "HttpAppFrameworkImpl.h"
#include "HttpUtils.h"
#include <drogon/HttpViewData.h>
#include <drogon/IOThreadStorage.h>
#include <filesystem>
#include <fstream>
#include <memory>
#include <cstdio>
#include <string>
#include <sys/stat.h>
#include <trantor/utils/Logger.h>
using namespace trantor;
using namespace drogon;
using namespace std::literals::string_literals;
using namespace std::placeholders;
#ifdef _WIN32
#undef max
#endif
namespace drogon
{
// "Fri, 23 Aug 2019 12:58:03 GMT" length = 29
static const size_t httpFullDateStringLength = 29;
static inline HttpResponsePtr genHttpResponse(const std::string &viewName,
const HttpViewData &data,
const HttpRequestPtr &req)
{
auto templ = DrTemplateBase::newTemplate(viewName);
if (templ)
{
auto res = HttpResponse::newHttpResponse();
res->setBody(templ->genText(data));
return res;
}
return drogon::HttpResponse::newNotFoundResponse(req);
}
} // namespace drogon
HttpResponsePtr HttpResponse::newHttpResponse()
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_TEXT_HTML);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpResponse(HttpStatusCode code,
ContentType type)
{
auto res = std::make_shared<HttpResponseImpl>(code, type);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(data);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data)
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(std::move(data));
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
const char *HttpResponseImpl::versionString() const
{
const char *result = "UNKNOWN";
switch (version_)
{
case Version::kHttp10:
result = "HTTP/1.0";
break;
case Version::kHttp11:
result = "HTTP/1.1";
break;
default:
break;
}
return result;
}
void HttpResponseImpl::generateBodyFromJson() const
{
if (!jsonPtr_ || flagForSerializingJson_)
{
return;
}
flagForSerializingJson_ = true;
static std::once_flag once;
static Json::StreamWriterBuilder builder;
std::call_once(once, []() {
builder["commentStyle"] = "None";
builder["indentation"] = "";
if (!app().isUnicodeEscapingUsedInJson())
{
builder["emitUTF8"] = true;
}
auto &precision = app().getFloatPrecisionInJson();
if (precision.first != 0)
{
builder["precision"] = precision.first;
builder["precisionType"] = precision.second;
}
});
bodyPtr_ = std::make_shared<HttpMessageStringBody>(
writeString(builder, *jsonPtr_));
}
HttpResponsePtr HttpResponse::newNotFoundResponse(const HttpRequestPtr &req)
{
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
auto &resp = HttpAppFrameworkImpl::instance().getCustom404Page();
if (resp)
{
if (loop && loop->index() < app().getThreadNum())
{
return resp;
}
else
{
return HttpResponsePtr{new HttpResponseImpl(
*static_cast<HttpResponseImpl *>(resp.get()))};
}
}
else
{
if (HttpAppFrameworkImpl::instance().isUsingCustomErrorHandler())
{
return app().getCustomErrorHandler()(k404NotFound, req);
}
else if (loop && loop->index() < app().getThreadNum())
{
// If the current thread is an IO thread
static std::once_flag threadOnce;
static IOThreadStorage<HttpResponsePtr> thread404Pages;
std::call_once(threadOnce, [req = req] {
thread404Pages.init([req = req](drogon::HttpResponsePtr &resp,
size_t /*index*/) {
HttpViewData data;
data.insert("version", drogon::getVersion());
resp = HttpResponse::newHttpViewResponse("drogon::NotFound",
data);
resp->setStatusCode(k404NotFound);
resp->setExpiredTime(0);
});
});
LOG_TRACE << "Use cached 404 response";
return thread404Pages.getThreadData();
}
else
{
HttpViewData data;
data.insert("version", drogon::getVersion());
auto notFoundResp =
HttpResponse::newHttpViewResponse("drogon::NotFound", data);
notFoundResp->setStatusCode(k404NotFound);
return notFoundResp;
}
}
}
HttpResponsePtr HttpResponse::newRedirectionResponse(
const std::string &location,
HttpStatusCode status)
{
auto res = std::make_shared<HttpResponseImpl>();
res->setStatusCode(status);
res->redirect(location);
AopAdvice::instance().passResponseCreationAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName,
const HttpViewData &data,
const HttpRequestPtr &req)
{
return genHttpResponse(viewName, data, req);
}
HttpResponsePtr HttpResponse::newFileResponse(
const unsigned char *pBuffer,
size_t bufferLength,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString)
{
// Make Raw HttpResponse
auto resp = std::make_shared<HttpResponseImpl>();
// Set response body and length
resp->setBody(
std::string(reinterpret_cast<const char *>(pBuffer), bufferLength));
// Set status of message
resp->setStatusCode(k200OK);
// Check for type and assign proper content type in header
if (!typeString.empty())
{
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_APPLICATION_OCTET_STREAM; // XXX: Is this Ok?
static_cast<HttpResponse *>(resp.get())
->setContentTypeCodeAndCustomString(type,
typeString.c_str(),
typeString.size());
}
else if (type != CT_NONE)
{
resp->setContentTypeCode(type);
}
else if (!attachmentFileName.empty())
{
resp->setContentTypeCode(drogon::getContentType(attachmentFileName));
}
else
{
resp->setContentTypeCode(
CT_APPLICATION_OCTET_STREAM); // default content-type for file;
}
// Add additional header values
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
// Finalize and return response
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
HttpResponsePtr HttpResponse::newFileResponse(
const std::string &fullPath,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString,
const HttpRequestPtr &req)
{
return newFileResponse(
fullPath, 0, 0, false, attachmentFileName, type, typeString, req);
}
HttpResponsePtr HttpResponse::newFileResponse(
const std::string &fullPath,
size_t offset,
size_t length,
bool setContentRange,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString,
const HttpRequestPtr &req)
{
std::ifstream infile(utils::toNativePath(fullPath), std::ifstream::binary);
LOG_TRACE << "send http file:" << fullPath << " offset " << offset
<< " length " << length;
if (!infile)
{
auto resp = HttpResponse::newNotFoundResponse(req);
return resp;
}
auto resp = std::make_shared<HttpResponseImpl>();
std::streambuf *pbuf = infile.rdbuf();
size_t filesize =
static_cast<size_t>(pbuf->pubseekoff(0, std::ifstream::end));
if (offset > filesize || length > filesize || // in case of overflow
offset + length > filesize)
{
resp->setStatusCode(k416RequestedRangeNotSatisfiable);
if (setContentRange)
{
char buf[64];
snprintf(buf, sizeof(buf), "bytes */%zu", filesize);
resp->addHeader("Content-Range", std::string(buf));
}
return resp;
}
if (length == 0)
{
length = filesize - offset;
}
pbuf->pubseekoff(offset, std::ifstream::beg); // rewind
if (HttpAppFrameworkImpl::instance().useSendfile() && length > 1024 * 200)
// TODO : Is 200k an appropriate value? Or set it to be configurable
{
// The advantages of sendfile() can only be reflected in sending large
// files.
resp->setSendfile(fullPath);
// Must set length with the right value! Content-Length header relies on
// this value.
resp->setSendfileRange(offset, length);
}
else
{
std::string str;
str.resize(length);
pbuf->sgetn(&str[0], length);
resp->setBody(std::move(str));
resp->setSendfileRange(offset, length);
}
// Set correct status code
if (length < filesize)
{
resp->setStatusCode(k206PartialContent);
}
else
{
resp->setStatusCode(k200OK);
}
// Infer content type
if (type == CT_NONE)
{
if (!typeString.empty())
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
else if (!attachmentFileName.empty())
{
resp->setContentTypeCode(
drogon::getContentType(attachmentFileName));
}
else
{
resp->setContentTypeCode(drogon::getContentType(fullPath));
}
}
else
{
if (typeString.empty())
resp->setContentTypeCode(type);
else
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
}
// Set headers
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
if (setContentRange && length > 0)
{
char buf[128];
snprintf(buf,
sizeof(buf),
"bytes %zu-%zu/%zu",
offset,
offset + length - 1,
filesize);
resp->addHeader("Content-Range", std::string(buf));
}
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
HttpResponsePtr HttpResponse::newStreamResponse(
const std::function<std::size_t(char *, std::size_t)> &callback,
const std::string &attachmentFileName,
ContentType type,
const std::string &typeString,
const HttpRequestPtr &req)
{
LOG_TRACE << "send stream as "s
<< (attachmentFileName.empty() ? "raw data"s
: "file: "s + attachmentFileName);
if (!callback)
{
auto resp = HttpResponse::newNotFoundResponse();
return resp;
}
auto resp = std::make_shared<HttpResponseImpl>();
resp->setStreamCallback(callback);
resp->setStatusCode(k200OK);
// Infer content type
if (type == CT_NONE)
{
if (!typeString.empty())
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
else if (!attachmentFileName.empty())
{
resp->setContentTypeCode(
drogon::getContentType(attachmentFileName));
}
}
else
{
if (typeString.empty())
resp->setContentTypeCode(type);
else
{
auto r = static_cast<HttpResponse *>(resp.get());
if (type == CT_NONE)
type = parseContentType(typeString);
if (type == CT_NONE)
type = CT_CUSTOM; // XXX: Is this Ok?
r->setContentTypeCodeAndCustomString(type, typeString);
}
}
// Set headers
if (!attachmentFileName.empty())
{
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
HttpResponsePtr HttpResponse::newAsyncStreamResponse(
const std::function<void(ResponseStreamPtr)> &callback,
bool disableKickoffTimeout)
{
if (!callback)
{
auto resp = HttpResponse::newNotFoundResponse();
return resp;
}
auto resp = std::make_shared<HttpResponseImpl>();
resp->setAsyncStreamCallback(callback, disableKickoffTimeout);
resp->setStatusCode(k200OK);
AopAdvice::instance().passResponseCreationAdvices(resp);
return resp;
}
void HttpResponseImpl::makeHeaderString(trantor::MsgBuffer &buffer)
{
buffer.ensureWritableBytes(128);
int len{0};
if (version_ == Version::kHttp11)
{
if (customStatusCode_ >= 0)
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.1 %d ",
customStatusCode_);
}
else
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.1 %d ",
statusCode_);
}
}
else
{
if (customStatusCode_ >= 0)
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.0 %d ",
customStatusCode_);
}
else
{
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.0 %d ",
statusCode_);
}
}
buffer.hasWritten(len);
if (!statusMessage_.empty())
buffer.append(statusMessage_.data(), statusMessage_.length());
buffer.append("\r\n");
generateBodyFromJson();
if (!passThrough_)
{
buffer.ensureWritableBytes(64);
if (!contentLengthIsAllowed())
{
len = 0;
if ((bodyPtr_ && bodyPtr_->length() > 0) ||
!sendfileName_.empty() || streamCallback_ ||
asyncStreamCallback_)
{
LOG_ERROR << "The body should be empty when the content-length "
"is not allowed!";
}
}
else if (streamCallback_ || asyncStreamCallback_)
{
// When the headers are created, it is time to set the transfer
// encoding to chunked if the contents size is not specified
if (!ifCloseConnection() &&
headers_.find("content-length") == headers_.end())
{
LOG_DEBUG << "send stream with transfer-encoding chunked";
headers_["transfer-encoding"] = "chunked";
}
len = 0;
}
else if (sendfileName_.empty())
{
auto bodyLength = bodyPtr_ ? bodyPtr_->length() : 0;
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(bodyLength)>(),
bodyLength);
}
else
{
auto bodyLength = sendfileRange_.second;
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(bodyLength)>(),
bodyLength);
}
buffer.hasWritten(len);
if (headers_.find("connection") == headers_.end())
{
if (closeConnection_)
{
buffer.append("connection: close\r\n");
}
else if (version_ == Version::kHttp10)
{
buffer.append("connection: Keep-Alive\r\n");
}
}
if (!contentTypeString_.empty())
{
buffer.append("content-type: ");
buffer.append(contentTypeString_);
buffer.append("\r\n");
}
if (HttpAppFrameworkImpl::instance().sendServerHeader())
{
buffer.append(
HttpAppFrameworkImpl::instance().getServerHeaderString());
}
}
for (auto it = headers_.begin(); it != headers_.end(); ++it)
{
buffer.append(it->first);
buffer.append(": ");
buffer.append(it->second);
buffer.append("\r\n");
}
}
void HttpResponseImpl::renderToBuffer(trantor::MsgBuffer &buffer)
{
if (expriedTime_ >= 0)
{
auto strPtr = renderToBuffer();
buffer.append(strPtr->peek(), strPtr->readableBytes());
return;
}
if (!fullHeaderString_)
{
makeHeaderString(buffer);
}
else
{
buffer.append(*fullHeaderString_);
}
// output cookies
if (!cookies_.empty())
{
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
buffer.append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
buffer.append("date: ");
buffer.append(utils::getHttpFullDateStr(trantor::Date::date()));
buffer.append("\r\n\r\n");
}
else
{
buffer.append("\r\n");
}
if (bodyPtr_ && contentLengthIsAllowed())
buffer.append(bodyPtr_->data(), bodyPtr_->length());
}
std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::renderToBuffer()
{
if (expriedTime_ >= 0)
{
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
if (datePos_ != static_cast<size_t>(-1))
{
auto now = trantor::Date::now();
bool isDateChanged =
((now.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC) != httpStringDate_);
assert(httpString_);
if (isDateChanged)
{
httpStringDate_ = now.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC;
auto newDate = utils::getHttpFullDate(now);
httpString_ =
std::make_shared<trantor::MsgBuffer>(*httpString_);
memcpy((void *)&(*httpString_)[datePos_],
newDate,
httpFullDateStringLength);
return httpString_;
}
return httpString_;
}
}
else
{
if (httpString_)
return httpString_;
}
}
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_)
{
makeHeaderString(*httpString);
}
else
{
httpString->append(*fullHeaderString_);
}
// output cookies
if (!cookies_.empty())
{
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
httpString->append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
httpString->append("date: ");
auto datePos = httpString->readableBytes();
httpString->append(utils::getHttpFullDateStr(trantor::Date::date()));
httpString->append("\r\n\r\n");
datePos_ = datePos;
}
else
{
httpString->append("\r\n");
}
LOG_TRACE << "response(no body):"
<< std::string_view{httpString->peek(),
httpString->readableBytes()};
if (bodyPtr_)
httpString->append(bodyPtr_->data(), bodyPtr_->length());
if (expriedTime_ >= 0)
{
httpString_ = httpString;
}
return httpString;
}
std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::
renderHeaderForHeadMethod()
{
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_)
{
makeHeaderString(*httpString);
}
else
{
httpString->append(*fullHeaderString_);
}
// output cookies
if (!cookies_.empty())
{
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
{
httpString->append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{
httpString->append("date: ");
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
httpString->append("\r\n\r\n");
}
else
{
httpString->append("\r\n");
}
return httpString;
}
void HttpResponseImpl::addHeader(const char *start,
const char *colon,
const char *end)
{
fullHeaderString_.reset();
std::string field(start, colon);
transform(field.begin(), field.end(), field.begin(), [](unsigned char c) {
return tolower(c);
});
++colon;
while (colon < end && isspace(static_cast<unsigned char>(*colon)))
{
++colon;
}
std::string value(colon, end);
while (!value.empty() &&
isspace(static_cast<unsigned char>(value[value.size() - 1])))
{
value.resize(value.size() - 1);
}
if (field == "set-cookie")
{
// LOG_INFO<<"cookies!!!:"<<value;
auto values = utils::splitString(value, ";");
Cookie cookie;
cookie.setHttpOnly(false);
for (size_t i = 0; i < values.size(); ++i)
{
std::string &coo = values[i];
std::string cookie_name;
std::string cookie_value;
auto epos = coo.find('=');
if (epos != std::string::npos)
{
cookie_name = coo.substr(0, epos);
std::string::size_type cpos = 0;
while (cpos < cookie_name.length() &&
isspace(static_cast<unsigned char>(cookie_name[cpos])))
++cpos;
cookie_name = cookie_name.substr(cpos);
++epos;
while (epos < coo.length() &&
isspace(static_cast<unsigned char>(coo[epos])))
++epos;
cookie_value = coo.substr(epos);
}
else
{
std::string::size_type cpos = 0;
while (cpos < coo.length() &&
isspace(static_cast<unsigned char>(coo[cpos])))
++cpos;
cookie_name = coo.substr(cpos);
}
if (i == 0)
{
cookie.setKey(cookie_name);
cookie.setValue(cookie_value);
}
else
{
std::transform(cookie_name.begin(),
cookie_name.end(),
cookie_name.begin(),
[](unsigned char c) { return tolower(c); });
if (cookie_name == "path")
{
cookie.setPath(cookie_value);
}
else if (cookie_name == "domain")
{
cookie.setDomain(cookie_value);
}
else if (cookie_name == "expires")
{
cookie.setExpiresDate(utils::getHttpDate(cookie_value));
}
else if (cookie_name == "secure")
{
cookie.setSecure(true);
}
else if (cookie_name == "httponly")
{
cookie.setHttpOnly(true);
}
else if (cookie_name == "samesite")
{
cookie.setSameSite(
cookie.convertString2SameSite(cookie_value));
}
else if (cookie_name == "max-age")
{
cookie.setMaxAge(std::stoi(cookie_value));
}
}
}
if (!cookie.key().empty())
{
cookies_[cookie.key()] = std::move(cookie);
}
}
else
{
headers_[std::move(field)] = std::move(value);
}
}
void HttpResponseImpl::swap(HttpResponseImpl &that) noexcept
{
using std::swap;
headers_.swap(that.headers_);
cookies_.swap(that.cookies_);
swap(statusCode_, that.statusCode_);
swap(version_, that.version_);
swap(statusMessage_, that.statusMessage_);
swap(closeConnection_, that.closeConnection_);
bodyPtr_.swap(that.bodyPtr_);
swap(contentType_, that.contentType_);
swap(flagForParsingContentType_, that.flagForParsingContentType_);
swap(flagForParsingJson_, that.flagForParsingJson_);
swap(sendfileName_, that.sendfileName_);
swap(streamCallback_, that.streamCallback_);
swap(asyncStreamCallback_, that.asyncStreamCallback_);
jsonPtr_.swap(that.jsonPtr_);
fullHeaderString_.swap(that.fullHeaderString_);
httpString_.swap(that.httpString_);
swap(datePos_, that.datePos_);
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
}
void HttpResponseImpl::clear()
{
statusCode_ = kUnknown;
version_ = Version::kHttp11;
statusMessage_ = std::string_view{};
fullHeaderString_.reset();
jsonParsingErrorPtr_.reset();
sendfileName_.clear();
if (streamCallback_)
{
LOG_TRACE << "Cleanup HttpResponse stream callback";
streamCallback_(nullptr, 0); // callback internal cleanup
streamCallback_ = {};
}
if (asyncStreamCallback_)
{
// asyncStreamCallback_(nullptr);
asyncStreamCallback_ = {};
}
headers_.clear();
cookies_.clear();
bodyPtr_.reset();
jsonPtr_.reset();
expriedTime_ = -1;
datePos_ = std::string::npos;
flagForParsingContentType_ = false;
flagForParsingJson_ = false;
}
void HttpResponseImpl::parseJson() const
{
static std::once_flag once;
static Json::CharReaderBuilder builder;
std::call_once(once, []() {
builder["collectComments"] = false;
builder["stackLimit"] =
static_cast<Json::UInt>(drogon::app().getJsonParserStackLimit());
});
JSONCPP_STRING errs;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (bodyPtr_)
{
jsonPtr_ = std::make_shared<Json::Value>();
if (!reader->parse(bodyPtr_->data(),
bodyPtr_->data() + bodyPtr_->length(),
jsonPtr_.get(),
&errs))
{
LOG_ERROR << errs;
LOG_ERROR << "body: " << bodyPtr_->getString();
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_shared<std::string>(std::move(errs));
}
else
{
jsonParsingErrorPtr_.reset();
}
}
else
{
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_shared<std::string>("empty response body");
}
}
bool HttpResponseImpl::shouldBeCompressed() const
{
if (streamCallback_ || asyncStreamCallback_ || !sendfileName_.empty() ||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
getBody().length() < 1024 ||
!(getHeaderBy("content-encoding").empty()) || !contentLengthIsAllowed())
{
return false;
}
return true;
}
void HttpResponseImpl::setContentTypeString(const char *typeString,
size_t typeStringLength)
{
std::string sv(typeString, typeStringLength);
auto contentType = parseContentType(sv);
if (contentType == CT_NONE)
contentType = CT_CUSTOM;
contentType_ = contentType;
contentTypeString_ = std::string(sv);
flagForParsingContentType_ = true;
}
+559
View File
@@ -0,0 +1,559 @@
/**
*
* @file HttpResponseImpl.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "HttpUtils.h"
#include "HttpMessageBody.h"
#include <drogon/exports.h>
#include <drogon/HttpResponse.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Date.h>
#include <trantor/utils/MsgBuffer.h>
#include <memory>
#include <mutex>
#include <string>
#include <atomic>
#include <unordered_map>
namespace drogon
{
class DROGON_EXPORT HttpResponseImpl : public HttpResponse
{
friend class HttpResponseParser;
public:
HttpResponseImpl() : creationDate_(trantor::Date::now())
{
}
HttpResponseImpl(HttpStatusCode code, ContentType type)
: statusCode_(code),
statusMessage_(statusCodeToString(code)),
creationDate_(trantor::Date::now()),
contentType_(type),
flagForParsingContentType_(true),
contentTypeString_(contentTypeToMime(type))
{
}
void setPassThrough(bool flag) override
{
passThrough_ = flag;
}
HttpStatusCode statusCode() const override
{
return statusCode_;
}
const trantor::Date &creationDate() const override
{
return creationDate_;
}
void setStatusCode(HttpStatusCode code) override
{
statusCode_ = code;
setStatusMessage(statusCodeToString(code));
}
void setVersion(const Version v) override
{
version_ = v;
if (version_ == Version::kHttp10)
{
closeConnection_ = true;
}
}
Version version() const override
{
return version_;
}
const char *versionString() const override;
void setCloseConnection(bool on) override
{
closeConnection_ = on;
}
bool ifCloseConnection() const override
{
return closeConnection_;
}
void setContentTypeCode(ContentType type) override
{
contentType_ = type;
auto ct = contentTypeToMime(type);
contentTypeString_ = std::string(ct.data(), ct.size());
flagForParsingContentType_ = true;
}
// void setContentTypeCodeAndCharacterSet(ContentType type, const
// std::string &charSet = "utf-8") override
// {
// contentType_ = type;
// setContentType(webContentTypeAndCharsetToString(type, charSet));
// }
ContentType contentType() const override
{
parseContentTypeAndString();
return contentType_;
}
const std::string &getHeader(std::string key) const override
{
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
return tolower(c);
});
return getHeaderBy(key);
}
void removeHeader(std::string key) override
{
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
return tolower(c);
});
removeHeaderBy(key);
}
const SafeStringMap<std::string> &headers() const override
{
return headers_;
}
const std::string &getHeaderBy(const std::string &lowerKey) const
{
static const std::string defaultVal;
auto iter = headers_.find(lowerKey);
if (iter == headers_.end())
{
return defaultVal;
}
return iter->second;
}
void removeHeaderBy(const std::string &lowerKey)
{
fullHeaderString_.reset();
headers_.erase(lowerKey);
}
void addHeader(std::string field, const std::string &value) override
{
fullHeaderString_.reset();
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = value;
}
void addHeader(std::string field, std::string &&value) override
{
fullHeaderString_.reset();
transform(field.begin(),
field.end(),
field.begin(),
[](unsigned char c) { return tolower(c); });
headers_[std::move(field)] = std::move(value);
}
void addHeader(const char *start, const char *colon, const char *end);
void addCookie(const std::string &key, const std::string &value) override
{
cookies_[key] = Cookie(key, value);
}
void addCookie(const Cookie &cookie) override
{
cookies_[cookie.key()] = cookie;
}
void addCookie(Cookie &&cookie) override
{
cookies_[cookie.key()] = std::move(cookie);
}
const Cookie &getCookie(const std::string &key) const override
{
static const Cookie defaultCookie;
auto it = cookies_.find(key);
if (it != cookies_.end())
{
return it->second;
}
return defaultCookie;
}
const SafeStringMap<Cookie> &cookies() const override
{
return cookies_;
}
void removeCookie(const std::string &key) override
{
cookies_.erase(key);
}
void setBody(const std::string &body) override
{
bodyPtr_ = std::make_shared<HttpMessageStringBody>(body);
if (passThrough_)
{
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
void setBody(std::string &&body) override
{
bodyPtr_ = std::make_shared<HttpMessageStringBody>(std::move(body));
if (passThrough_)
{
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
void redirect(const std::string &url)
{
headers_["location"] = url;
}
std::shared_ptr<trantor::MsgBuffer> renderToBuffer();
void renderToBuffer(trantor::MsgBuffer &buffer);
std::shared_ptr<trantor::MsgBuffer> renderHeaderForHeadMethod();
void clear() override;
void setExpiredTime(ssize_t expiredTime) override
{
expriedTime_ = expiredTime;
datePos_ = std::string::npos;
if (expriedTime_ < 0 && version_ == Version::kHttp10)
{
fullHeaderString_.reset();
}
}
ssize_t expiredTime() const override
{
return expriedTime_;
}
const char *getBodyData() const override
{
if (!flagForSerializingJson_ && jsonPtr_)
{
generateBodyFromJson();
}
else if (!bodyPtr_)
{
return nullptr;
}
return bodyPtr_->data();
}
size_t getBodyLength() const override
{
if (bodyPtr_)
return bodyPtr_->length();
return 0;
}
void swap(HttpResponseImpl &that) noexcept;
void parseJson() const;
const std::shared_ptr<Json::Value> &jsonObject() const override
{
// Not multi-thread safe but good, because we basically call this
// function in a single thread
if (!flagForParsingJson_)
{
flagForParsingJson_ = true;
parseJson();
}
return jsonPtr_;
}
const std::string &getJsonError() const override
{
static const std::string none;
if (jsonParsingErrorPtr_)
return *jsonParsingErrorPtr_;
return none;
}
void setJsonObject(const Json::Value &pJson)
{
flagForParsingJson_ = true;
flagForSerializingJson_ = false;
jsonPtr_ = std::make_shared<Json::Value>(pJson);
}
void setJsonObject(Json::Value &&pJson)
{
flagForParsingJson_ = true;
flagForSerializingJson_ = false;
jsonPtr_ = std::make_shared<Json::Value>(std::move(pJson));
}
bool shouldBeCompressed() const;
void generateBodyFromJson() const;
const std::string &sendfileName() const override
{
return sendfileName_;
}
const SendfileRange &sendfileRange() const override
{
return sendfileRange_;
}
const trantor::CertificatePtr &peerCertificate() const override
{
return peerCertificate_;
}
void setPeerCertificate(const trantor::CertificatePtr &cert)
{
peerCertificate_ = cert;
}
void setSendfile(const std::string &filename)
{
sendfileName_ = filename;
}
void setSendfileRange(size_t offset, size_t len)
{
sendfileRange_.first = offset;
sendfileRange_.second = len;
}
const std::function<std::size_t(char *, std::size_t)> &streamCallback()
const override
{
return streamCallback_;
}
void setStreamCallback(
const std::function<std::size_t(char *, std::size_t)> &callback)
{
streamCallback_ = callback;
}
const std::function<void(ResponseStreamPtr)> &asyncStreamCallback()
const override
{
return asyncStreamCallback_;
}
void setAsyncStreamCallback(
const std::function<void(ResponseStreamPtr)> &callback,
bool disableKickoffTimeout)
{
asyncStreamCallback_ = callback;
asyncStreamDisableKickoff_ = disableKickoffTimeout;
}
bool asyncStreamKickoffDisabled() const
{
return asyncStreamDisableKickoff_;
}
void makeHeaderString()
{
fullHeaderString_ = std::make_shared<trantor::MsgBuffer>(128);
makeHeaderString(*fullHeaderString_);
}
std::string contentTypeString() const override
{
parseContentTypeAndString();
return contentTypeString_;
}
void gunzip()
{
if (bodyPtr_)
{
auto gunzipBody =
utils::gzipDecompress(bodyPtr_->data(), bodyPtr_->length());
removeHeaderBy("content-encoding");
bodyPtr_ =
std::make_shared<HttpMessageStringBody>(std::move(gunzipBody));
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
bool contentLengthIsAllowed() const
{
int statusCode =
customStatusCode_ >= 0 ? customStatusCode_ : statusCode_;
// return false if status code is 1xx or 204
return (statusCode >= k200OK || statusCode < k100Continue) &&
statusCode != k204NoContent;
}
#ifdef USE_BROTLI
void brDecompress()
{
if (bodyPtr_)
{
auto gunzipBody =
utils::brotliDecompress(bodyPtr_->data(), bodyPtr_->length());
removeHeaderBy("content-encoding");
bodyPtr_ =
std::make_shared<HttpMessageStringBody>(std::move(gunzipBody));
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
#endif
~HttpResponseImpl() override = default;
protected:
void makeHeaderString(trantor::MsgBuffer &headerString);
void parseContentTypeAndString() const
{
if (!flagForParsingContentType_)
{
flagForParsingContentType_ = true;
auto &contentTypeString = getHeaderBy("content-type");
if (contentTypeString == "")
{
contentType_ = CT_NONE;
}
else
{
auto pos = contentTypeString.find(';');
if (pos != std::string::npos)
{
contentType_ = parseContentType(
std::string_view(contentTypeString.data(), pos));
}
else
{
contentType_ =
parseContentType(std::string_view(contentTypeString));
}
if (contentType_ == CT_NONE)
contentType_ = CT_CUSTOM;
contentTypeString_ = contentTypeString;
}
}
}
private:
void setBody(const char *body, size_t len) override
{
bodyPtr_ = std::make_shared<HttpMessageStringViewBody>(body, len);
if (passThrough_)
{
addHeader("content-length", std::to_string(bodyPtr_->length()));
}
}
void setContentTypeCodeAndCustomString(ContentType type,
const char *typeString,
size_t typeStringLength) override
{
contentType_ = type;
flagForParsingContentType_ = true;
std::string_view sv(typeString, typeStringLength);
bool haveHeader = sv.find("content-type: ") == 0;
bool haveCRLF = sv.rfind("\r\n") == sv.size() - 2;
size_t endOffset = 0;
if (haveHeader)
endOffset += 14;
if (haveCRLF)
endOffset += 2;
setContentType(std::string_view{typeString + (haveHeader ? 14 : 0),
typeStringLength - endOffset});
}
void setContentTypeString(const char *typeString,
size_t typeStringLength) override;
void setCustomStatusCode(int code,
const char *message,
size_t messageLength) override
{
assert(code >= 0);
customStatusCode_ = code;
statusMessage_ = std::string_view{message, messageLength};
}
SafeStringMap<std::string> headers_;
SafeStringMap<Cookie> cookies_;
int customStatusCode_{-1};
HttpStatusCode statusCode_{kUnknown};
std::string_view statusMessage_;
trantor::Date creationDate_;
Version version_{Version::kHttp11};
bool closeConnection_{false};
mutable std::shared_ptr<HttpMessageBody> bodyPtr_;
ssize_t expriedTime_{-1};
std::string sendfileName_;
SendfileRange sendfileRange_{0, 0};
std::function<std::size_t(char *, std::size_t)> streamCallback_;
std::function<void(ResponseStreamPtr)> asyncStreamCallback_;
bool asyncStreamDisableKickoff_{false};
mutable std::shared_ptr<Json::Value> jsonPtr_;
std::shared_ptr<trantor::MsgBuffer> fullHeaderString_;
trantor::CertificatePtr peerCertificate_;
mutable std::shared_ptr<trantor::MsgBuffer> httpString_;
mutable size_t datePos_{static_cast<size_t>(-1)};
mutable int64_t httpStringDate_{-1};
mutable bool flagForParsingJson_{false};
mutable bool flagForSerializingJson_{true};
mutable ContentType contentType_{CT_TEXT_PLAIN};
mutable bool flagForParsingContentType_{false};
mutable std::shared_ptr<std::string> jsonParsingErrorPtr_;
mutable std::string contentTypeString_{"text/html; charset=utf-8"};
bool passThrough_{false};
void setContentType(const std::string_view &contentType)
{
contentTypeString_ =
std::string(contentType.data(), contentType.size());
}
void setStatusMessage(const std::string_view &message)
{
statusMessage_ = message;
}
};
using HttpResponseImplPtr = std::shared_ptr<HttpResponseImpl>;
inline void swap(HttpResponseImpl &one, HttpResponseImpl &two) noexcept
{
one.swap(two);
}
} // namespace drogon

Some files were not shown because too many files have changed in this diff Show More