复现已有算法
This commit is contained in:
+130
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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 ®Exp,
|
||||
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 ®istrator_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool AutoCreation>
|
||||
typename HttpController<T, AutoCreation>::methodRegistrator
|
||||
HttpController<T, AutoCreation>::registrator_;
|
||||
} // namespace drogon
|
||||
+135
@@ -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
|
||||
@@ -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
|
||||
@@ -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> &¶ms) = 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> ¶meters() 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 ¶ms = 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
|
||||
@@ -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 ®istrator_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool AutoCreation>
|
||||
typename HttpSimpleController<T, AutoCreation>::pathRegistrator
|
||||
HttpSimpleController<T, AutoCreation>::registrator_;
|
||||
|
||||
} // namespace drogon
|
||||
+294
@@ -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
|
||||
@@ -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
|
||||
" --> "
|
||||
& --> &
|
||||
< --> <
|
||||
> --> >
|
||||
@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
|
||||
@@ -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
@@ -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 ¶ms = 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ®Exp,
|
||||
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 ®istrator_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool AutoCreation>
|
||||
typename WebSocketController<T, AutoCreation>::pathRegistrator
|
||||
WebSocketController<T, AutoCreation>::registrator_;
|
||||
|
||||
} // namespace drogon
|
||||
+50
@@ -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
|
||||
@@ -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 ¶m1)
|
||||
{
|
||||
return funcName + "(" + param1 + ")";
|
||||
}
|
||||
|
||||
inline std::string stringifyFuncCall(const std::string &funcName,
|
||||
const std::string ¶m1,
|
||||
const std::string ¶m2)
|
||||
{
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ®istry)
|
||||
{
|
||||
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
|
||||
Reference in New Issue
Block a user