复现已有算法
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
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
*
|
||||
* AOPAdvice.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AOPAdvice.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
static void doAdviceChain(
|
||||
const std::vector<std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)>> &adviceChain,
|
||||
size_t index,
|
||||
const HttpRequestImplPtr &req,
|
||||
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
||||
&&callbackPtr);
|
||||
|
||||
bool AopAdvice::passNewConnectionAdvices(
|
||||
const trantor::TcpConnectionPtr &conn) const
|
||||
{
|
||||
for (auto &advice : newConnectionAdvices_)
|
||||
{
|
||||
if (!advice(conn->localAddr(), conn->peerAddr()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AopAdvice::passResponseCreationAdvices(const HttpResponsePtr &resp) const
|
||||
{
|
||||
if (!responseCreationAdvices_.empty())
|
||||
{
|
||||
for (auto &advice : responseCreationAdvices_)
|
||||
{
|
||||
advice(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponsePtr AopAdvice::passSyncAdvices(const HttpRequestPtr &req) const
|
||||
{
|
||||
for (auto &advice : syncAdvices_)
|
||||
{
|
||||
if (auto resp = advice(req))
|
||||
{
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AopAdvice::passPreRoutingObservers(const HttpRequestImplPtr &req) const
|
||||
{
|
||||
if (!preRoutingObservers_.empty())
|
||||
{
|
||||
for (auto &observer : preRoutingObservers_)
|
||||
{
|
||||
observer(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AopAdvice::passPreRoutingAdvices(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
if (preRoutingAdvices_.empty())
|
||||
{
|
||||
callback(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||
doAdviceChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||
}
|
||||
|
||||
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
|
||||
{
|
||||
if (!postRoutingObservers_.empty())
|
||||
{
|
||||
for (auto &observer : postRoutingObservers_)
|
||||
{
|
||||
observer(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AopAdvice::passPostRoutingAdvices(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
if (postRoutingAdvices_.empty())
|
||||
{
|
||||
callback(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||
doAdviceChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||
}
|
||||
|
||||
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
|
||||
{
|
||||
if (!preHandlingObservers_.empty())
|
||||
{
|
||||
for (auto &observer : preHandlingObservers_)
|
||||
{
|
||||
observer(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AopAdvice::passPreHandlingAdvices(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
if (preHandlingAdvices_.empty())
|
||||
{
|
||||
callback(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||
doAdviceChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
|
||||
}
|
||||
|
||||
void AopAdvice::passPostHandlingAdvices(const HttpRequestImplPtr &req,
|
||||
const HttpResponsePtr &resp) const
|
||||
{
|
||||
for (auto &advice : postHandlingAdvices_)
|
||||
{
|
||||
advice(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
void AopAdvice::passPreSendingAdvices(const HttpRequestImplPtr &req,
|
||||
const HttpResponsePtr &resp) const
|
||||
{
|
||||
for (auto &advice : preSendingAdvices_)
|
||||
{
|
||||
advice(req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
static void doAdviceChain(
|
||||
const std::vector<std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)>> &adviceChain,
|
||||
size_t index,
|
||||
const HttpRequestImplPtr &req,
|
||||
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
||||
&&callbackPtr)
|
||||
{
|
||||
if (index < adviceChain.size())
|
||||
{
|
||||
auto &advice = adviceChain[index];
|
||||
advice(
|
||||
req,
|
||||
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
|
||||
(*callbackPtr)(resp);
|
||||
},
|
||||
[index, req, callbackPtr, &adviceChain]() mutable {
|
||||
auto ioLoop = req->getLoop();
|
||||
if (ioLoop && !ioLoop->isInLoopThread())
|
||||
{
|
||||
ioLoop->queueInLoop([index,
|
||||
req,
|
||||
callbackPtr = std::move(callbackPtr),
|
||||
&adviceChain]() mutable {
|
||||
doAdviceChain(adviceChain,
|
||||
index + 1,
|
||||
req,
|
||||
std::move(callbackPtr));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
doAdviceChain(adviceChain,
|
||||
index + 1,
|
||||
req,
|
||||
std::move(callbackPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
(*callbackPtr)(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace drogon
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
*
|
||||
* AOPAdvice.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "impl_forwards.h"
|
||||
#include <drogon/drogon_callbacks.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class AopAdvice
|
||||
{
|
||||
public:
|
||||
static AopAdvice &instance()
|
||||
{
|
||||
static AopAdvice inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
// Getters?
|
||||
bool hasPreRoutingAdvices() const
|
||||
{
|
||||
return !preRoutingAdvices_.empty();
|
||||
}
|
||||
|
||||
bool hasPostRoutingAdvices() const
|
||||
{
|
||||
return !postRoutingAdvices_.empty();
|
||||
}
|
||||
|
||||
bool hasPreHandlingAdvices() const
|
||||
{
|
||||
return !preHandlingAdvices_.empty();
|
||||
}
|
||||
|
||||
// Setters?
|
||||
void registerNewConnectionAdvice(
|
||||
std::function<bool(const trantor::InetAddress &,
|
||||
const trantor::InetAddress &)> advice)
|
||||
{
|
||||
newConnectionAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerHttpResponseCreationAdvice(
|
||||
std::function<void(const HttpResponsePtr &)> advice)
|
||||
{
|
||||
responseCreationAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerSyncAdvice(
|
||||
std::function<HttpResponsePtr(const HttpRequestPtr &)> advice)
|
||||
|
||||
{
|
||||
syncAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPreRoutingObserver(
|
||||
std::function<void(const HttpRequestPtr &)> advice)
|
||||
{
|
||||
preRoutingObservers_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPreRoutingAdvice(
|
||||
std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> advice)
|
||||
{
|
||||
preRoutingAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPostRoutingObserver(
|
||||
std::function<void(const HttpRequestPtr &)> advice)
|
||||
{
|
||||
postRoutingObservers_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPostRoutingAdvice(
|
||||
std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> advice)
|
||||
{
|
||||
postRoutingAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPreHandlingObserver(
|
||||
std::function<void(const HttpRequestPtr &)> advice)
|
||||
{
|
||||
preHandlingObservers_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPreHandlingAdvice(
|
||||
std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> advice)
|
||||
{
|
||||
preHandlingAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPostHandlingAdvice(
|
||||
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>
|
||||
advice)
|
||||
{
|
||||
postHandlingAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
void registerPreSendingAdvice(
|
||||
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>
|
||||
advice)
|
||||
{
|
||||
preSendingAdvices_.emplace_back(std::move(advice));
|
||||
}
|
||||
|
||||
// Executors
|
||||
bool passNewConnectionAdvices(const trantor::TcpConnectionPtr &conn) const;
|
||||
void passResponseCreationAdvices(const HttpResponsePtr &resp) const;
|
||||
|
||||
HttpResponsePtr passSyncAdvices(const HttpRequestPtr &req) const;
|
||||
void passPreRoutingObservers(const HttpRequestImplPtr &req) const;
|
||||
void passPreRoutingAdvices(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const;
|
||||
void passPostRoutingObservers(const HttpRequestImplPtr &req) const;
|
||||
void passPostRoutingAdvices(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const;
|
||||
void passPreHandlingObservers(const HttpRequestImplPtr &req) const;
|
||||
void passPreHandlingAdvices(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const;
|
||||
void passPostHandlingAdvices(const HttpRequestImplPtr &req,
|
||||
const HttpResponsePtr &resp) const;
|
||||
void passPreSendingAdvices(const HttpRequestImplPtr &req,
|
||||
const HttpResponsePtr &resp) const;
|
||||
|
||||
private:
|
||||
using SyncAdvice = std::function<HttpResponsePtr(const HttpRequestPtr &)>;
|
||||
using SyncReqObserver = std::function<void(const HttpRequestPtr &)>;
|
||||
using SyncObserver =
|
||||
std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)>;
|
||||
using AsyncAdvice = std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)>;
|
||||
|
||||
// If we want to add aop functions anytime, we can add a mutex here
|
||||
|
||||
std::vector<std::function<bool(const trantor::InetAddress &,
|
||||
const trantor::InetAddress &)>>
|
||||
newConnectionAdvices_;
|
||||
std::vector<std::function<void(const HttpResponsePtr &)>>
|
||||
responseCreationAdvices_;
|
||||
|
||||
std::vector<SyncAdvice> syncAdvices_;
|
||||
std::vector<SyncReqObserver> preRoutingObservers_;
|
||||
std::vector<AsyncAdvice> preRoutingAdvices_;
|
||||
std::vector<SyncReqObserver> postRoutingObservers_;
|
||||
std::vector<AsyncAdvice> postRoutingAdvices_;
|
||||
std::vector<SyncReqObserver> preHandlingObservers_;
|
||||
std::vector<AsyncAdvice> preHandlingAdvices_;
|
||||
std::vector<SyncObserver> postHandlingAdvices_;
|
||||
std::vector<SyncObserver> preSendingAdvices_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
+661
@@ -0,0 +1,661 @@
|
||||
/**
|
||||
*
|
||||
* @file AccessLogger.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpUtils.h"
|
||||
#include <drogon/drogon.h>
|
||||
#include <drogon/plugins/AccessLogger.h>
|
||||
#include <drogon/plugins/RealIpResolver.h>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
#if !defined _WIN32 && !defined __HAIKU__
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#elif defined __HAIKU__
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <sstream>
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#ifdef DROGON_SPDLOG_SUPPORT
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/logger.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
#ifdef _WIN32
|
||||
#include <spdlog/sinks/msvc_sink.h>
|
||||
// Damn antedeluvian M$ macros
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
#ifndef _WIN32
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#define os_access access
|
||||
#elif !defined(_WIN32) || defined(__MINGW32__)
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
#define os_access access
|
||||
#else
|
||||
#include <io.h>
|
||||
#define os_access _waccess
|
||||
#define R_OK 04
|
||||
#define W_OK 02
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using namespace drogon;
|
||||
using namespace drogon::plugin;
|
||||
|
||||
bool AccessLogger::useRealIp_ = false;
|
||||
|
||||
void AccessLogger::initAndStart(const Json::Value &config)
|
||||
{
|
||||
useLocalTime_ = config.get("use_local_time", true).asBool();
|
||||
showMicroseconds_ = config.get("show_microseconds", true).asBool();
|
||||
timeFormat_ = config.get("custom_time_format", "").asString();
|
||||
useCustomTimeFormat_ = !timeFormat_.empty();
|
||||
useRealIp_ = config.get("use_real_ip", false).asBool();
|
||||
|
||||
logFunctionMap_ = {{"$request_path", outputReqPath},
|
||||
{"$path", outputReqPath},
|
||||
{"$date",
|
||||
[this](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &resp) {
|
||||
outputDate(stream, req, resp);
|
||||
}},
|
||||
{"$request_date",
|
||||
[this](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &resp) {
|
||||
outputReqDate(stream, req, resp);
|
||||
}},
|
||||
{"$request_query", outputReqQuery},
|
||||
{"$request_url", outputReqURL},
|
||||
{"$query", outputReqQuery},
|
||||
{"$url", outputReqURL},
|
||||
{"$request_version", outputVersion},
|
||||
{"$version", outputVersion},
|
||||
{"$request", outputReqLine},
|
||||
{"$remote_addr", outputRemoteAddr},
|
||||
{"$local_addr", outputLocalAddr},
|
||||
{"$request_len", outputReqLength},
|
||||
{"$body_bytes_received", outputReqLength},
|
||||
{"$method", outputMethod},
|
||||
{"$thread", outputThreadNumber},
|
||||
{"$response_len", outputRespLength},
|
||||
{"$body_bytes_sent", outputRespLength},
|
||||
{"$status", outputStatusString},
|
||||
{"$status_code", outputStatusCode},
|
||||
{"$processing_time", outputProcessingTime},
|
||||
{"$upstream_http_content-type", outputRespContentType},
|
||||
{"$upstream_http_content_type", outputRespContentType}};
|
||||
auto format = config.get("log_format", "").asString();
|
||||
if (format.empty())
|
||||
{
|
||||
format =
|
||||
"$request_date $method $url [$body_bytes_received] ($remote_addr - "
|
||||
"$local_addr) $status $body_bytes_sent $processing_time";
|
||||
}
|
||||
createLogFunctions(format);
|
||||
auto logPath = config.get("log_path", "").asString();
|
||||
|
||||
if (config.isMember("path_exempt"))
|
||||
{
|
||||
if (config["path_exempt"].isArray())
|
||||
{
|
||||
const auto &exempts = config["path_exempt"];
|
||||
size_t exemptsCount = exempts.size();
|
||||
if (exemptsCount)
|
||||
{
|
||||
std::string regexString;
|
||||
size_t len = 0;
|
||||
for (const auto &exempt : exempts)
|
||||
{
|
||||
assert(exempt.isString());
|
||||
len += exempt.size();
|
||||
}
|
||||
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
|
||||
|
||||
const auto last = --exempts.end();
|
||||
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
|
||||
regexString.append("(")
|
||||
.append(exempt->asString())
|
||||
.append(")|");
|
||||
regexString.append("(").append(last->asString()).append(")");
|
||||
|
||||
exemptRegex_ = std::regex(regexString);
|
||||
regexFlag_ = true;
|
||||
}
|
||||
}
|
||||
else if (config["path_exempt"].isString())
|
||||
{
|
||||
exemptRegex_ = std::regex(config["path_exempt"].asString());
|
||||
regexFlag_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "path_exempt must be a string or string array!";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DROGON_SPDLOG_SUPPORT
|
||||
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
|
||||
config.get("use_spdlog", false).asBool();
|
||||
if (logWithSpdlog)
|
||||
{
|
||||
logIndex_ = config.get("log_index", 0).asInt();
|
||||
// Do nothing if already initialized...
|
||||
if (!trantor::Logger::getSpdLogger(logIndex_))
|
||||
{
|
||||
trantor::Logger::enableSpdLog(logIndex_);
|
||||
// Get the new logger & replace its sinks with the ones of the
|
||||
// config
|
||||
auto logger = trantor::Logger::getSpdLogger(logIndex_);
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
while (!logPath.empty())
|
||||
{
|
||||
// 1. check existence of folder or try to create it
|
||||
auto fsLogPath =
|
||||
std::filesystem::path(utils::toNativePath(logPath));
|
||||
std::error_code fsErr;
|
||||
if (!std::filesystem::create_directories(fsLogPath, fsErr) &&
|
||||
fsErr)
|
||||
{
|
||||
LOG_ERROR << "could not create log file path";
|
||||
break;
|
||||
}
|
||||
// 2. check if we have rights to create files in the folder
|
||||
if (os_access(fsLogPath.native().c_str(), W_OK) != 0)
|
||||
{
|
||||
LOG_ERROR << "cannot create files in log folder";
|
||||
break;
|
||||
}
|
||||
std::filesystem::path fileName(
|
||||
config.get("log_file", "access.log").asString());
|
||||
if (fileName.empty())
|
||||
fileName = "access.log";
|
||||
else
|
||||
fileName.replace_extension(".log");
|
||||
auto sizeLimit = config.get("log_size_limit", 0).asUInt64();
|
||||
if (sizeLimit == 0)
|
||||
sizeLimit = config.get("size_limit", 0).asUInt64();
|
||||
if (sizeLimit == 0) // 0 is not allowed by this sink
|
||||
sizeLimit = std::numeric_limits<std::size_t>::max();
|
||||
std::size_t maxFiles = config.get("max_files", 0).asUInt();
|
||||
sinks.push_back(
|
||||
std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
(fsLogPath / fileName).string(),
|
||||
sizeLimit,
|
||||
// spdlog limitation
|
||||
std::min(maxFiles, std::size_t(20000)),
|
||||
false));
|
||||
break;
|
||||
}
|
||||
if (sinks.empty())
|
||||
sinks.push_back(
|
||||
std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
|
||||
#if defined(_WIN32) && defined(_DEBUG)
|
||||
// On Windows with debug, it may be interesting to have the logs
|
||||
// directly in the Visual Studio / WinDbg console
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::msvc_sink_mt>());
|
||||
#endif
|
||||
logger->sinks() = sinks;
|
||||
// Override the pattern set in
|
||||
// trantor::Logger::getDefaultSpdLogger() and let AccessLogger
|
||||
// format the output
|
||||
logger->set_pattern("%v");
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (!logPath.empty())
|
||||
{
|
||||
auto fileName = config.get("log_file", "access.log").asString();
|
||||
auto extension = std::string(".log");
|
||||
auto pos = fileName.rfind('.');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
extension = fileName.substr(pos);
|
||||
fileName = fileName.substr(0, pos);
|
||||
}
|
||||
if (fileName.empty())
|
||||
{
|
||||
fileName = "access";
|
||||
}
|
||||
asyncFileLogger_.setFileName(fileName, extension, logPath);
|
||||
asyncFileLogger_.startLogging();
|
||||
logIndex_ = config.get("log_index", 0).asInt();
|
||||
trantor::Logger::setOutputFunction(
|
||||
[&](const char *msg, const uint64_t len) {
|
||||
asyncFileLogger_.output(msg, len);
|
||||
},
|
||||
[&]() { asyncFileLogger_.flush(); },
|
||||
logIndex_);
|
||||
auto sizeLimit = config.get("log_size_limit", 0).asUInt64();
|
||||
if (sizeLimit == 0)
|
||||
{
|
||||
// In earlier code, "size_limit" is taken instead of
|
||||
// "log_size_limit" as it said in the comment in AccessLogger.h.
|
||||
// In order to ensure backward compatibility we still take this
|
||||
// field as a fallback.
|
||||
sizeLimit = config.get("size_limit", 0).asUInt64();
|
||||
}
|
||||
if (sizeLimit > 0)
|
||||
{
|
||||
asyncFileLogger_.setFileSizeLimit(sizeLimit);
|
||||
}
|
||||
auto maxFiles = config.get("max_files", 0).asUInt();
|
||||
asyncFileLogger_.setMaxFiles(maxFiles);
|
||||
}
|
||||
drogon::app().registerPreSendingAdvice(
|
||||
[this](const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &resp) {
|
||||
if (regexFlag_)
|
||||
{
|
||||
if (!std::regex_match(req->path(), exemptRegex_))
|
||||
{
|
||||
logging(LOG_RAW_TO(logIndex_), req, resp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logging(LOG_RAW_TO(logIndex_), req, resp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AccessLogger::shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
void AccessLogger::logging(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &resp)
|
||||
{
|
||||
for (auto &func : logFunctions_)
|
||||
{
|
||||
func(stream, req, resp);
|
||||
}
|
||||
}
|
||||
|
||||
void AccessLogger::createLogFunctions(std::string format)
|
||||
{
|
||||
std::string rawString;
|
||||
while (!format.empty())
|
||||
{
|
||||
LOG_TRACE << format;
|
||||
auto pos = format.find('$');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
rawString += format.substr(0, pos);
|
||||
|
||||
format = format.substr(pos);
|
||||
std::regex e{"^\\$[a-zA-Z0-9\\-_]+"};
|
||||
std::smatch m;
|
||||
if (std::regex_search(format, m, e))
|
||||
{
|
||||
if (!rawString.empty())
|
||||
{
|
||||
logFunctions_.emplace_back(
|
||||
[rawString](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &) {
|
||||
stream << rawString;
|
||||
});
|
||||
rawString.clear();
|
||||
}
|
||||
auto placeholder = m[0];
|
||||
logFunctions_.emplace_back(newLogFunction(placeholder));
|
||||
format = m.suffix().str();
|
||||
}
|
||||
else
|
||||
{
|
||||
rawString += '$';
|
||||
format = format.substr(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rawString += format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!rawString.empty())
|
||||
{
|
||||
logFunctions_.emplace_back(
|
||||
[rawString =
|
||||
std::move(rawString)](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &) {
|
||||
stream << rawString << "\n";
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
logFunctions_.emplace_back(
|
||||
[](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &) { stream << "\n"; });
|
||||
}
|
||||
}
|
||||
|
||||
AccessLogger::LogFunction AccessLogger::newLogFunction(
|
||||
const std::string &placeholder)
|
||||
{
|
||||
auto iter = logFunctionMap_.find(placeholder);
|
||||
if (iter != logFunctionMap_.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
if (placeholder.find("$http_") == 0 && placeholder.size() > 6)
|
||||
{
|
||||
auto headerName = placeholder.substr(6);
|
||||
return [headerName =
|
||||
std::move(headerName)](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &) {
|
||||
outputReqHeader(stream, req, headerName);
|
||||
};
|
||||
}
|
||||
if (placeholder.find("$cookie_") == 0 && placeholder.size() > 8)
|
||||
{
|
||||
auto cookieName = placeholder.substr(8);
|
||||
return [cookieName =
|
||||
std::move(cookieName)](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &) {
|
||||
outputReqCookie(stream, req, cookieName);
|
||||
};
|
||||
}
|
||||
if (placeholder.find("$upstream_http_") == 0 && placeholder.size() > 15)
|
||||
{
|
||||
auto headerName = placeholder.substr(15);
|
||||
return [headerName = std::move(
|
||||
headerName)](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &resp) {
|
||||
outputRespHeader(stream, resp, headerName);
|
||||
};
|
||||
}
|
||||
return [placeholder](trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &) {
|
||||
stream << placeholder;
|
||||
};
|
||||
}
|
||||
|
||||
void AccessLogger::outputReqPath(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
stream << req->path();
|
||||
}
|
||||
|
||||
void AccessLogger::outputDate(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &) const
|
||||
{
|
||||
if (useCustomTimeFormat_)
|
||||
{
|
||||
if (useLocalTime_)
|
||||
{
|
||||
stream << trantor::Date::now().toCustomFormattedStringLocal(
|
||||
timeFormat_, showMicroseconds_);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << trantor::Date::now().toCustomFormattedString(
|
||||
timeFormat_, showMicroseconds_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useLocalTime_)
|
||||
{
|
||||
stream << trantor::Date::now().toFormattedStringLocal(
|
||||
showMicroseconds_);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << trantor::Date::now().toFormattedString(showMicroseconds_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccessLogger::outputReqDate(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &) const
|
||||
{
|
||||
if (useCustomTimeFormat_)
|
||||
{
|
||||
if (useLocalTime_)
|
||||
{
|
||||
stream << req->creationDate().toCustomFormattedStringLocal(
|
||||
timeFormat_, showMicroseconds_);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << req->creationDate().toCustomFormattedString(
|
||||
timeFormat_, showMicroseconds_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useLocalTime_)
|
||||
{
|
||||
stream << req->creationDate().toFormattedStringLocal(
|
||||
showMicroseconds_);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << req->creationDate().toFormattedString(showMicroseconds_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//$request_query
|
||||
void AccessLogger::outputReqQuery(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
stream << req->query();
|
||||
}
|
||||
|
||||
//$request_url
|
||||
void AccessLogger::outputReqURL(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
auto &query = req->query();
|
||||
if (query.empty())
|
||||
{
|
||||
stream << req->path();
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << req->path() << '?' << query;
|
||||
}
|
||||
}
|
||||
|
||||
//$request_version
|
||||
void AccessLogger::outputVersion(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
stream << req->versionString();
|
||||
}
|
||||
|
||||
//$request
|
||||
void AccessLogger::outputReqLine(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
auto &query = req->query();
|
||||
if (query.empty())
|
||||
{
|
||||
stream << req->methodString() << " " << req->path() << " "
|
||||
<< req->versionString();
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << req->methodString() << " " << req->path() << '?' << query
|
||||
<< " " << req->versionString();
|
||||
}
|
||||
}
|
||||
|
||||
void AccessLogger::outputRemoteAddr(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
if (useRealIp_)
|
||||
{
|
||||
stream << RealIpResolver::GetRealAddr(req).toIpPort();
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << req->peerAddr().toIpPort();
|
||||
}
|
||||
}
|
||||
|
||||
void AccessLogger::outputLocalAddr(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
stream << req->localAddr().toIpPort();
|
||||
}
|
||||
|
||||
void AccessLogger::outputReqLength(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
stream << req->body().length();
|
||||
}
|
||||
|
||||
void AccessLogger::outputRespLength(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &resp)
|
||||
{
|
||||
stream << resp->body().length();
|
||||
}
|
||||
|
||||
void AccessLogger::outputMethod(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
stream << req->methodString();
|
||||
}
|
||||
|
||||
void AccessLogger::outputThreadNumber(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
#ifdef __linux__
|
||||
static thread_local pid_t threadId_{0};
|
||||
#else
|
||||
static thread_local uint64_t threadId_{0};
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
if (threadId_ == 0)
|
||||
threadId_ = static_cast<pid_t>(::syscall(SYS_gettid));
|
||||
#elif defined __FreeBSD__
|
||||
if (threadId_ == 0)
|
||||
{
|
||||
threadId_ = pthread_getthreadid_np();
|
||||
}
|
||||
#elif defined __OpenBSD__
|
||||
if (threadId_ == 0)
|
||||
{
|
||||
threadId_ = getthrid();
|
||||
}
|
||||
#elif defined _WIN32 || defined __HAIKU__
|
||||
if (threadId_ == 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
threadId_ = std::stoull(ss.str());
|
||||
}
|
||||
#else
|
||||
if (threadId_ == 0)
|
||||
{
|
||||
pthread_threadid_np(NULL, &threadId_);
|
||||
}
|
||||
#endif
|
||||
stream << threadId_;
|
||||
}
|
||||
|
||||
//$http_[header_name]
|
||||
void AccessLogger::outputReqHeader(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const std::string &headerName)
|
||||
{
|
||||
stream << headerName << ": " << req->getHeader(headerName);
|
||||
}
|
||||
|
||||
//$cookie_[cookie_name]
|
||||
void AccessLogger::outputReqCookie(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const std::string &cookie)
|
||||
{
|
||||
stream << "(cookie)" << cookie << "=" << req->getCookie(cookie);
|
||||
}
|
||||
|
||||
//$upstream_http_[header_name]
|
||||
void AccessLogger::outputRespHeader(trantor::LogStream &stream,
|
||||
const drogon::HttpResponsePtr &resp,
|
||||
const std::string &headerName)
|
||||
{
|
||||
stream << headerName << ": " << resp->getHeader(headerName);
|
||||
}
|
||||
|
||||
//$status
|
||||
void AccessLogger::outputStatusString(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &resp)
|
||||
{
|
||||
int code = resp->getStatusCode();
|
||||
stream << code << " " << statusCodeToString(code);
|
||||
}
|
||||
|
||||
//$status_code
|
||||
void AccessLogger::outputStatusCode(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &resp)
|
||||
{
|
||||
stream << resp->getStatusCode();
|
||||
}
|
||||
|
||||
//$processing_time
|
||||
void AccessLogger::outputProcessingTime(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpResponsePtr &)
|
||||
{
|
||||
auto start = req->creationDate();
|
||||
auto end = trantor::Date::now();
|
||||
auto duration =
|
||||
end.microSecondsSinceEpoch() - start.microSecondsSinceEpoch();
|
||||
auto seconds = static_cast<double>(duration) / 1000000.0;
|
||||
stream << seconds;
|
||||
}
|
||||
|
||||
//$upstream_http_content-type $upstream_http_content_type
|
||||
void AccessLogger::outputRespContentType(trantor::LogStream &stream,
|
||||
const drogon::HttpRequestPtr &,
|
||||
const drogon::HttpResponsePtr &resp)
|
||||
{
|
||||
stream << resp->contentTypeString();
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
*
|
||||
* CacheFile.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CacheFile.h"
|
||||
#include <trantor/utils/Logger.h>
|
||||
#ifdef _WIN32
|
||||
#include <mman.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
CacheFile::CacheFile(const std::string &path, bool autoDelete)
|
||||
: autoDelete_(autoDelete), path_(path)
|
||||
{
|
||||
#ifndef _MSC_VER
|
||||
file_ = fopen(path_.data(), "wb+");
|
||||
#else
|
||||
auto wPath{drogon::utils::toNativePath(path)};
|
||||
if (_wfopen_s(&file_, wPath.c_str(), L"wb+") != 0)
|
||||
{
|
||||
file_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
if (!file_)
|
||||
LOG_SYSERR << "CacheFile fopen:";
|
||||
}
|
||||
|
||||
CacheFile::~CacheFile()
|
||||
{
|
||||
if (data_)
|
||||
{
|
||||
munmap(data_, dataLength_);
|
||||
}
|
||||
if (autoDelete_ && file_)
|
||||
{
|
||||
fclose(file_);
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
auto wPath{drogon::utils::toNativePath(path_)};
|
||||
_wunlink(wPath.c_str());
|
||||
#else
|
||||
unlink(path_.data());
|
||||
#endif
|
||||
}
|
||||
else if (file_)
|
||||
{
|
||||
fclose(file_);
|
||||
}
|
||||
}
|
||||
|
||||
void CacheFile::append(const char *data, size_t length)
|
||||
{
|
||||
if (file_)
|
||||
{
|
||||
if (!fwrite(data, length, 1, file_))
|
||||
LOG_SYSERR << "CacheFile append:";
|
||||
}
|
||||
}
|
||||
|
||||
size_t CacheFile::length()
|
||||
{
|
||||
if (file_)
|
||||
#ifdef _WIN32
|
||||
return _ftelli64(file_);
|
||||
#else
|
||||
return ftell(file_);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *CacheFile::data()
|
||||
{
|
||||
if (!file_)
|
||||
return nullptr;
|
||||
if (!data_)
|
||||
{
|
||||
fflush(file_);
|
||||
#ifdef _WIN32
|
||||
auto fd = _fileno(file_);
|
||||
#else
|
||||
auto fd = fileno(file_);
|
||||
#endif
|
||||
dataLength_ = length();
|
||||
data_ = static_cast<char *>(
|
||||
mmap(nullptr, dataLength_, PROT_READ, MAP_SHARED, fd, 0));
|
||||
if (data_ == MAP_FAILED)
|
||||
{
|
||||
data_ = nullptr;
|
||||
LOG_SYSERR << "CacheFile mmap:";
|
||||
}
|
||||
}
|
||||
return data_;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
*
|
||||
* CacheFile.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class CacheFile : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
explicit CacheFile(const std::string &path, bool autoDelete = true);
|
||||
~CacheFile();
|
||||
|
||||
void append(const std::string &data)
|
||||
{
|
||||
append(data.data(), data.length());
|
||||
}
|
||||
|
||||
void append(const char *data, size_t length);
|
||||
|
||||
std::string_view getStringView()
|
||||
{
|
||||
if (data())
|
||||
return std::string_view(data_, dataLength_);
|
||||
return std::string_view();
|
||||
}
|
||||
|
||||
private:
|
||||
char *data();
|
||||
size_t length();
|
||||
FILE *file_{nullptr};
|
||||
bool autoDelete_{true};
|
||||
const std::string path_;
|
||||
char *data_{nullptr};
|
||||
size_t dataLength_{0};
|
||||
};
|
||||
} // namespace drogon
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <json/json.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class ConfigAdapter
|
||||
{
|
||||
public:
|
||||
virtual ~ConfigAdapter() = default;
|
||||
virtual Json::Value getJson(const std::string &content) const
|
||||
noexcept(false) = 0;
|
||||
virtual std::vector<std::string> getExtensions() const = 0;
|
||||
};
|
||||
|
||||
using ConfigAdapterPtr = std::shared_ptr<ConfigAdapter>;
|
||||
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "ConfigAdapterManager.h"
|
||||
#include "JsonConfigAdapter.h"
|
||||
#include "YamlConfigAdapter.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace drogon;
|
||||
#define REGISTER_CONFIG_ADAPTER(adapter) \
|
||||
{ \
|
||||
auto adapterPtr = std::make_shared<adapter>(); \
|
||||
auto exts = adapterPtr->getExtensions(); \
|
||||
for (auto ext : exts) \
|
||||
{ \
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); \
|
||||
adapters_[ext] = adapterPtr; \
|
||||
} \
|
||||
}
|
||||
|
||||
ConfigAdapterManager &ConfigAdapterManager::instance()
|
||||
{
|
||||
static ConfigAdapterManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Json::Value ConfigAdapterManager::getJson(const std::string &content,
|
||||
std::string ext) const noexcept(false)
|
||||
{
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
||||
auto it = adapters_.find(ext);
|
||||
if (it == adapters_.end())
|
||||
{
|
||||
throw std::runtime_error("No valid parser for this config file!");
|
||||
}
|
||||
return it->second->getJson(content);
|
||||
}
|
||||
|
||||
ConfigAdapterManager::ConfigAdapterManager()
|
||||
{
|
||||
REGISTER_CONFIG_ADAPTER(JsonConfigAdapter);
|
||||
REGISTER_CONFIG_ADAPTER(YamlConfigAdapter);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "ConfigAdapterManager.h"
|
||||
#include "ConfigAdapter.h"
|
||||
#include <map>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class ConfigAdapterManager
|
||||
{
|
||||
public:
|
||||
static ConfigAdapterManager &instance();
|
||||
Json::Value getJson(const std::string &content, std::string ext) const
|
||||
noexcept(false);
|
||||
|
||||
private:
|
||||
ConfigAdapterManager();
|
||||
std::map<std::string, ConfigAdapterPtr> adapters_;
|
||||
};
|
||||
} // namespace drogon
|
||||
+709
@@ -0,0 +1,709 @@
|
||||
/**
|
||||
*
|
||||
* @file ConfigLoader.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ConfigLoader.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include <drogon/config.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#if !defined(_WIN32)
|
||||
#include <unistd.h>
|
||||
#define os_access access
|
||||
#else
|
||||
#include <io.h>
|
||||
#ifndef __MINGW32__
|
||||
#define os_access _waccess
|
||||
#define R_OK 04
|
||||
#define W_OK 02
|
||||
#else
|
||||
#define os_access access
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include "ConfigAdapterManager.h"
|
||||
#include <filesystem>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
static bool bytesSize(std::string &sizeStr, size_t &size)
|
||||
{
|
||||
if (sizeStr.empty())
|
||||
{
|
||||
size = -1;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = 1;
|
||||
switch (sizeStr[sizeStr.length() - 1])
|
||||
{
|
||||
case 'k':
|
||||
case 'K':
|
||||
size = 1024;
|
||||
sizeStr.resize(sizeStr.length() - 1);
|
||||
break;
|
||||
case 'M':
|
||||
case 'm':
|
||||
size = (1024 * 1024);
|
||||
sizeStr.resize(sizeStr.length() - 1);
|
||||
break;
|
||||
case 'g':
|
||||
case 'G':
|
||||
size = (1024 * 1024 * 1024);
|
||||
sizeStr.resize(sizeStr.length() - 1);
|
||||
break;
|
||||
#if ((ULONG_MAX) != (UINT_MAX))
|
||||
// 64bit system
|
||||
case 't':
|
||||
case 'T':
|
||||
size = (1024L * 1024L * 1024L * 1024L);
|
||||
sizeStr.resize(sizeStr.length() - 1);
|
||||
break;
|
||||
#endif
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
std::istringstream iss(sizeStr);
|
||||
size_t tmpSize;
|
||||
iss >> tmpSize;
|
||||
if (iss.fail())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((size_t(-1) / tmpSize) >= size)
|
||||
size *= tmpSize;
|
||||
else
|
||||
{
|
||||
size = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoader::ConfigLoader(const std::string &configFile)
|
||||
{
|
||||
if (os_access(drogon::utils::toNativePath(configFile).c_str(), 0) != 0)
|
||||
{
|
||||
throw std::runtime_error("Config file " + configFile + " not found!");
|
||||
}
|
||||
if (os_access(drogon::utils::toNativePath(configFile).c_str(), R_OK) != 0)
|
||||
{
|
||||
throw std::runtime_error("No permission to read config file " +
|
||||
configFile);
|
||||
}
|
||||
configFile_ = configFile;
|
||||
auto pos = configFile.find_last_of('.');
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
throw std::runtime_error("Invalid config file name!");
|
||||
}
|
||||
auto ext = configFile.substr(pos + 1);
|
||||
std::ifstream infile(drogon::utils::toNativePath(configFile).c_str(),
|
||||
std::ifstream::in);
|
||||
// get the content of the infile
|
||||
std::string content((std::istreambuf_iterator<char>(infile)),
|
||||
std::istreambuf_iterator<char>());
|
||||
try
|
||||
{
|
||||
configJsonRoot_ =
|
||||
ConfigAdapterManager::instance().getJson(content, std::move(ext));
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
throw std::runtime_error("Error reading config file " + configFile +
|
||||
": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoader::ConfigLoader(const Json::Value &data) : configJsonRoot_(data)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigLoader::ConfigLoader(Json::Value &&data)
|
||||
: configJsonRoot_(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
ConfigLoader::~ConfigLoader()
|
||||
{
|
||||
}
|
||||
|
||||
static void loadLogSetting(const Json::Value &log)
|
||||
{
|
||||
if (!log)
|
||||
return;
|
||||
auto useSpdlog = log.get("use_spdlog", false).asBool();
|
||||
auto logPath = log.get("log_path", "").asString();
|
||||
auto baseName = log.get("logfile_base_name", "").asString();
|
||||
auto logSize = log.get("log_size_limit", 100000000).asUInt64();
|
||||
auto maxFiles = log.get("max_files", 0).asUInt();
|
||||
HttpAppFrameworkImpl::instance().setLogPath(
|
||||
logPath, baseName, logSize, maxFiles, useSpdlog);
|
||||
auto logLevel = log.get("log_level", "DEBUG").asString();
|
||||
if (logLevel == "TRACE")
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
}
|
||||
else if (logLevel == "DEBUG")
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kDebug);
|
||||
}
|
||||
else if (logLevel == "INFO")
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
|
||||
}
|
||||
else if (logLevel == "WARN")
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kWarn);
|
||||
}
|
||||
auto localTime = log.get("display_local_time", false).asBool();
|
||||
trantor::Logger::setDisplayLocalTime(localTime);
|
||||
}
|
||||
|
||||
static void loadControllers(const Json::Value &controllers)
|
||||
{
|
||||
if (!controllers)
|
||||
return;
|
||||
for (auto const &controller : controllers)
|
||||
{
|
||||
auto path = controller.get("path", "").asString();
|
||||
auto ctrlName = controller.get("controller", "").asString();
|
||||
if (path == "" || ctrlName == "")
|
||||
continue;
|
||||
std::vector<internal::HttpConstraint> constraints;
|
||||
if (!controller["http_methods"].isNull())
|
||||
{
|
||||
for (auto const &method : controller["http_methods"])
|
||||
{
|
||||
auto strMethod = method.asString();
|
||||
std::transform(strMethod.begin(),
|
||||
strMethod.end(),
|
||||
strMethod.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
if (strMethod == "get")
|
||||
{
|
||||
constraints.push_back(Get);
|
||||
}
|
||||
else if (strMethod == "post")
|
||||
{
|
||||
constraints.push_back(Post);
|
||||
}
|
||||
else if (strMethod == "head") // The branch never work
|
||||
{
|
||||
constraints.push_back(Head);
|
||||
}
|
||||
else if (strMethod == "put")
|
||||
{
|
||||
constraints.push_back(Put);
|
||||
}
|
||||
else if (strMethod == "delete")
|
||||
{
|
||||
constraints.push_back(Delete);
|
||||
}
|
||||
else if (strMethod == "patch")
|
||||
{
|
||||
constraints.push_back(Patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!controller["filters"].isNull())
|
||||
{
|
||||
for (auto const &filter : controller["filters"])
|
||||
{
|
||||
constraints.push_back(filter.asString());
|
||||
}
|
||||
}
|
||||
drogon::app().registerHttpSimpleController(path, ctrlName, constraints);
|
||||
}
|
||||
}
|
||||
|
||||
static void loadApp(const Json::Value &app)
|
||||
{
|
||||
if (!app)
|
||||
return;
|
||||
// threads number
|
||||
auto threadsNum = app.get("threads_num", 1).asUInt64();
|
||||
if (threadsNum == 1)
|
||||
{
|
||||
threadsNum = app.get("number_of_threads", 1).asUInt64();
|
||||
}
|
||||
if (threadsNum == 0)
|
||||
{
|
||||
// set the number to the number of processors.
|
||||
threadsNum = std::thread::hardware_concurrency();
|
||||
LOG_TRACE << "The number of processors is " << threadsNum;
|
||||
}
|
||||
if (threadsNum < 1)
|
||||
threadsNum = 1;
|
||||
drogon::app().setThreadNum(threadsNum);
|
||||
// session
|
||||
auto enableSession = app.get("enable_session", false).asBool();
|
||||
if (enableSession)
|
||||
{
|
||||
auto timeout = app.get("session_timeout", 0).asUInt64();
|
||||
auto sameSite = app.get("session_same_site", "Null").asString();
|
||||
auto cookieKey = app.get("session_cookie_key", "JSESSIONID").asString();
|
||||
auto maxAge = app.get("session_max_age", -1).asInt();
|
||||
drogon::app().enableSession(timeout,
|
||||
Cookie::convertString2SameSite(sameSite),
|
||||
cookieKey,
|
||||
maxAge);
|
||||
}
|
||||
else
|
||||
drogon::app().disableSession();
|
||||
// document root
|
||||
auto documentRoot = app.get("document_root", "").asString();
|
||||
if (documentRoot != "")
|
||||
{
|
||||
drogon::app().setDocumentRoot(documentRoot);
|
||||
}
|
||||
if (!app["static_file_headers"].empty())
|
||||
{
|
||||
if (app["static_file_headers"].isArray())
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
for (auto &header : app["static_file_headers"])
|
||||
{
|
||||
headers.emplace_back(
|
||||
std::make_pair(header["name"].asString(),
|
||||
header["value"].asString()));
|
||||
}
|
||||
drogon::app().setStaticFileHeaders(headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"The static_file_headers option must be an array");
|
||||
}
|
||||
}
|
||||
// upload path
|
||||
auto uploadPath = app.get("upload_path", "uploads").asString();
|
||||
drogon::app().setUploadPath(uploadPath);
|
||||
// file types
|
||||
auto fileTypes = app["file_types"];
|
||||
if (fileTypes.isArray() && !fileTypes.empty())
|
||||
{
|
||||
std::vector<std::string> types;
|
||||
for (auto const &fileType : fileTypes)
|
||||
{
|
||||
types.push_back(fileType.asString());
|
||||
LOG_TRACE << "file type:" << types.back();
|
||||
}
|
||||
drogon::app().setFileTypes(types);
|
||||
}
|
||||
// locations
|
||||
if (app.isMember("locations"))
|
||||
{
|
||||
auto &locations = app["locations"];
|
||||
if (!locations.isArray())
|
||||
{
|
||||
throw std::runtime_error("The locations option must be an array");
|
||||
}
|
||||
for (auto &location : locations)
|
||||
{
|
||||
auto uri = location.get("uri_prefix", "").asString();
|
||||
if (uri.empty())
|
||||
continue;
|
||||
auto defaultContentType =
|
||||
location.get("default_content_type", "").asString();
|
||||
auto alias = location.get("alias", "").asString();
|
||||
auto isCaseSensitive =
|
||||
location.get("is_case_sensitive", false).asBool();
|
||||
auto allAll = location.get("allow_all", true).asBool();
|
||||
auto isRecursive = location.get("is_recursive", true).asBool();
|
||||
if (!location["filters"].isNull())
|
||||
{
|
||||
if (location["filters"].isArray())
|
||||
{
|
||||
std::vector<std::string> filters;
|
||||
for (auto const &filter : location["filters"])
|
||||
{
|
||||
filters.push_back(filter.asString());
|
||||
}
|
||||
drogon::app().addALocation(uri,
|
||||
defaultContentType,
|
||||
alias,
|
||||
isCaseSensitive,
|
||||
allAll,
|
||||
isRecursive,
|
||||
filters);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("the filters of location '" + uri +
|
||||
"' should be an array");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
drogon::app().addALocation(uri,
|
||||
defaultContentType,
|
||||
alias,
|
||||
isCaseSensitive,
|
||||
allAll,
|
||||
isRecursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
// max connections
|
||||
auto maxConns = app.get("max_connections", 0).asUInt64();
|
||||
if (maxConns > 0)
|
||||
{
|
||||
drogon::app().setMaxConnectionNum(maxConns);
|
||||
}
|
||||
// max connections per IP
|
||||
auto maxConnsPerIP = app.get("max_connections_per_ip", 0).asUInt64();
|
||||
if (maxConnsPerIP > 0)
|
||||
{
|
||||
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
|
||||
}
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
// dynamic views
|
||||
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
|
||||
if (enableDynamicViews)
|
||||
{
|
||||
auto viewsPaths = app["dynamic_views_path"];
|
||||
if (viewsPaths.isArray() && viewsPaths.size() > 0)
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
for (auto const &viewsPath : viewsPaths)
|
||||
{
|
||||
paths.push_back(viewsPath.asString());
|
||||
LOG_TRACE << "views path:" << paths.back();
|
||||
}
|
||||
auto outputPath =
|
||||
app.get("dynamic_views_output_path", "").asString();
|
||||
drogon::app().enableDynamicViewsLoading(paths, outputPath);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
auto stackLimit = app.get("json_parser_stack_limit", 1000).asUInt64();
|
||||
drogon::app().setJsonParserStackLimit(stackLimit);
|
||||
auto unicodeEscaping =
|
||||
app.get("enable_unicode_escaping_in_json", true).asBool();
|
||||
drogon::app().setUnicodeEscapingInJson(unicodeEscaping);
|
||||
auto &precision = app["float_precision_in_json"];
|
||||
if (!precision.isNull())
|
||||
{
|
||||
auto precisionLength = precision.get("precision", 0).asUInt64();
|
||||
auto precisionType =
|
||||
precision.get("precision_type", "significant").asString();
|
||||
drogon::app().setFloatPrecisionInJson((unsigned int)precisionLength,
|
||||
precisionType);
|
||||
}
|
||||
// log
|
||||
loadLogSetting(app["log"]);
|
||||
// run as daemon
|
||||
auto runAsDaemon = app.get("run_as_daemon", false).asBool();
|
||||
if (runAsDaemon)
|
||||
{
|
||||
drogon::app().enableRunAsDaemon();
|
||||
}
|
||||
// handle SIGTERM
|
||||
auto handleSigterm = app.get("handle_sig_term", true).asBool();
|
||||
if (!handleSigterm)
|
||||
{
|
||||
drogon::app().disableSigtermHandling();
|
||||
}
|
||||
// relaunch
|
||||
auto relaunch = app.get("relaunch_on_error", false).asBool();
|
||||
if (relaunch)
|
||||
{
|
||||
drogon::app().enableRelaunchOnError();
|
||||
}
|
||||
auto useSendfile = app.get("use_sendfile", true).asBool();
|
||||
drogon::app().enableSendfile(useSendfile);
|
||||
auto useGzip = app.get("use_gzip", true).asBool();
|
||||
drogon::app().enableGzip(useGzip);
|
||||
auto useBr = app.get("use_brotli", false).asBool();
|
||||
drogon::app().enableBrotli(useBr);
|
||||
auto staticFilesCacheTime = app.get("static_files_cache_time", 5).asInt();
|
||||
drogon::app().setStaticFilesCacheTime(staticFilesCacheTime);
|
||||
loadControllers(app["simple_controllers_map"]);
|
||||
// Kick off idle connections
|
||||
auto kickOffTimeout = app.get("idle_connection_timeout", 60).asUInt64();
|
||||
drogon::app().setIdleConnectionTimeout(kickOffTimeout);
|
||||
auto server = app.get("server_header_field", "").asString();
|
||||
if (!server.empty())
|
||||
drogon::app().setServerHeaderField(server);
|
||||
auto sendServerHeader = app.get("enable_server_header", true).asBool();
|
||||
drogon::app().enableServerHeader(sendServerHeader);
|
||||
auto sendDateHeader = app.get("enable_date_header", true).asBool();
|
||||
drogon::app().enableDateHeader(sendDateHeader);
|
||||
auto keepaliveReqs = app.get("keepalive_requests", 0).asUInt64();
|
||||
drogon::app().setKeepaliveRequestsNumber(keepaliveReqs);
|
||||
auto pipeliningReqs = app.get("pipelining_requests", 0).asUInt64();
|
||||
drogon::app().setPipeliningRequestsNumber(pipeliningReqs);
|
||||
auto useGzipStatic = app.get("gzip_static", true).asBool();
|
||||
drogon::app().setGzipStatic(useGzipStatic);
|
||||
auto useBrStatic = app.get("br_static", true).asBool();
|
||||
drogon::app().setBrStatic(useBrStatic);
|
||||
auto maxBodySize = app.get("client_max_body_size", "1M").asString();
|
||||
size_t size;
|
||||
if (bytesSize(maxBodySize, size))
|
||||
{
|
||||
drogon::app().setClientMaxBodySize(size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Error format of client_max_body_size");
|
||||
}
|
||||
auto maxMemoryBodySize =
|
||||
app.get("client_max_memory_body_size", "64K").asString();
|
||||
if (bytesSize(maxMemoryBodySize, size))
|
||||
{
|
||||
drogon::app().setClientMaxMemoryBodySize(size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Error format of client_max_memory_body_size");
|
||||
}
|
||||
auto maxWsMsgSize =
|
||||
app.get("client_max_websocket_message_size", "128K").asString();
|
||||
if (bytesSize(maxWsMsgSize, size))
|
||||
{
|
||||
drogon::app().setClientMaxWebSocketMessageSize(size);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Error format of client_max_websocket_message_size");
|
||||
}
|
||||
drogon::app().enableReusePort(app.get("reuse_port", false).asBool());
|
||||
drogon::app().setHomePage(app.get("home_page", "index.html").asString());
|
||||
drogon::app().setImplicitPageEnable(
|
||||
app.get("use_implicit_page", true).asBool());
|
||||
drogon::app().setImplicitPage(
|
||||
app.get("implicit_page", "index.html").asString());
|
||||
auto mimes = app["mime"];
|
||||
if (!mimes.isNull())
|
||||
{
|
||||
auto names = mimes.getMemberNames();
|
||||
for (const auto &mime : names)
|
||||
{
|
||||
auto ext = mimes[mime];
|
||||
std::vector<std::string> exts;
|
||||
if (ext.isString())
|
||||
exts.push_back(ext.asString());
|
||||
else if (ext.isArray())
|
||||
{
|
||||
for (const auto &extension : ext)
|
||||
exts.push_back(extension.asString());
|
||||
}
|
||||
|
||||
for (const auto &extension : exts)
|
||||
drogon::app().registerCustomExtensionMime(extension, mime);
|
||||
}
|
||||
}
|
||||
bool enableCompressedRequests =
|
||||
app.get("enabled_compressed_request", false).asBool();
|
||||
drogon::app().enableCompressedRequest(enableCompressedRequests);
|
||||
|
||||
drogon::app().enableRequestStream(
|
||||
app.get("enable_request_stream", false).asBool());
|
||||
}
|
||||
|
||||
static void loadDbClients(const Json::Value &dbClients)
|
||||
{
|
||||
if (!dbClients)
|
||||
return;
|
||||
for (auto const &client : dbClients)
|
||||
{
|
||||
auto type = client.get("rdbms", "postgresql").asString();
|
||||
std::transform(type.begin(),
|
||||
type.end(),
|
||||
type.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
auto host = client.get("host", "127.0.0.1").asString();
|
||||
unsigned short port = client.get("port", 5432).asUInt();
|
||||
auto dbname = client.get("dbname", "").asString();
|
||||
if (dbname.empty() && type != "sqlite3")
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Please configure dbname in the configuration file");
|
||||
}
|
||||
auto user = client.get("user", "postgres").asString();
|
||||
auto password = client.get("passwd", "").asString();
|
||||
if (password.empty())
|
||||
{
|
||||
password = client.get("password", "").asString();
|
||||
}
|
||||
auto connNum = client.get("connection_number", 1).asUInt();
|
||||
if (connNum == 1)
|
||||
{
|
||||
connNum = client.get("number_of_connections", 1).asUInt();
|
||||
}
|
||||
auto name = client.get("name", "default").asString();
|
||||
auto filename = client.get("filename", "").asString();
|
||||
auto isFast = client.get("is_fast", false).asBool();
|
||||
auto characterSet = client.get("characterSet", "").asString();
|
||||
if (characterSet.empty())
|
||||
{
|
||||
characterSet = client.get("client_encoding", "").asString();
|
||||
}
|
||||
auto connectOptions = client.get("connect_options", Json::Value());
|
||||
auto timeout = client.get("timeout", -1.0).asDouble();
|
||||
auto autoBatch = client.get("auto_batch", false).asBool();
|
||||
|
||||
std::unordered_map<std::string, std::string> options;
|
||||
if (connectOptions.isObject() && !connectOptions.empty())
|
||||
{
|
||||
for (const auto &key : connectOptions.getMemberNames())
|
||||
{
|
||||
options[key] = connectOptions[key].asString();
|
||||
}
|
||||
}
|
||||
|
||||
HttpAppFrameworkImpl::instance().addDbClient(type,
|
||||
host,
|
||||
port,
|
||||
dbname,
|
||||
user,
|
||||
password,
|
||||
connNum,
|
||||
filename,
|
||||
name,
|
||||
isFast,
|
||||
characterSet,
|
||||
timeout,
|
||||
autoBatch,
|
||||
std::move(options));
|
||||
}
|
||||
}
|
||||
|
||||
static void loadRedisClients(const Json::Value &redisClients)
|
||||
{
|
||||
if (!redisClients)
|
||||
return;
|
||||
for (auto const &client : redisClients)
|
||||
{
|
||||
std::promise<std::string> promise;
|
||||
auto future = promise.get_future();
|
||||
auto host = client.get("host", "127.0.0.1").asString();
|
||||
trantor::Resolver::newResolver()->resolve(
|
||||
host, [&promise](const trantor::InetAddress &address) {
|
||||
promise.set_value(address.toIp());
|
||||
});
|
||||
auto port = client.get("port", 6379).asUInt();
|
||||
auto username = client.get("username", "").asString();
|
||||
auto password = client.get("passwd", "").asString();
|
||||
if (password.empty())
|
||||
{
|
||||
password = client.get("password", "").asString();
|
||||
}
|
||||
auto connNum = client.get("connection_number", 1).asUInt();
|
||||
if (connNum == 1)
|
||||
{
|
||||
connNum = client.get("number_of_connections", 1).asUInt();
|
||||
}
|
||||
auto name = client.get("name", "default").asString();
|
||||
auto isFast = client.get("is_fast", false).asBool();
|
||||
auto timeout = client.get("timeout", -1.0).asDouble();
|
||||
auto db = client.get("db", 0).asUInt();
|
||||
auto hostIp = future.get();
|
||||
drogon::app().createRedisClient(hostIp,
|
||||
port,
|
||||
name,
|
||||
password,
|
||||
connNum,
|
||||
isFast,
|
||||
timeout,
|
||||
db,
|
||||
username);
|
||||
}
|
||||
}
|
||||
|
||||
static void loadListeners(const Json::Value &listeners)
|
||||
{
|
||||
if (!listeners)
|
||||
return;
|
||||
LOG_TRACE << "Has " << listeners.size() << " listeners";
|
||||
for (auto const &listener : listeners)
|
||||
{
|
||||
auto addr = listener.get("address", "0.0.0.0").asString();
|
||||
auto port = (uint16_t)listener.get("port", 0).asUInt();
|
||||
auto useSSL = listener.get("https", false).asBool();
|
||||
auto cert = listener.get("cert", "").asString();
|
||||
auto key = listener.get("key", "").asString();
|
||||
auto useOldTLS = listener.get("use_old_tls", false).asBool();
|
||||
std::vector<std::pair<std::string, std::string>> sslConfCmds;
|
||||
if (listener.isMember("ssl_conf"))
|
||||
{
|
||||
for (const auto &opt : listener["ssl_conf"])
|
||||
{
|
||||
if (opt.size() == 0 || opt.size() > 2)
|
||||
{
|
||||
LOG_FATAL << "SSL configuration option should be an 1 or "
|
||||
"2-element array";
|
||||
abort();
|
||||
}
|
||||
sslConfCmds.emplace_back(opt[0].asString(),
|
||||
opt.get(1, "").asString());
|
||||
}
|
||||
}
|
||||
LOG_TRACE << "Add listener:" << addr << ":" << port;
|
||||
drogon::app().addListener(
|
||||
addr, port, useSSL, cert, key, useOldTLS, sslConfCmds);
|
||||
}
|
||||
}
|
||||
|
||||
static void loadSSL(const Json::Value &sslConf)
|
||||
{
|
||||
if (!sslConf)
|
||||
return;
|
||||
auto key = sslConf.get("key", "").asString();
|
||||
auto cert = sslConf.get("cert", "").asString();
|
||||
drogon::app().setSSLFiles(cert, key);
|
||||
std::vector<std::pair<std::string, std::string>> sslConfCmds;
|
||||
if (sslConf.isMember("conf"))
|
||||
{
|
||||
for (const auto &opt : sslConf["conf"])
|
||||
{
|
||||
if (opt.size() == 0 || opt.size() > 2)
|
||||
{
|
||||
LOG_FATAL << "SSL configuration option should be an 1 or "
|
||||
"2-element array";
|
||||
abort();
|
||||
}
|
||||
sslConfCmds.emplace_back(opt[0].asString(),
|
||||
opt.get(1, "").asString());
|
||||
}
|
||||
}
|
||||
drogon::app().setSSLConfigCommands(sslConfCmds);
|
||||
}
|
||||
|
||||
void ConfigLoader::load()
|
||||
{
|
||||
// std::cout<<configJsonRoot_<<std::endl;
|
||||
loadApp(configJsonRoot_["app"]);
|
||||
loadSSL(configJsonRoot_["ssl"]);
|
||||
loadListeners(configJsonRoot_["listeners"]);
|
||||
loadDbClients(configJsonRoot_["db_clients"]);
|
||||
loadRedisClients(configJsonRoot_["redis_clients"]);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
*
|
||||
* ConfigLoader.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
#include <string>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class ConfigLoader : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
explicit ConfigLoader(const std::string &configFile) noexcept(false);
|
||||
explicit ConfigLoader(const Json::Value &data);
|
||||
explicit ConfigLoader(Json::Value &&data);
|
||||
~ConfigLoader();
|
||||
|
||||
const Json::Value &jsonValue() const
|
||||
{
|
||||
return configJsonRoot_;
|
||||
}
|
||||
|
||||
void load() noexcept(false);
|
||||
|
||||
private:
|
||||
std::string configFile_;
|
||||
Json::Value configJsonRoot_;
|
||||
};
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
*
|
||||
* @file ControllerBinderBase.h
|
||||
* @author Nitromelon
|
||||
*
|
||||
* Copyright 2023, Nitromelon. All rights reserved.
|
||||
* https://github.com/drogonframework/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <drogon/IOThreadStorage.h>
|
||||
#include <drogon/HttpResponse.h>
|
||||
#include "HttpRequestImpl.h"
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpMiddlewareBase;
|
||||
|
||||
/**
|
||||
* @brief A component to associate router class and controller class
|
||||
*/
|
||||
struct ControllerBinderBase
|
||||
{
|
||||
std::string handlerName_;
|
||||
std::vector<std::string> middlewareNames_;
|
||||
std::vector<std::shared_ptr<HttpMiddlewareBase>> middlewares_;
|
||||
IOThreadStorage<HttpResponsePtr> responseCache_;
|
||||
std::shared_ptr<std::string> corsMethods_;
|
||||
bool isCORS_{false};
|
||||
|
||||
virtual ~ControllerBinderBase() = default;
|
||||
virtual void handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const = 0;
|
||||
|
||||
virtual bool isStreamHandler() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct RouteResult
|
||||
{
|
||||
enum
|
||||
{
|
||||
Success,
|
||||
MethodNotAllowed,
|
||||
NotFound
|
||||
} result;
|
||||
|
||||
std::shared_ptr<ControllerBinderBase> binderPtr;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
*
|
||||
* Cookie.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include <drogon/Cookie.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
using namespace drogon;
|
||||
|
||||
std::string Cookie::cookieString() const
|
||||
{
|
||||
constexpr std::string_view prefix = "Set-Cookie: ";
|
||||
std::string ret;
|
||||
// reserve space to reduce frequency allocation
|
||||
ret.reserve(prefix.size() + key_.size() + value_.size() + 30);
|
||||
ret = prefix;
|
||||
ret.append(key_).append("=").append(value_).append("; ");
|
||||
if (expiresDate_.microSecondsSinceEpoch() !=
|
||||
(std::numeric_limits<int64_t>::max)() &&
|
||||
expiresDate_.microSecondsSinceEpoch() >= 0)
|
||||
{
|
||||
ret.append("Expires=")
|
||||
.append(utils::getHttpFullDateStr(expiresDate_))
|
||||
.append("; ");
|
||||
}
|
||||
if (maxAge_.has_value())
|
||||
{
|
||||
ret.append("Max-Age=")
|
||||
.append(std::to_string(maxAge_.value()))
|
||||
.append("; ");
|
||||
}
|
||||
if (!domain_.empty())
|
||||
{
|
||||
ret.append("Domain=").append(domain_).append("; ");
|
||||
}
|
||||
if (!path_.empty())
|
||||
{
|
||||
ret.append("Path=").append(path_).append("; ");
|
||||
}
|
||||
if (sameSite_ != SameSite::kNull)
|
||||
{
|
||||
switch (sameSite_)
|
||||
{
|
||||
case SameSite::kLax:
|
||||
ret.append("SameSite=Lax; ");
|
||||
break;
|
||||
case SameSite::kStrict:
|
||||
ret.append("SameSite=Strict; ");
|
||||
break;
|
||||
case SameSite::kNone:
|
||||
ret.append("SameSite=None; ");
|
||||
// Cookies with SameSite=None must now also specify the Secure
|
||||
// attribute (they require a secure context/HTTPS).
|
||||
ret.append("Secure; ");
|
||||
break;
|
||||
default:
|
||||
// Lax replaced None as the default value to ensure that users
|
||||
// have reasonably robust defense against some CSRF attacks
|
||||
ret.append("SameSite=Lax; ");
|
||||
}
|
||||
}
|
||||
if ((secure_ && sameSite_ != SameSite::kNone) || partitioned_)
|
||||
{
|
||||
ret.append("Secure; ");
|
||||
}
|
||||
if (httpOnly_)
|
||||
{
|
||||
ret.append("HttpOnly; ");
|
||||
}
|
||||
if (partitioned_)
|
||||
{
|
||||
ret.append("Partitioned; ");
|
||||
}
|
||||
ret.resize(ret.length() - 2); // delete last semicolon
|
||||
ret.append("\r\n");
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
*
|
||||
* @file DbClientManager.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <drogon/orm/DbClient.h>
|
||||
#include <drogon/orm/DbConfig.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/IOThreadStorage.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
namespace orm
|
||||
{
|
||||
class DbClientManager : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
void createDbClients(const std::vector<trantor::EventLoop *> &ioLoops);
|
||||
|
||||
DbClientPtr getDbClient(const std::string &name)
|
||||
{
|
||||
assert(dbClientsMap_.find(name) != dbClientsMap_.end());
|
||||
return dbClientsMap_[name];
|
||||
}
|
||||
|
||||
~DbClientManager();
|
||||
|
||||
DbClientPtr getFastDbClient(const std::string &name)
|
||||
{
|
||||
auto iter = dbFastClientsMap_.find(name);
|
||||
assert(iter != dbFastClientsMap_.end());
|
||||
return iter->second.getThreadData();
|
||||
}
|
||||
|
||||
void addDbClient(const DbConfig &config);
|
||||
bool areAllDbClientsAvailable() const noexcept;
|
||||
|
||||
private:
|
||||
std::map<std::string, DbClientPtr> dbClientsMap_;
|
||||
|
||||
struct DbInfo
|
||||
{
|
||||
std::string connectionInfo_;
|
||||
DbConfig config_;
|
||||
};
|
||||
|
||||
std::vector<DbInfo> dbInfos_;
|
||||
std::map<std::string, IOThreadStorage<orm::DbClientPtr>> dbFastClientsMap_;
|
||||
};
|
||||
} // namespace orm
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
*
|
||||
* DbClientManagerSkipped.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DbClientManager.h"
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace drogon::orm;
|
||||
using namespace drogon;
|
||||
|
||||
void DbClientManager::createDbClients(
|
||||
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void DbClientManager::addDbClient(const DbConfig &)
|
||||
{
|
||||
LOG_FATAL << "No database is supported by drogon, please install the "
|
||||
"database development library first.";
|
||||
abort();
|
||||
}
|
||||
|
||||
bool DbClientManager::areAllDbClientsAvailable() const noexcept
|
||||
{
|
||||
LOG_FATAL << "No database is supported by drogon, please install the "
|
||||
"database development library first.";
|
||||
abort();
|
||||
}
|
||||
|
||||
DbClientManager::~DbClientManager()
|
||||
{
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
*
|
||||
* DrClassMap.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include <drogon/DrClassMap.h>
|
||||
#include <drogon/DrObject.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>> &
|
||||
getObjsMap()
|
||||
{
|
||||
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>>
|
||||
singleInstanceMap;
|
||||
return singleInstanceMap;
|
||||
}
|
||||
|
||||
static std::mutex &getMapMutex()
|
||||
{
|
||||
static std::mutex mtx;
|
||||
return mtx;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace drogon
|
||||
|
||||
void DrClassMap::registerClass(const std::string &className,
|
||||
const DrAllocFunc &func,
|
||||
const DrSharedAllocFunc &sharedFunc)
|
||||
{
|
||||
LOG_TRACE << "Register class:" << className;
|
||||
getMap().insert(
|
||||
std::make_pair(className, std::make_pair(func, sharedFunc)));
|
||||
}
|
||||
|
||||
DrObjectBase *DrClassMap::newObject(const std::string &className)
|
||||
{
|
||||
auto iter = getMap().find(className);
|
||||
if (iter != getMap().end())
|
||||
{
|
||||
return iter->second.first();
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<DrObjectBase> DrClassMap::newSharedObject(
|
||||
const std::string &className)
|
||||
{
|
||||
auto iter = getMap().find(className);
|
||||
if (iter != getMap().end())
|
||||
{
|
||||
if (iter->second.second)
|
||||
return iter->second.second();
|
||||
else
|
||||
return std::shared_ptr<DrObjectBase>(iter->second.first());
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::shared_ptr<DrObjectBase> &DrClassMap::getSingleInstance(
|
||||
const std::string &className)
|
||||
{
|
||||
auto &mtx = internal::getMapMutex();
|
||||
auto &singleInstanceMap = internal::getObjsMap();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
auto iter = singleInstanceMap.find(className);
|
||||
if (iter != singleInstanceMap.end())
|
||||
return iter->second;
|
||||
}
|
||||
auto newObj = newSharedObject(className);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
auto ret = singleInstanceMap.insert(
|
||||
std::make_pair(className, std::move(newObj)));
|
||||
return ret.first->second;
|
||||
}
|
||||
}
|
||||
|
||||
void DrClassMap::setSingleInstance(const std::shared_ptr<DrObjectBase> &ins)
|
||||
{
|
||||
auto &mtx = internal::getMapMutex();
|
||||
auto &singleInstanceMap = internal::getObjsMap();
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
singleInstanceMap[ins->className()] = ins;
|
||||
}
|
||||
|
||||
std::vector<std::string> DrClassMap::getAllClassName()
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
for (auto const &iter : getMap())
|
||||
{
|
||||
ret.push_back(iter.first);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::pair<DrAllocFunc, DrSharedAllocFunc>> &
|
||||
DrClassMap::getMap()
|
||||
{
|
||||
static std::unordered_map<std::string,
|
||||
std::pair<DrAllocFunc, DrSharedAllocFunc>>
|
||||
map;
|
||||
return map;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
*
|
||||
* @file DrTemplateBase.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include <drogon/DrClassMap.h>
|
||||
#include <drogon/DrTemplateBase.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
std::shared_ptr<DrTemplateBase> DrTemplateBase::newTemplate(
|
||||
const std::string &templateName)
|
||||
{
|
||||
LOG_TRACE << "http view name=" << templateName;
|
||||
auto l = templateName.length();
|
||||
if (l >= 4 && templateName[l - 4] == '.' && templateName[l - 3] == 'c' &&
|
||||
templateName[l - 2] == 's' && templateName[l - 1] == 'p')
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
std::string newName;
|
||||
newName.reserve(templateName.size());
|
||||
if (templateName[0] == '/' || templateName[0] == '\\')
|
||||
{
|
||||
pos = 1;
|
||||
}
|
||||
else if (templateName[0] == '.' &&
|
||||
(templateName[1] == '/' || templateName[1] == '\\'))
|
||||
{
|
||||
pos = 2;
|
||||
}
|
||||
while (pos < l - 4)
|
||||
{
|
||||
if (templateName[pos] == '/' || templateName[pos] == '\\')
|
||||
{
|
||||
newName.append("::");
|
||||
}
|
||||
else
|
||||
{
|
||||
newName.append(1, templateName[pos]);
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
return std::shared_ptr<DrTemplateBase>(dynamic_cast<DrTemplateBase *>(
|
||||
drogon::DrClassMap::newObject(newName)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<DrTemplateBase>(dynamic_cast<DrTemplateBase *>(
|
||||
drogon::DrClassMap::newObject(templateName)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "FixedWindowRateLimiter.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
FixedWindowRateLimiter::FixedWindowRateLimiter(
|
||||
size_t capacity,
|
||||
std::chrono::duration<double> timeUnit)
|
||||
: capacity_(capacity),
|
||||
lastTime_(std::chrono::steady_clock::now()),
|
||||
timeUnit_(timeUnit)
|
||||
{
|
||||
}
|
||||
|
||||
// implementation of the fixed window algorithm
|
||||
|
||||
bool FixedWindowRateLimiter::isAllowed()
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
now - lastTime_);
|
||||
if (duration >= timeUnit_)
|
||||
{
|
||||
currentRequests_ = 0;
|
||||
lastTime_ = now;
|
||||
}
|
||||
if (currentRequests_ < capacity_)
|
||||
{
|
||||
currentRequests_++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <drogon/RateLimiter.h>
|
||||
#include <chrono>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class FixedWindowRateLimiter : public RateLimiter
|
||||
{
|
||||
public:
|
||||
FixedWindowRateLimiter(size_t capacity,
|
||||
std::chrono::duration<double> timeUnit);
|
||||
bool isAllowed() override;
|
||||
~FixedWindowRateLimiter() noexcept override = default;
|
||||
|
||||
private:
|
||||
size_t capacity_;
|
||||
size_t currentRequests_{0};
|
||||
std::chrono::steady_clock::time_point lastTime_;
|
||||
std::chrono::duration<double> timeUnit_;
|
||||
};
|
||||
} // namespace drogon
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#include <drogon/plugins/GlobalFilters.h>
|
||||
#include <drogon/DrClassMap.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include "MiddlewaresFunction.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
|
||||
using namespace drogon::plugin;
|
||||
|
||||
void GlobalFilters::initAndStart(const Json::Value &config)
|
||||
{
|
||||
if (config.isMember("filters") && config["filters"].isArray())
|
||||
{
|
||||
auto &filters = config["filters"];
|
||||
|
||||
for (auto const &filter : filters)
|
||||
{
|
||||
if (filter.isString())
|
||||
{
|
||||
auto filterPtr = std::dynamic_pointer_cast<HttpFilterBase>(
|
||||
drogon::DrClassMap::getSingleInstance(filter.asString()));
|
||||
if (filterPtr)
|
||||
{
|
||||
filters_.push_back(filterPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Filter " << filter.asString()
|
||||
<< " not found!";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.isMember("exempt"))
|
||||
{
|
||||
auto exempt = config["exempt"];
|
||||
if (exempt.isArray())
|
||||
{
|
||||
std::string regexStr;
|
||||
for (auto const &ex : exempt)
|
||||
{
|
||||
if (ex.isString())
|
||||
{
|
||||
regexStr.append("(").append(ex.asString()).append(")|");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "exempt must be a string array!";
|
||||
}
|
||||
}
|
||||
if (!regexStr.empty())
|
||||
{
|
||||
regexStr.pop_back();
|
||||
exemptPegex_ = std::regex(regexStr);
|
||||
regexFlag_ = true;
|
||||
}
|
||||
}
|
||||
else if (exempt.isString())
|
||||
{
|
||||
exemptPegex_ = std::regex(exempt.asString());
|
||||
regexFlag_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "exempt must be a string or string array!";
|
||||
}
|
||||
}
|
||||
std::weak_ptr<GlobalFilters> weakPtr = shared_from_this();
|
||||
drogon::app().registerPreRoutingAdvice(
|
||||
[weakPtr](const drogon::HttpRequestPtr &req,
|
||||
drogon::AdviceCallback &&acb,
|
||||
drogon::AdviceChainCallback &&accb) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
{
|
||||
accb();
|
||||
return;
|
||||
}
|
||||
if (thisPtr->regexFlag_)
|
||||
{
|
||||
if (std::regex_match(req->path(), thisPtr->exemptPegex_))
|
||||
{
|
||||
accb();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
drogon::middlewares_function::doFilters(
|
||||
thisPtr->filters_,
|
||||
std::static_pointer_cast<HttpRequestImpl>(req),
|
||||
[acb = std::move(acb),
|
||||
accb = std::move(accb)](const HttpResponsePtr &resp) {
|
||||
if (resp)
|
||||
{
|
||||
acb(resp);
|
||||
}
|
||||
else
|
||||
{
|
||||
accb();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void GlobalFilters::shutdown()
|
||||
{
|
||||
filters_.clear();
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
#include <drogon/utils/monitoring/Histogram.h>
|
||||
using namespace drogon;
|
||||
using namespace drogon::monitoring;
|
||||
|
||||
void Histogram::observe(double value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (maxAge_ > std::chrono::seconds(0) &&
|
||||
timerId_ == trantor::InvalidTimerId)
|
||||
{
|
||||
std::weak_ptr<Histogram> weakPtr =
|
||||
std::dynamic_pointer_cast<Histogram>(shared_from_this());
|
||||
timerId_ = loopPtr_->runEvery(maxAge_ / timeBucketCount_, [weakPtr]() {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
return;
|
||||
thisPtr->rotateTimeBuckets();
|
||||
});
|
||||
}
|
||||
auto ¤tBucket = timeBuckets_.back();
|
||||
currentBucket.sum += value;
|
||||
currentBucket.count += 1;
|
||||
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
|
||||
{
|
||||
if (value <= bucketBoundaries_[i])
|
||||
{
|
||||
currentBucket.buckets[i] += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (value > bucketBoundaries_.back())
|
||||
{
|
||||
currentBucket.buckets.back() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Sample> Histogram::collect() const
|
||||
{
|
||||
std::vector<Sample> samples;
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
size_t count{0};
|
||||
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
|
||||
{
|
||||
Sample sample;
|
||||
for (auto &bucket : timeBuckets_)
|
||||
{
|
||||
count += bucket.buckets[i];
|
||||
}
|
||||
sample.name = name_ + "_bucket";
|
||||
sample.exLabels.emplace_back("le",
|
||||
std::to_string(bucketBoundaries_[i]));
|
||||
sample.value = count;
|
||||
samples.emplace_back(std::move(sample));
|
||||
}
|
||||
Sample sample;
|
||||
for (auto &bucket : timeBuckets_)
|
||||
{
|
||||
count += bucket.buckets.back();
|
||||
}
|
||||
sample.name = name_ + "_bucket";
|
||||
sample.exLabels.emplace_back("le", "+Inf");
|
||||
sample.value = count;
|
||||
samples.emplace_back(std::move(sample));
|
||||
double sum{0};
|
||||
uint64_t totalCount{0};
|
||||
for (auto &bucket : timeBuckets_)
|
||||
{
|
||||
sum += bucket.sum;
|
||||
totalCount += bucket.count;
|
||||
}
|
||||
Sample sumSample;
|
||||
sumSample.name = name_ + "_sum";
|
||||
sumSample.value = sum;
|
||||
samples.emplace_back(std::move(sumSample));
|
||||
Sample countSample;
|
||||
countSample.name = name_ + "_count";
|
||||
countSample.value = totalCount;
|
||||
samples.emplace_back(std::move(countSample));
|
||||
return samples;
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
#include <drogon/plugins/Hodor.h>
|
||||
#include <drogon/plugins/RealIpResolver.h>
|
||||
|
||||
using namespace drogon::plugin;
|
||||
|
||||
Hodor::LimitStrategy Hodor::makeLimitStrategy(const Json::Value &config)
|
||||
{
|
||||
LimitStrategy strategy;
|
||||
strategy.capacity = config.get("capacity", 0).asUInt();
|
||||
if (config.isMember("urls") && config["urls"].isArray())
|
||||
{
|
||||
std::string regexString;
|
||||
for (auto &str : config["urls"])
|
||||
{
|
||||
assert(str.isString());
|
||||
regexString.append("(").append(str.asString()).append(")|");
|
||||
}
|
||||
if (!regexString.empty())
|
||||
{
|
||||
regexString.resize(regexString.length() - 1);
|
||||
strategy.urlsRegex = std::regex(regexString);
|
||||
strategy.regexFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (strategy.capacity > 0)
|
||||
{
|
||||
if (multiThreads_)
|
||||
{
|
||||
strategy.globalLimiterPtr = std::make_shared<SafeRateLimiter>(
|
||||
RateLimiter::newRateLimiter(algorithm_,
|
||||
strategy.capacity,
|
||||
timeUnit_));
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy.globalLimiterPtr =
|
||||
RateLimiter::newRateLimiter(algorithm_,
|
||||
strategy.capacity,
|
||||
timeUnit_);
|
||||
}
|
||||
}
|
||||
strategy.ipCapacity = config.get("ip_capacity", 0).asUInt();
|
||||
if (strategy.ipCapacity > 0)
|
||||
{
|
||||
strategy.ipLimiterMapPtr =
|
||||
std::make_unique<CacheMap<std::string, RateLimiterPtr>>(
|
||||
drogon::app().getLoop(),
|
||||
float(timeUnit_.count() / 60 < 1 ? 1 : timeUnit_.count() / 60),
|
||||
2,
|
||||
100);
|
||||
}
|
||||
|
||||
strategy.userCapacity = config.get("user_capacity", 0).asUInt();
|
||||
if (strategy.userCapacity > 0)
|
||||
{
|
||||
strategy.userLimiterMapPtr =
|
||||
std::make_unique<CacheMap<std::string, RateLimiterPtr>>(
|
||||
drogon::app().getLoop(),
|
||||
float(timeUnit_.count() / 60 < 1 ? 1 : timeUnit_.count() / 60),
|
||||
2,
|
||||
100);
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
void Hodor::initAndStart(const Json::Value &config)
|
||||
{
|
||||
algorithm_ = stringToRateLimiterType(
|
||||
config.get("algorithm", "token_bucket").asString());
|
||||
timeUnit_ = std::chrono::seconds(config.get("time_unit", 60).asUInt());
|
||||
|
||||
multiThreads_ = config.get("multi_threads", true).asBool();
|
||||
|
||||
useRealIpResolver_ = config.get("use_real_ip_resolver", false).asBool();
|
||||
rejectResponse_ = HttpResponse::newHttpResponse();
|
||||
rejectResponse_->setStatusCode(k429TooManyRequests);
|
||||
rejectResponse_->setBody(
|
||||
config.get("rejection_message", "Too many requests").asString());
|
||||
rejectResponse_->setCloseConnection(true);
|
||||
limiterExpireTime_ =
|
||||
(std::max)(static_cast<size_t>(
|
||||
config.get("limiter_expire_time", 600).asUInt()),
|
||||
static_cast<size_t>(timeUnit_.count() * 3));
|
||||
limitStrategies_.emplace_back(makeLimitStrategy(config));
|
||||
if (config.isMember("sub_limits") && config["sub_limits"].isArray())
|
||||
{
|
||||
for (auto &subLimit : config["sub_limits"])
|
||||
{
|
||||
assert(subLimit.isObject());
|
||||
if (!subLimit["urls"].isArray() || subLimit["urls"].size() == 0)
|
||||
{
|
||||
LOG_ERROR
|
||||
<< "The urls of sub_limits must be an array and not empty!";
|
||||
continue;
|
||||
}
|
||||
if (subLimit["capacity"].asUInt() == 0 &&
|
||||
subLimit["ip_capacity"].asUInt() == 0 &&
|
||||
subLimit["user_capacity"].asUInt() == 0)
|
||||
{
|
||||
LOG_ERROR << "At least one capacity of sub_limits must be "
|
||||
"greater than 0!";
|
||||
continue;
|
||||
}
|
||||
limitStrategies_.emplace_back(makeLimitStrategy(subLimit));
|
||||
}
|
||||
}
|
||||
|
||||
const Json::Value &trustIps = config["trust_ips"];
|
||||
if (!trustIps.isNull() && !trustIps.isArray())
|
||||
{
|
||||
throw std::runtime_error("Invalid trusted_ips. Should be array.");
|
||||
}
|
||||
for (const auto &ipOrCidr : trustIps)
|
||||
{
|
||||
trustCIDRs_.emplace_back(ipOrCidr.asString());
|
||||
}
|
||||
|
||||
app().registerPreHandlingAdvice([this](const drogon::HttpRequestPtr &req,
|
||||
AdviceCallback &&acb,
|
||||
AdviceChainCallback &&accb) {
|
||||
onHttpRequest(req, std::move(acb), std::move(accb));
|
||||
});
|
||||
}
|
||||
|
||||
void Hodor::shutdown()
|
||||
{
|
||||
LOG_TRACE << "Hodor plugin is shutdown!";
|
||||
}
|
||||
|
||||
bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
|
||||
const LimitStrategy &strategy,
|
||||
const trantor::InetAddress &ip,
|
||||
const std::optional<std::string> &userId)
|
||||
{
|
||||
if (RealIpResolver::matchCidr(ip, trustCIDRs_))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (strategy.regexFlag)
|
||||
{
|
||||
if (!std::regex_match(req->path(), strategy.urlsRegex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (strategy.globalLimiterPtr)
|
||||
{
|
||||
if (!strategy.globalLimiterPtr->isAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (strategy.ipCapacity > 0)
|
||||
{
|
||||
RateLimiterPtr limiterPtr;
|
||||
strategy.ipLimiterMapPtr->modify(
|
||||
ip.toIpNetEndian(),
|
||||
[this, &limiterPtr, &strategy](RateLimiterPtr &ptr) {
|
||||
if (!ptr)
|
||||
{
|
||||
if (multiThreads_)
|
||||
{
|
||||
ptr = std::make_shared<SafeRateLimiter>(
|
||||
RateLimiter::newRateLimiter(algorithm_,
|
||||
strategy.ipCapacity,
|
||||
timeUnit_));
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = RateLimiter::newRateLimiter(algorithm_,
|
||||
strategy.ipCapacity,
|
||||
timeUnit_);
|
||||
}
|
||||
}
|
||||
limiterPtr = ptr;
|
||||
},
|
||||
limiterExpireTime_);
|
||||
if (!limiterPtr->isAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (strategy.userCapacity > 0)
|
||||
{
|
||||
if (!userId.has_value())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
RateLimiterPtr limiterPtr;
|
||||
strategy.userLimiterMapPtr->modify(
|
||||
*userId,
|
||||
[this, &strategy, &limiterPtr](RateLimiterPtr &ptr) {
|
||||
if (!ptr)
|
||||
{
|
||||
if (multiThreads_)
|
||||
{
|
||||
ptr = std::make_shared<SafeRateLimiter>(
|
||||
RateLimiter::newRateLimiter(algorithm_,
|
||||
strategy.userCapacity,
|
||||
timeUnit_));
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = RateLimiter::newRateLimiter(algorithm_,
|
||||
strategy.userCapacity,
|
||||
timeUnit_);
|
||||
}
|
||||
}
|
||||
limiterPtr = ptr;
|
||||
},
|
||||
limiterExpireTime_);
|
||||
if (!limiterPtr->isAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Hodor::onHttpRequest(const drogon::HttpRequestPtr &req,
|
||||
drogon::AdviceCallback &&adviceCallback,
|
||||
drogon::AdviceChainCallback &&chainCallback)
|
||||
{
|
||||
const trantor::InetAddress &ip =
|
||||
useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
|
||||
: req->peerAddr();
|
||||
std::optional<std::string> userId;
|
||||
if (userIdGetter_)
|
||||
{
|
||||
userId = userIdGetter_(req);
|
||||
}
|
||||
for (auto &strategy : limitStrategies_)
|
||||
{
|
||||
if (!checkLimit(req, strategy, ip, userId))
|
||||
{
|
||||
if (rejectResponseFactory_)
|
||||
{
|
||||
adviceCallback(rejectResponseFactory_(req));
|
||||
}
|
||||
else
|
||||
{
|
||||
adviceCallback(rejectResponse_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
chainCallback();
|
||||
}
|
||||
+1375
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,768 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpAppFrameworkImpl.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/config.h>
|
||||
#include <json/json.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "SessionManager.h"
|
||||
#include "drogon/utils/Utilities.h"
|
||||
#include "impl_forwards.h"
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class EventLoopThreadPool;
|
||||
}
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
HttpResponsePtr defaultErrorHandler(HttpStatusCode code,
|
||||
const HttpRequestPtr &req);
|
||||
void defaultExceptionHandler(const std::exception &,
|
||||
const HttpRequestPtr &,
|
||||
std::function<void(const HttpResponsePtr &)> &&);
|
||||
|
||||
struct InitBeforeMainFunction
|
||||
{
|
||||
explicit InitBeforeMainFunction(const std::function<void()> &func)
|
||||
{
|
||||
func();
|
||||
}
|
||||
};
|
||||
|
||||
class HttpAppFrameworkImpl final : public HttpAppFramework
|
||||
{
|
||||
public:
|
||||
HttpAppFrameworkImpl();
|
||||
|
||||
const Json::Value &getCustomConfig() const override
|
||||
{
|
||||
return jsonConfig_["custom_config"];
|
||||
}
|
||||
|
||||
PluginBase *getPlugin(const std::string &name) override;
|
||||
std::shared_ptr<PluginBase> getSharedPlugin(
|
||||
const std::string &name) override;
|
||||
void addPlugins(const Json::Value &configs) override;
|
||||
void addPlugin(const std::string &name,
|
||||
const std::vector<std::string> &dependencies,
|
||||
const Json::Value &config) override;
|
||||
HttpAppFramework &addListener(
|
||||
const std::string &ip,
|
||||
uint16_t port,
|
||||
bool useSSL,
|
||||
const std::string &certFile,
|
||||
const std::string &keyFile,
|
||||
bool useOldTLS,
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
|
||||
override;
|
||||
HttpAppFramework &setThreadNum(size_t threadNum) override;
|
||||
|
||||
size_t getThreadNum() const override
|
||||
{
|
||||
return threadNum_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setSSLConfigCommands(
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
|
||||
override;
|
||||
HttpAppFramework &setSSLFiles(const std::string &certPath,
|
||||
const std::string &keyPath) override;
|
||||
|
||||
HttpAppFramework &reloadSSLFiles() override;
|
||||
|
||||
void run() override;
|
||||
HttpAppFramework ®isterWebSocketController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints) override;
|
||||
HttpAppFramework ®isterWebSocketControllerRegex(
|
||||
const std::string ®Exp,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints) override;
|
||||
HttpAppFramework ®isterHttpSimpleController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints) override;
|
||||
|
||||
HttpAppFramework &setCustom404Page(const HttpResponsePtr &resp,
|
||||
bool set404) override
|
||||
{
|
||||
if (set404)
|
||||
{
|
||||
resp->setStatusCode(k404NotFound);
|
||||
}
|
||||
custom404_ = resp;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setCustomErrorHandler(
|
||||
std::function<HttpResponsePtr(HttpStatusCode,
|
||||
const HttpRequestPtr &req)>
|
||||
&&resp_generator) override;
|
||||
|
||||
const HttpResponsePtr &getCustom404Page();
|
||||
|
||||
void forward(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const std::string &hostString,
|
||||
double timeout) override;
|
||||
|
||||
void forward(const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const std::string &hostString,
|
||||
double timeout = 0);
|
||||
|
||||
HttpAppFramework ®isterBeginningAdvice(
|
||||
const std::function<void()> &advice) override
|
||||
{
|
||||
beginningAdvices_.emplace_back(advice);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework ®isterNewConnectionAdvice(
|
||||
const std::function<bool(const trantor::InetAddress &,
|
||||
const trantor::InetAddress &)> &advice)
|
||||
override;
|
||||
|
||||
HttpAppFramework ®isterHttpResponseCreationAdvice(
|
||||
const std::function<void(const HttpResponsePtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterSyncAdvice(
|
||||
const std::function<HttpResponsePtr(const HttpRequestPtr &)> &advice)
|
||||
override;
|
||||
|
||||
HttpAppFramework ®isterPreRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPostRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreHandlingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPostRoutingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreHandlingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPostHandlingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
const HttpResponsePtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework ®isterPreSendingAdvice(
|
||||
const std::function<void(const HttpRequestPtr &,
|
||||
const HttpResponsePtr &)> &advice) override;
|
||||
|
||||
HttpAppFramework &setDefaultHandler(DefaultHandler handler) override;
|
||||
|
||||
HttpAppFramework &setupFileLogger() override;
|
||||
|
||||
HttpAppFramework &enableSession(
|
||||
const size_t timeout,
|
||||
Cookie::SameSite sameSite = Cookie::SameSite::kNull,
|
||||
const std::string &cookieKey = "JSESSIONID",
|
||||
int maxAge = -1,
|
||||
SessionManager::IdGeneratorCallback idGeneratorCallback =
|
||||
nullptr) override
|
||||
{
|
||||
useSession_ = true;
|
||||
sessionTimeout_ = timeout;
|
||||
sessionSameSite_ = sameSite;
|
||||
sessionCookieKey_ = cookieKey;
|
||||
sessionMaxAge_ = maxAge;
|
||||
return setSessionIdGenerator(idGeneratorCallback);
|
||||
}
|
||||
|
||||
HttpAppFramework &setSessionIdGenerator(
|
||||
SessionManager::IdGeneratorCallback idGeneratorCallback = nullptr)
|
||||
{
|
||||
sessionIdGeneratorCallback_ =
|
||||
idGeneratorCallback ? idGeneratorCallback
|
||||
: []() { return utils::getUuid(true); };
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &disableSession() override
|
||||
{
|
||||
useSession_ = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework ®isterSessionStartAdvice(
|
||||
const AdviceStartSessionCallback &advice) override
|
||||
{
|
||||
sessionStartAdvices_.emplace_back(advice);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework ®isterSessionDestroyAdvice(
|
||||
const AdviceDestroySessionCallback &advice) override
|
||||
{
|
||||
sessionDestroyAdvices_.emplace_back(advice);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string &getDocumentRoot() const override
|
||||
{
|
||||
return rootPath_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setDocumentRoot(const std::string &rootPath) override
|
||||
{
|
||||
rootPath_ = rootPath;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setStaticFileHeaders(
|
||||
const std::vector<std::pair<std::string, std::string>> &headers)
|
||||
override;
|
||||
|
||||
HttpAppFramework &addALocation(
|
||||
const std::string &uriPrefix,
|
||||
const std::string &defaultContentType,
|
||||
const std::string &alias,
|
||||
bool isCaseSensitive,
|
||||
bool allowAll,
|
||||
bool isRecursive,
|
||||
const std::vector<std::string> &middlewareNames) override;
|
||||
|
||||
const std::string &getUploadPath() const override
|
||||
{
|
||||
return uploadPath_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<trantor::Resolver> &getResolver() const override
|
||||
{
|
||||
static auto resolver = trantor::Resolver::newResolver(getLoop());
|
||||
return resolver;
|
||||
}
|
||||
|
||||
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
|
||||
HttpAppFramework &setFileTypes(
|
||||
const std::vector<std::string> &types) override;
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
HttpAppFramework &enableDynamicViewsLoading(
|
||||
const std::vector<std::string> &libPaths,
|
||||
const std::string &outputPath) override;
|
||||
#endif
|
||||
HttpAppFramework &setMaxConnectionNum(size_t maxConnections) override;
|
||||
HttpAppFramework &setMaxConnectionNumPerIP(
|
||||
size_t maxConnectionsPerIP) override;
|
||||
HttpAppFramework &loadConfigFile(const std::string &fileName) noexcept(
|
||||
false) override;
|
||||
HttpAppFramework &loadConfigJson(const Json::Value &data) noexcept(
|
||||
false) override;
|
||||
HttpAppFramework &loadConfigJson(Json::Value &&data) noexcept(
|
||||
false) override;
|
||||
|
||||
HttpAppFramework &enableRunAsDaemon() override
|
||||
{
|
||||
runAsDaemon_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &disableSigtermHandling() override
|
||||
{
|
||||
handleSigterm_ = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &enableRelaunchOnError() override
|
||||
{
|
||||
relaunchOnError_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setLogPath(const std::string &logPath,
|
||||
const std::string &logfileBaseName,
|
||||
size_t logfileSize,
|
||||
size_t maxFiles,
|
||||
bool useSpdlog) override;
|
||||
HttpAppFramework &setLogLevel(trantor::Logger::LogLevel level) override;
|
||||
HttpAppFramework &setLogLocalTime(bool on) override;
|
||||
|
||||
HttpAppFramework &enableSendfile(bool sendFile) override
|
||||
{
|
||||
useSendfile_ = sendFile;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &enableGzip(bool useGzip) override
|
||||
{
|
||||
useGzip_ = useGzip;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isGzipEnabled() const override
|
||||
{
|
||||
return useGzip_;
|
||||
}
|
||||
|
||||
HttpAppFramework &enableBrotli(bool useBrotli) override
|
||||
{
|
||||
useBrotli_ = useBrotli;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isBrotliEnabled() const override
|
||||
{
|
||||
return useBrotli_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setStaticFilesCacheTime(int cacheTime) override;
|
||||
int staticFilesCacheTime() const override;
|
||||
|
||||
HttpAppFramework &setIdleConnectionTimeout(size_t timeout) override
|
||||
{
|
||||
idleConnectionTimeout_ = timeout;
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t getIdleConnectionTimeout() const // could expose in base class
|
||||
{
|
||||
return idleConnectionTimeout_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setKeepaliveRequestsNumber(const size_t number) override
|
||||
{
|
||||
keepaliveRequestsNumber_ = number;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setPipeliningRequestsNumber(const size_t number) override
|
||||
{
|
||||
pipeliningRequestsNumber_ = number;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setGzipStatic(bool useGzipStatic) override;
|
||||
HttpAppFramework &setBrStatic(bool useGzipStatic) override;
|
||||
|
||||
HttpAppFramework &setClientMaxBodySize(size_t maxSize) override
|
||||
{
|
||||
clientMaxBodySize_ = maxSize;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setClientMaxMemoryBodySize(size_t maxSize) override
|
||||
{
|
||||
clientMaxMemoryBodySize_ = maxSize;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setClientMaxWebSocketMessageSize(size_t maxSize) override
|
||||
{
|
||||
clientMaxWebSocketMessageSize_ = maxSize;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &setHomePage(const std::string &homePageFile) override
|
||||
{
|
||||
homePageFile_ = homePageFile;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string &getHomePage() const override
|
||||
{
|
||||
return homePageFile_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setTermSignalHandler(
|
||||
const std::function<void()> &handler) override
|
||||
{
|
||||
termSignalHandler_ = handler;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::function<void()> &getTermSignalHandler() const
|
||||
{
|
||||
return termSignalHandler_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setIntSignalHandler(
|
||||
const std::function<void()> &handler) override
|
||||
{
|
||||
intSignalHandler_ = handler;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::function<void()> &getIntSignalHandler() const
|
||||
{
|
||||
return intSignalHandler_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setImplicitPageEnable(bool useImplicitPage) override;
|
||||
bool isImplicitPageEnabled() const override;
|
||||
HttpAppFramework &setImplicitPage(
|
||||
const std::string &implicitPageFile) override;
|
||||
const std::string &getImplicitPage() const override;
|
||||
|
||||
size_t getClientMaxBodySize() const
|
||||
{
|
||||
return clientMaxBodySize_;
|
||||
}
|
||||
|
||||
size_t getClientMaxMemoryBodySize() const
|
||||
{
|
||||
return clientMaxMemoryBodySize_;
|
||||
}
|
||||
|
||||
size_t getClientMaxWebSocketMessageSize() const
|
||||
{
|
||||
return clientMaxWebSocketMessageSize_;
|
||||
}
|
||||
|
||||
std::vector<HttpHandlerInfo> getHandlersInfo() const override;
|
||||
|
||||
size_t keepaliveRequestsNumber() const
|
||||
{
|
||||
return keepaliveRequestsNumber_;
|
||||
}
|
||||
|
||||
size_t pipeliningRequestsNumber() const
|
||||
{
|
||||
return pipeliningRequestsNumber_;
|
||||
}
|
||||
|
||||
~HttpAppFrameworkImpl() noexcept override;
|
||||
|
||||
bool isRunning() override
|
||||
{
|
||||
return running_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setJsonParserStackLimit(size_t limit) noexcept override
|
||||
{
|
||||
jsonStackLimit_ = limit;
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t getJsonParserStackLimit() const noexcept override
|
||||
{
|
||||
return jsonStackLimit_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setUnicodeEscapingInJson(bool enable) noexcept override
|
||||
{
|
||||
usingUnicodeEscaping_ = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isUnicodeEscapingUsedInJson() const noexcept override
|
||||
{
|
||||
return usingUnicodeEscaping_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setFloatPrecisionInJson(
|
||||
unsigned int precision,
|
||||
const std::string &precisionType) noexcept override
|
||||
{
|
||||
floatPrecisionInJson_ = std::make_pair(precision, precisionType);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::pair<unsigned int, std::string> &getFloatPrecisionInJson()
|
||||
const noexcept override
|
||||
{
|
||||
return floatPrecisionInJson_;
|
||||
}
|
||||
|
||||
trantor::EventLoop *getLoop() const override;
|
||||
|
||||
trantor::EventLoop *getIOLoop(size_t id) const override;
|
||||
|
||||
void quit() override;
|
||||
|
||||
HttpAppFramework &setServerHeaderField(const std::string &server) override
|
||||
{
|
||||
assert(!running_);
|
||||
assert(server.find("\r\n") == std::string::npos);
|
||||
serverHeader_ = "server: " + server + "\r\n";
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &enableServerHeader(bool flag) override
|
||||
{
|
||||
enableServerHeader_ = flag;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &enableDateHeader(bool flag) override
|
||||
{
|
||||
enableDateHeader_ = flag;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool sendServerHeader() const
|
||||
{
|
||||
return enableServerHeader_;
|
||||
}
|
||||
|
||||
bool sendDateHeader() const
|
||||
{
|
||||
return enableDateHeader_;
|
||||
}
|
||||
|
||||
const std::string &getServerHeaderString() const
|
||||
{
|
||||
return serverHeader_;
|
||||
}
|
||||
|
||||
orm::DbClientPtr getDbClient(const std::string &name) override;
|
||||
orm::DbClientPtr getFastDbClient(const std::string &name) override;
|
||||
|
||||
HttpAppFramework &createDbClient(const std::string &dbType,
|
||||
const std::string &host,
|
||||
unsigned short port,
|
||||
const std::string &databaseName,
|
||||
const std::string &userName,
|
||||
const std::string &password,
|
||||
size_t connectionNum,
|
||||
const std::string &filename,
|
||||
const std::string &name,
|
||||
bool isFast,
|
||||
const std::string &characterSet,
|
||||
double timeout,
|
||||
bool autoBatch) override;
|
||||
// a helper method
|
||||
void addDbClient(const std::string &dbType,
|
||||
const std::string &host,
|
||||
unsigned short port,
|
||||
const std::string &databaseName,
|
||||
const std::string &userName,
|
||||
const std::string &password,
|
||||
size_t connectionNum,
|
||||
const std::string &filename,
|
||||
const std::string &name,
|
||||
bool isFast,
|
||||
const std::string &characterSet,
|
||||
double timeout,
|
||||
bool autoBatch,
|
||||
std::unordered_map<std::string, std::string> options);
|
||||
HttpAppFramework &addDbClient(const orm::DbConfig &config) override;
|
||||
|
||||
HttpAppFramework &createRedisClient(const std::string &ip,
|
||||
unsigned short port,
|
||||
const std::string &name,
|
||||
const std::string &password,
|
||||
size_t connectionNum,
|
||||
bool isFast,
|
||||
double timeout,
|
||||
unsigned int db,
|
||||
const std::string &username) override;
|
||||
nosql::RedisClientPtr getRedisClient(const std::string &name) override;
|
||||
nosql::RedisClientPtr getFastRedisClient(const std::string &name) override;
|
||||
std::vector<trantor::InetAddress> getListeners() const override;
|
||||
|
||||
inline static HttpAppFrameworkImpl &instance()
|
||||
{
|
||||
static HttpAppFrameworkImpl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool useSendfile() const
|
||||
{
|
||||
return useSendfile_;
|
||||
}
|
||||
|
||||
bool supportSSL() const override
|
||||
{
|
||||
return trantor::utils::tlsBackend() != "None";
|
||||
}
|
||||
|
||||
size_t getCurrentThreadIndex() const override
|
||||
{
|
||||
auto *loop = trantor::EventLoop::getEventLoopOfCurrentThread();
|
||||
if (loop)
|
||||
{
|
||||
return loop->index();
|
||||
}
|
||||
return (std::numeric_limits<size_t>::max)();
|
||||
}
|
||||
|
||||
bool areAllDbClientsAvailable() const noexcept override;
|
||||
const std::function<HttpResponsePtr(HttpStatusCode,
|
||||
const HttpRequestPtr &req)> &
|
||||
getCustomErrorHandler() const override;
|
||||
|
||||
bool isUsingCustomErrorHandler() const
|
||||
{
|
||||
return usingCustomErrorHandler_;
|
||||
}
|
||||
|
||||
void enableReusePort(bool enable) override
|
||||
{
|
||||
reusePort_ = enable;
|
||||
}
|
||||
|
||||
bool reusePort() const override
|
||||
{
|
||||
return reusePort_;
|
||||
}
|
||||
|
||||
HttpAppFramework &setExceptionHandler(ExceptionHandler handler) override
|
||||
{
|
||||
exceptionHandler_ = std::move(handler);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const ExceptionHandler &getExceptionHandler() const override
|
||||
{
|
||||
return exceptionHandler_;
|
||||
}
|
||||
|
||||
HttpAppFramework &enableCompressedRequest(bool enable) override
|
||||
{
|
||||
enableCompressedRequest_ = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isCompressedRequestEnabled() const override
|
||||
{
|
||||
return enableCompressedRequest_;
|
||||
}
|
||||
|
||||
HttpAppFramework ®isterCustomExtensionMime(
|
||||
const std::string &ext,
|
||||
const std::string &mime) override;
|
||||
|
||||
// should return unsigned type!
|
||||
int64_t getConnectionCount() const override;
|
||||
|
||||
// TODO: move session related codes to its own singleton class
|
||||
void findSessionForRequest(const HttpRequestImplPtr &req);
|
||||
HttpResponsePtr handleSessionForResponse(const HttpRequestImplPtr &req,
|
||||
const HttpResponsePtr &resp);
|
||||
|
||||
HttpAppFramework &setBeforeListenSockOptCallback(
|
||||
std::function<void(int)> cb) override;
|
||||
HttpAppFramework &setAfterAcceptSockOptCallback(
|
||||
std::function<void(int)> cb) override;
|
||||
HttpAppFramework &setConnectionCallback(
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> cb) override;
|
||||
|
||||
HttpAppFramework &enableRequestStream(bool enable) override;
|
||||
bool isRequestStreamEnabled() const override;
|
||||
|
||||
private:
|
||||
void registerHttpController(const std::string &pathPattern,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &middlewareNames,
|
||||
const std::string &handlerName) override;
|
||||
void registerHttpControllerViaRegex(
|
||||
const std::string ®Exp,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &middlewareNames,
|
||||
const std::string &handlerName) override;
|
||||
|
||||
// We use an uuid string as session id;
|
||||
// set sessionTimeout_=0 to make location session valid forever based on
|
||||
// cookies;
|
||||
size_t sessionTimeout_{0};
|
||||
Cookie::SameSite sessionSameSite_{Cookie::SameSite::kNull};
|
||||
std::string sessionCookieKey_{"JSESSIONID"};
|
||||
int sessionMaxAge_{-1};
|
||||
size_t idleConnectionTimeout_{60};
|
||||
bool useSession_{false};
|
||||
std::string serverHeader_{"server: drogon/" + drogon::getVersion() +
|
||||
"\r\n"};
|
||||
|
||||
std::unique_ptr<ListenerManager> listenerManagerPtr_;
|
||||
std::unique_ptr<PluginsManager> pluginsManagerPtr_;
|
||||
std::unique_ptr<orm::DbClientManager> dbClientManagerPtr_;
|
||||
std::unique_ptr<nosql::RedisClientManager> redisClientManagerPtr_;
|
||||
|
||||
std::string rootPath_{"./"};
|
||||
std::string uploadPath_;
|
||||
std::atomic_bool running_{false};
|
||||
std::atomic_bool routersInit_{false};
|
||||
|
||||
size_t threadNum_{1};
|
||||
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
|
||||
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
std::vector<std::string> libFilePaths_;
|
||||
std::string libFileOutputPath_;
|
||||
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;
|
||||
#endif
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
|
||||
std::string sslCertPath_;
|
||||
std::string sslKeyPath_;
|
||||
|
||||
bool runAsDaemon_{false};
|
||||
bool handleSigterm_{true};
|
||||
bool relaunchOnError_{false};
|
||||
bool logWithSpdlog_{false};
|
||||
std::string logPath_;
|
||||
std::string logfileBaseName_;
|
||||
size_t logfileSize_{100000000};
|
||||
size_t logfileMaxNum_{0};
|
||||
size_t keepaliveRequestsNumber_{0};
|
||||
size_t pipeliningRequestsNumber_{0};
|
||||
size_t jsonStackLimit_{1000};
|
||||
bool useSendfile_{true};
|
||||
bool useGzip_{true};
|
||||
bool useBrotli_{false};
|
||||
bool usingUnicodeEscaping_{true};
|
||||
std::pair<unsigned int, std::string> floatPrecisionInJson_{0,
|
||||
"significant"};
|
||||
bool usingCustomErrorHandler_{false};
|
||||
size_t clientMaxBodySize_{1024 * 1024};
|
||||
size_t clientMaxMemoryBodySize_{64 * 1024};
|
||||
size_t clientMaxWebSocketMessageSize_{128 * 1024};
|
||||
std::string homePageFile_{"index.html"};
|
||||
std::function<void()> termSignalHandler_{[]() { app().quit(); }};
|
||||
std::function<void()> intSignalHandler_{[]() { app().quit(); }};
|
||||
std::unique_ptr<SessionManager> sessionManagerPtr_;
|
||||
std::vector<AdviceStartSessionCallback> sessionStartAdvices_;
|
||||
std::vector<AdviceDestroySessionCallback> sessionDestroyAdvices_;
|
||||
SessionManager::IdGeneratorCallback sessionIdGeneratorCallback_;
|
||||
std::shared_ptr<trantor::AsyncFileLogger> asyncFileLoggerPtr_;
|
||||
Json::Value jsonConfig_;
|
||||
Json::Value jsonRuntimeConfig_;
|
||||
HttpResponsePtr custom404_;
|
||||
std::function<HttpResponsePtr(HttpStatusCode, const HttpRequestPtr &req)>
|
||||
customErrorHandler_ = &defaultErrorHandler;
|
||||
static InitBeforeMainFunction initFirst_;
|
||||
bool enableServerHeader_{true};
|
||||
bool enableDateHeader_{true};
|
||||
bool reusePort_{false};
|
||||
std::vector<std::function<void()>> beginningAdvices_;
|
||||
|
||||
ExceptionHandler exceptionHandler_{defaultExceptionHandler};
|
||||
bool enableCompressedRequest_{false};
|
||||
|
||||
bool enableRequestStream_{false};
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
*
|
||||
* HttpBinder.h
|
||||
* Martin Chang
|
||||
*
|
||||
* Copyright 2021, Martin Chang. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include <drogon/HttpBinder.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
void handleException(const std::exception &e,
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
app().getExceptionHandler()(e, req, std::move(callback));
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace drogon
|
||||
+728
@@ -0,0 +1,728 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpClientImpl.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpClientImpl.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include "HttpResponseParser.h"
|
||||
|
||||
#include <drogon/config.h>
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace trantor;
|
||||
using namespace drogon;
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
static const size_t kDefaultDNSTimeout{600};
|
||||
}
|
||||
|
||||
void HttpClientImpl::createTcpClient()
|
||||
{
|
||||
LOG_TRACE << "New TcpClient," << serverAddr_.toIpPort();
|
||||
tcpClientPtr_ =
|
||||
std::make_shared<trantor::TcpClient>(loop_, serverAddr_, "httpClient");
|
||||
|
||||
if (useSSL_ && utils::supportsTls())
|
||||
{
|
||||
LOG_TRACE << "useOldTLS=" << useOldTLS_;
|
||||
LOG_TRACE << "domain=" << domain_;
|
||||
auto policy = trantor::TLSPolicy::defaultClientPolicy();
|
||||
policy->setUseOldTLS(useOldTLS_)
|
||||
.setValidate(validateCert_)
|
||||
.setHostname(domain_)
|
||||
.setConfCmds(sslConfCmds_)
|
||||
.setCertPath(clientCertPath_)
|
||||
.setKeyPath(clientKeyPath_);
|
||||
tcpClientPtr_->enableSSL(std::move(policy));
|
||||
}
|
||||
|
||||
auto thisPtr = shared_from_this();
|
||||
std::weak_ptr<HttpClientImpl> weakPtr = thisPtr;
|
||||
tcpClientPtr_->setSockOptCallback([weakPtr](int fd) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
return;
|
||||
if (thisPtr->sockOptCallback_)
|
||||
thisPtr->sockOptCallback_(fd);
|
||||
});
|
||||
tcpClientPtr_->setConnectionCallback(
|
||||
[weakPtr](const trantor::TcpConnectionPtr &connPtr) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
return;
|
||||
if (connPtr->connected())
|
||||
{
|
||||
connPtr->setContext(
|
||||
std::make_shared<HttpResponseParser>(connPtr));
|
||||
// send request;
|
||||
LOG_TRACE << "Connection established!";
|
||||
while (thisPtr->pipeliningCallbacks_.size() <=
|
||||
thisPtr->pipeliningDepth_ &&
|
||||
!thisPtr->requestsBuffer_.empty())
|
||||
{
|
||||
thisPtr->sendReq(connPtr,
|
||||
thisPtr->requestsBuffer_.front().first);
|
||||
thisPtr->pipeliningCallbacks_.push(
|
||||
std::move(thisPtr->requestsBuffer_.front()));
|
||||
thisPtr->requestsBuffer_.pop_front();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_TRACE << "connection disconnect";
|
||||
auto responseParser = connPtr->getContext<HttpResponseParser>();
|
||||
if (responseParser && responseParser->parseResponseOnClose() &&
|
||||
responseParser->gotAll())
|
||||
{
|
||||
auto &firstReq = thisPtr->pipeliningCallbacks_.front();
|
||||
if (firstReq.first->method() == Head)
|
||||
{
|
||||
responseParser->setForHeadMethod();
|
||||
}
|
||||
auto resp = responseParser->responseImpl();
|
||||
responseParser->reset();
|
||||
// temporary fix of dead tcpClientPtr_
|
||||
// TODO: fix HttpResponseParser when content-length absence
|
||||
thisPtr->tcpClientPtr_.reset();
|
||||
thisPtr->handleResponse(resp, std::move(firstReq), connPtr);
|
||||
if (!thisPtr->requestsBuffer_.empty())
|
||||
{
|
||||
thisPtr->createTcpClient();
|
||||
}
|
||||
return;
|
||||
}
|
||||
thisPtr->onError(ReqResult::NetworkFailure);
|
||||
}
|
||||
});
|
||||
tcpClientPtr_->setConnectionErrorCallback([weakPtr]() {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
return;
|
||||
// can't connect to server
|
||||
thisPtr->onError(ReqResult::BadServerAddress);
|
||||
});
|
||||
tcpClientPtr_->setMessageCallback(
|
||||
[weakPtr](const trantor::TcpConnectionPtr &connPtr,
|
||||
trantor::MsgBuffer *msg) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (thisPtr)
|
||||
{
|
||||
thisPtr->onRecvMessage(connPtr, msg);
|
||||
}
|
||||
});
|
||||
tcpClientPtr_->setSSLErrorCallback([weakPtr](SSLError err) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
return;
|
||||
if (err == trantor::SSLError::kSSLHandshakeError)
|
||||
thisPtr->onError(ReqResult::HandshakeError);
|
||||
else if (err == trantor::SSLError::kSSLInvalidCertificate)
|
||||
thisPtr->onError(ReqResult::InvalidCertificate);
|
||||
else if (err == trantor::SSLError::kSSLProtocolError)
|
||||
thisPtr->onError(ReqResult::EncryptionFailure);
|
||||
else
|
||||
{
|
||||
LOG_FATAL << "Invalid value for SSLError";
|
||||
abort();
|
||||
}
|
||||
});
|
||||
tcpClientPtr_->connect();
|
||||
}
|
||||
|
||||
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
|
||||
const trantor::InetAddress &addr,
|
||||
bool useSSL,
|
||||
bool useOldTLS,
|
||||
bool validateCert)
|
||||
: loop_(loop),
|
||||
serverAddr_(addr),
|
||||
useSSL_(useSSL),
|
||||
validateCert_(validateCert),
|
||||
useOldTLS_(useOldTLS)
|
||||
{
|
||||
}
|
||||
|
||||
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
|
||||
const std::string &hostString,
|
||||
bool useOldTLS,
|
||||
bool validateCert)
|
||||
: loop_(loop), validateCert_(validateCert), useOldTLS_(useOldTLS)
|
||||
{
|
||||
auto lowerHost = hostString;
|
||||
std::transform(lowerHost.begin(),
|
||||
lowerHost.end(),
|
||||
lowerHost.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
if (lowerHost.find("https://") == 0)
|
||||
{
|
||||
useSSL_ = true;
|
||||
lowerHost = lowerHost.substr(8);
|
||||
}
|
||||
else if (lowerHost.find("http://") == 0)
|
||||
{
|
||||
useSSL_ = false;
|
||||
lowerHost = lowerHost.substr(7);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto pos = lowerHost.find(']');
|
||||
if (lowerHost[0] == '[' && pos != std::string::npos)
|
||||
{
|
||||
// ipv6
|
||||
domain_ = lowerHost.substr(1, pos - 1);
|
||||
if (lowerHost[pos + 1] == ':')
|
||||
{
|
||||
auto portStr = lowerHost.substr(pos + 2);
|
||||
pos = portStr.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
portStr = portStr.substr(0, pos);
|
||||
}
|
||||
auto port = atoi(portStr.c_str());
|
||||
if (port > 0 && port < 65536)
|
||||
{
|
||||
serverAddr_ = InetAddress(domain_, port, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useSSL_)
|
||||
{
|
||||
serverAddr_ = InetAddress(domain_, 443, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
serverAddr_ = InetAddress(domain_, 80, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos = lowerHost.find(':');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
domain_ = lowerHost.substr(0, pos);
|
||||
auto portStr = lowerHost.substr(pos + 1);
|
||||
pos = portStr.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
portStr = portStr.substr(0, pos);
|
||||
}
|
||||
auto port = atoi(portStr.c_str());
|
||||
if (port > 0 && port < 65536)
|
||||
{
|
||||
serverAddr_ = InetAddress(domain_, port);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
domain_ = lowerHost;
|
||||
pos = domain_.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
domain_ = domain_.substr(0, pos);
|
||||
}
|
||||
if (useSSL_)
|
||||
{
|
||||
serverAddr_ = InetAddress(domain_, 443);
|
||||
}
|
||||
else
|
||||
{
|
||||
serverAddr_ = InetAddress(domain_, 80);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (serverAddr_.isUnspecified())
|
||||
{
|
||||
isDomainName_ = true;
|
||||
}
|
||||
LOG_TRACE << "userSSL=" << useSSL_ << " domain=" << domain_;
|
||||
}
|
||||
|
||||
HttpClientImpl::~HttpClientImpl()
|
||||
{
|
||||
LOG_TRACE << "Deconstruction HttpClient";
|
||||
if (resolverPtr_ && !(loop_->isInLoopThread()))
|
||||
{
|
||||
// Make sure the resolverPtr_ is destroyed in the correct thread.
|
||||
loop_->queueInLoop([resolverPtr = std::move(resolverPtr_)]() {});
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req,
|
||||
const drogon::HttpReqCallback &callback,
|
||||
double timeout)
|
||||
{
|
||||
auto thisPtr = shared_from_this();
|
||||
loop_->runInLoop([thisPtr, req, callback = callback, timeout]() mutable {
|
||||
thisPtr->sendRequestInLoop(req, std::move(callback), timeout);
|
||||
});
|
||||
}
|
||||
|
||||
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req,
|
||||
drogon::HttpReqCallback &&callback,
|
||||
double timeout)
|
||||
{
|
||||
auto thisPtr = shared_from_this();
|
||||
loop_->runInLoop(
|
||||
[thisPtr, req, callback = std::move(callback), timeout]() mutable {
|
||||
thisPtr->sendRequestInLoop(req, std::move(callback), timeout);
|
||||
});
|
||||
}
|
||||
|
||||
struct RequestCallbackParams
|
||||
{
|
||||
RequestCallbackParams(HttpReqCallback &&cb,
|
||||
HttpClientImplPtr client,
|
||||
HttpRequestPtr req)
|
||||
: callback(std::move(cb)),
|
||||
clientPtr(std::move(client)),
|
||||
requestPtr(std::move(req))
|
||||
{
|
||||
}
|
||||
|
||||
const drogon::HttpReqCallback callback;
|
||||
const HttpClientImplPtr clientPtr;
|
||||
const HttpRequestPtr requestPtr;
|
||||
bool timeoutFlag{false};
|
||||
};
|
||||
|
||||
void HttpClientImpl::sendRequestInLoop(const HttpRequestPtr &req,
|
||||
HttpReqCallback &&callback,
|
||||
double timeout)
|
||||
{
|
||||
if (timeout <= 0)
|
||||
{
|
||||
sendRequestInLoop(req, std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
auto callbackParamsPtr =
|
||||
std::make_shared<RequestCallbackParams>(std::move(callback),
|
||||
shared_from_this(),
|
||||
req);
|
||||
|
||||
loop_->runAfter(
|
||||
timeout,
|
||||
[weakCallbackBackPtr =
|
||||
std::weak_ptr<RequestCallbackParams>(callbackParamsPtr)] {
|
||||
auto callbackParamsPtr = weakCallbackBackPtr.lock();
|
||||
if (callbackParamsPtr != nullptr)
|
||||
{
|
||||
auto &thisPtr = callbackParamsPtr->clientPtr;
|
||||
if (callbackParamsPtr->timeoutFlag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
callbackParamsPtr->timeoutFlag = true;
|
||||
|
||||
for (auto iter = thisPtr->requestsBuffer_.begin();
|
||||
iter != thisPtr->requestsBuffer_.end();
|
||||
++iter)
|
||||
{
|
||||
if (iter->first == callbackParamsPtr->requestPtr)
|
||||
{
|
||||
thisPtr->requestsBuffer_.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(callbackParamsPtr->callback)(ReqResult::Timeout, nullptr);
|
||||
}
|
||||
});
|
||||
sendRequestInLoop(req,
|
||||
[callbackParamsPtr](ReqResult r,
|
||||
const HttpResponsePtr &resp) {
|
||||
if (callbackParamsPtr->timeoutFlag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
callbackParamsPtr->timeoutFlag = true;
|
||||
(callbackParamsPtr->callback)(r, resp);
|
||||
});
|
||||
}
|
||||
|
||||
static bool isValidIpAddr(const trantor::InetAddress &addr)
|
||||
{
|
||||
if (addr.portNetEndian() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!addr.isIpV6())
|
||||
{
|
||||
return addr.ipNetEndian() != 0;
|
||||
}
|
||||
// Is ipv6
|
||||
auto ipaddr = addr.ip6NetEndian();
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (ipaddr[i] != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
||||
drogon::HttpReqCallback &&callback)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
if (!static_cast<drogon::HttpRequestImpl *>(req.get())->passThrough())
|
||||
{
|
||||
req->addHeader("connection", "Keep-Alive");
|
||||
if (!userAgent_.empty())
|
||||
req->addHeader("user-agent", userAgent_);
|
||||
}
|
||||
// Set the host header if not already set
|
||||
if (req->getHeader("host").empty())
|
||||
{
|
||||
if (onDefaultPort())
|
||||
{
|
||||
req->addHeader("host", host());
|
||||
}
|
||||
else
|
||||
{
|
||||
req->addHeader("host", host() + ":" + std::to_string(port()));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &cookie : validCookies_)
|
||||
{
|
||||
if ((cookie.expiresDate().microSecondsSinceEpoch() == 0 ||
|
||||
cookie.expiresDate() > trantor::Date::now()) &&
|
||||
(cookie.path().empty() || req->path().find(cookie.path()) == 0))
|
||||
{
|
||||
req->addCookie(cookie.key(), cookie.value());
|
||||
}
|
||||
}
|
||||
|
||||
if (!tcpClientPtr_)
|
||||
{
|
||||
auto callbackPtr =
|
||||
std::make_shared<drogon::HttpReqCallback>(std::move(callback));
|
||||
requestsBuffer_.push_back(
|
||||
{req,
|
||||
[thisPtr = shared_from_this(),
|
||||
callbackPtr](ReqResult result, const HttpResponsePtr &response) {
|
||||
(*callbackPtr)(result, response);
|
||||
}});
|
||||
|
||||
if (domain_.empty() || !isDomainName_)
|
||||
{
|
||||
// Valid ip address, no domain, connect directly
|
||||
if (isValidIpAddr(serverAddr_))
|
||||
{
|
||||
createTcpClient();
|
||||
}
|
||||
// No ip address and no domain, respond with BadServerAddress
|
||||
else
|
||||
{
|
||||
requestsBuffer_.pop_front();
|
||||
(*callbackPtr)(ReqResult::BadServerAddress, nullptr);
|
||||
assert(requestsBuffer_.empty());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// A dns query is on going.
|
||||
if (dns_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Always do dns query when (re)connects a domain.
|
||||
dns_ = true;
|
||||
if (!resolverPtr_)
|
||||
{
|
||||
resolverPtr_ =
|
||||
trantor::Resolver::newResolver(loop_, kDefaultDNSTimeout);
|
||||
}
|
||||
auto thisPtr = shared_from_this();
|
||||
resolverPtr_->resolve(
|
||||
domain_, [thisPtr](const trantor::InetAddress &addr) {
|
||||
thisPtr->loop_->runInLoop([thisPtr, addr]() {
|
||||
// Retrieve port from old serverAddr_
|
||||
auto port = thisPtr->serverAddr_.portNetEndian();
|
||||
thisPtr->serverAddr_ = addr;
|
||||
thisPtr->serverAddr_.setPortNetEndian(port);
|
||||
LOG_TRACE << "dns:domain=" << thisPtr->domain_
|
||||
<< ";ip=" << thisPtr->serverAddr_.toIp();
|
||||
thisPtr->dns_ = false;
|
||||
|
||||
if (isValidIpAddr(thisPtr->serverAddr_))
|
||||
{
|
||||
thisPtr->createTcpClient();
|
||||
return;
|
||||
}
|
||||
|
||||
// DNS fail to get valid ip address,
|
||||
// respond all requests with BadServerAddress
|
||||
while (!(thisPtr->requestsBuffer_).empty())
|
||||
{
|
||||
auto &reqAndCb = (thisPtr->requestsBuffer_).front();
|
||||
reqAndCb.second(ReqResult::BadServerAddress, nullptr);
|
||||
(thisPtr->requestsBuffer_).pop_front();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// send request;
|
||||
auto connPtr = tcpClientPtr_->connection();
|
||||
auto thisPtr = shared_from_this();
|
||||
|
||||
// Not connected, push request to buffer and wait for connection
|
||||
if (!connPtr || connPtr->disconnected())
|
||||
{
|
||||
requestsBuffer_.push_back(
|
||||
{req,
|
||||
[thisPtr,
|
||||
callback = std::move(callback)](ReqResult result,
|
||||
const HttpResponsePtr &response) {
|
||||
callback(result, response);
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
// Connected, send request now
|
||||
if (pipeliningCallbacks_.size() <= pipeliningDepth_ &&
|
||||
requestsBuffer_.empty())
|
||||
{
|
||||
sendReq(connPtr, req);
|
||||
pipeliningCallbacks_.push(
|
||||
{req,
|
||||
[thisPtr,
|
||||
callback = std::move(callback)](ReqResult result,
|
||||
const HttpResponsePtr &response) {
|
||||
callback(result, response);
|
||||
}});
|
||||
}
|
||||
else
|
||||
{
|
||||
requestsBuffer_.push_back(
|
||||
{req,
|
||||
[thisPtr,
|
||||
callback = std::move(callback)](ReqResult result,
|
||||
const HttpResponsePtr &response) {
|
||||
callback(result, response);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr,
|
||||
const HttpRequestPtr &req)
|
||||
{
|
||||
trantor::MsgBuffer buffer;
|
||||
assert(req);
|
||||
auto implPtr = static_cast<HttpRequestImpl *>(req.get());
|
||||
implPtr->appendToBuffer(&buffer);
|
||||
LOG_TRACE << "Send request:"
|
||||
<< std::string(buffer.peek(), buffer.readableBytes());
|
||||
bytesSent_ += buffer.readableBytes();
|
||||
connPtr->send(std::move(buffer));
|
||||
}
|
||||
|
||||
void HttpClientImpl::handleResponse(
|
||||
const HttpResponseImplPtr &resp,
|
||||
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
||||
const trantor::TcpConnectionPtr &connPtr)
|
||||
{
|
||||
assert(!pipeliningCallbacks_.empty());
|
||||
auto &coding = resp->getHeaderBy("content-encoding");
|
||||
if (coding == "gzip")
|
||||
{
|
||||
resp->gunzip();
|
||||
}
|
||||
#ifdef USE_BROTLI
|
||||
else if (coding == "br")
|
||||
{
|
||||
resp->brDecompress();
|
||||
}
|
||||
#endif
|
||||
auto cb = std::move(reqAndCb);
|
||||
pipeliningCallbacks_.pop();
|
||||
handleCookies(resp);
|
||||
cb.second(ReqResult::Ok, resp);
|
||||
|
||||
// LOG_TRACE << "pipelining buffer size=" <<
|
||||
// pipeliningCallbacks_.size(); LOG_TRACE << "requests buffer size="
|
||||
// << requestsBuffer_.size();
|
||||
|
||||
if (connPtr->connected())
|
||||
{
|
||||
if (!requestsBuffer_.empty())
|
||||
{
|
||||
auto &reqAndCallback = requestsBuffer_.front();
|
||||
sendReq(connPtr, reqAndCallback.first);
|
||||
pipeliningCallbacks_.push(std::move(reqAndCallback));
|
||||
requestsBuffer_.pop_front();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resp->ifCloseConnection() && pipeliningCallbacks_.empty())
|
||||
{
|
||||
tcpClientPtr_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (!pipeliningCallbacks_.empty())
|
||||
{
|
||||
auto cb = std::move(pipeliningCallbacks_.front());
|
||||
pipeliningCallbacks_.pop();
|
||||
cb.second(ReqResult::NetworkFailure, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
|
||||
trantor::MsgBuffer *msg)
|
||||
{
|
||||
auto responseParser = connPtr->getContext<HttpResponseParser>();
|
||||
|
||||
// LOG_TRACE << "###:" << msg->readableBytes();
|
||||
auto msgSize = msg->readableBytes();
|
||||
while (msg->readableBytes() > 0)
|
||||
{
|
||||
if (pipeliningCallbacks_.empty())
|
||||
{
|
||||
LOG_ERROR << "More responses than expected!";
|
||||
connPtr->shutdown();
|
||||
return;
|
||||
}
|
||||
auto &firstReq = pipeliningCallbacks_.front();
|
||||
if (firstReq.first->method() == Head)
|
||||
{
|
||||
responseParser->setForHeadMethod();
|
||||
}
|
||||
if (!responseParser->parseResponse(msg))
|
||||
{
|
||||
onError(ReqResult::BadResponse);
|
||||
bytesReceived_ += (msgSize - msg->readableBytes());
|
||||
return;
|
||||
}
|
||||
if (responseParser->gotAll())
|
||||
{
|
||||
auto resp = responseParser->responseImpl();
|
||||
resp->setPeerCertificate(connPtr->peerCertificate());
|
||||
responseParser->reset();
|
||||
bytesReceived_ += (msgSize - msg->readableBytes());
|
||||
msgSize = msg->readableBytes();
|
||||
handleResponse(resp, std::move(firstReq), connPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesReceived_ += (msgSize - msg->readableBytes());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
|
||||
uint16_t port,
|
||||
bool useSSL,
|
||||
trantor::EventLoop *loop,
|
||||
bool useOldTLS,
|
||||
bool validateCert)
|
||||
{
|
||||
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
|
||||
return std::make_shared<HttpClientImpl>(
|
||||
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
|
||||
trantor::InetAddress(ip, port, isIpv6),
|
||||
useSSL,
|
||||
useOldTLS,
|
||||
validateCert);
|
||||
}
|
||||
|
||||
HttpClientPtr HttpClient::newHttpClient(const std::string &hostString,
|
||||
trantor::EventLoop *loop,
|
||||
bool useOldTLS,
|
||||
bool validateCert)
|
||||
{
|
||||
return std::make_shared<HttpClientImpl>(
|
||||
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
|
||||
hostString,
|
||||
useOldTLS,
|
||||
validateCert);
|
||||
}
|
||||
|
||||
void HttpClientImpl::onError(ReqResult result)
|
||||
{
|
||||
while (!pipeliningCallbacks_.empty())
|
||||
{
|
||||
auto cb = std::move(pipeliningCallbacks_.front());
|
||||
pipeliningCallbacks_.pop();
|
||||
cb.second(result, nullptr);
|
||||
}
|
||||
while (!requestsBuffer_.empty())
|
||||
{
|
||||
auto cb = std::move(requestsBuffer_.front().second);
|
||||
requestsBuffer_.pop_front();
|
||||
cb(result, nullptr);
|
||||
}
|
||||
tcpClientPtr_.reset();
|
||||
}
|
||||
|
||||
void HttpClientImpl::handleCookies(const HttpResponseImplPtr &resp)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
if (!enableCookies_)
|
||||
return;
|
||||
for (auto &iter : resp->getCookies())
|
||||
{
|
||||
auto &cookie = iter.second;
|
||||
if (!cookie.domain().empty() && cookie.domain() != domain_)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (cookie.isSecure())
|
||||
{
|
||||
if (useSSL_)
|
||||
{
|
||||
validCookies_.emplace_back(cookie);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
validCookies_.emplace_back(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClientImpl::setCertPath(const std::string &cert,
|
||||
const std::string &key)
|
||||
{
|
||||
clientCertPath_ = cert;
|
||||
clientKeyPath_ = key;
|
||||
}
|
||||
|
||||
void HttpClientImpl::addSSLConfigs(
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
|
||||
{
|
||||
for (const auto &cmd : sslConfCmds)
|
||||
{
|
||||
sslConfCmds_.push_back(cmd);
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpClientImpl.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by the MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <drogon/Cookie.h>
|
||||
#include <drogon/HttpClient.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/Resolver.h>
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include "impl_forwards.h"
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpClientImpl final : public HttpClient,
|
||||
public std::enable_shared_from_this<HttpClientImpl>
|
||||
{
|
||||
public:
|
||||
HttpClientImpl(trantor::EventLoop *loop,
|
||||
const trantor::InetAddress &addr,
|
||||
bool useSSL = false,
|
||||
bool useOldTLS = false,
|
||||
bool validateCert = true);
|
||||
HttpClientImpl(trantor::EventLoop *loop,
|
||||
const std::string &hostString,
|
||||
bool useOldTLS = false,
|
||||
bool validateCert = true);
|
||||
void sendRequest(const HttpRequestPtr &req,
|
||||
const HttpReqCallback &callback,
|
||||
double timeout = 0) override;
|
||||
void sendRequest(const HttpRequestPtr &req,
|
||||
HttpReqCallback &&callback,
|
||||
double timeout = 0) override;
|
||||
|
||||
trantor::EventLoop *getLoop() override
|
||||
{
|
||||
return loop_;
|
||||
}
|
||||
|
||||
void setPipeliningDepth(size_t depth) override
|
||||
{
|
||||
pipeliningDepth_ = depth;
|
||||
}
|
||||
|
||||
~HttpClientImpl();
|
||||
|
||||
void enableCookies(bool flag = true) override
|
||||
{
|
||||
enableCookies_ = flag;
|
||||
}
|
||||
|
||||
void addCookie(const std::string &key, const std::string &value) override
|
||||
{
|
||||
validCookies_.emplace_back(Cookie(key, value));
|
||||
}
|
||||
|
||||
void addCookie(const Cookie &cookie) override
|
||||
{
|
||||
validCookies_.emplace_back(cookie);
|
||||
}
|
||||
|
||||
size_t bytesSent() const override
|
||||
{
|
||||
return bytesSent_;
|
||||
}
|
||||
|
||||
size_t bytesReceived() const override
|
||||
{
|
||||
return bytesReceived_;
|
||||
}
|
||||
|
||||
void setUserAgent(const std::string &userAgent) override
|
||||
{
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
uint16_t port() const override
|
||||
{
|
||||
return serverAddr_.toPort();
|
||||
}
|
||||
|
||||
std::string host() const override
|
||||
{
|
||||
if (domain_.empty())
|
||||
return serverAddr_.toIp();
|
||||
return domain_;
|
||||
}
|
||||
|
||||
bool secure() const override
|
||||
{
|
||||
return useSSL_;
|
||||
}
|
||||
|
||||
void setCertPath(const std::string &cert, const std::string &key) override;
|
||||
void addSSLConfigs(const std::vector<std::pair<std::string, std::string>>
|
||||
&sslConfCmds) override;
|
||||
|
||||
void setSockOptCallback(std::function<void(int)> cb) override
|
||||
{
|
||||
sockOptCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
std::size_t requestsBufferSize() override
|
||||
{
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
return requestsBuffer_.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::promise<std::size_t> bufferSize;
|
||||
loop_->queueInLoop(
|
||||
[&] { bufferSize.set_value(requestsBuffer_.size()); });
|
||||
return bufferSize.get_future().get();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<trantor::TcpClient> tcpClientPtr_;
|
||||
trantor::EventLoop *loop_;
|
||||
trantor::InetAddress serverAddr_;
|
||||
bool useSSL_;
|
||||
bool validateCert_;
|
||||
void sendReq(const trantor::TcpConnectionPtr &connPtr,
|
||||
const HttpRequestPtr &req);
|
||||
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||
HttpReqCallback &&callback);
|
||||
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||
HttpReqCallback &&callback,
|
||||
double timeout);
|
||||
void handleCookies(const HttpResponseImplPtr &resp);
|
||||
void handleResponse(const HttpResponseImplPtr &resp,
|
||||
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
||||
const trantor::TcpConnectionPtr &connPtr);
|
||||
void createTcpClient();
|
||||
std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> pipeliningCallbacks_;
|
||||
std::list<std::pair<HttpRequestPtr, HttpReqCallback>> requestsBuffer_;
|
||||
void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
|
||||
void onError(ReqResult result);
|
||||
std::string domain_;
|
||||
bool isDomainName_{true}; // true if domain_ is name
|
||||
size_t pipeliningDepth_{0};
|
||||
bool enableCookies_{false};
|
||||
std::vector<Cookie> validCookies_;
|
||||
size_t bytesSent_{0};
|
||||
size_t bytesReceived_{0};
|
||||
bool dns_{false};
|
||||
std::shared_ptr<trantor::Resolver> resolverPtr_;
|
||||
bool useOldTLS_{false};
|
||||
std::string userAgent_{"DrogonClient"};
|
||||
std::vector<std::pair<std::string, std::string>> sslConfCmds_;
|
||||
std::string clientCertPath_;
|
||||
std::string clientKeyPath_;
|
||||
std::function<void(int)> sockOptCallback_;
|
||||
};
|
||||
|
||||
using HttpClientImplPtr = std::shared_ptr<HttpClientImpl>;
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpConnectionLimit.cc
|
||||
* @author Nitromelon
|
||||
*
|
||||
* Copyright 2023, Nitromelon. All rights reserved.
|
||||
* https://github.com/drogonframework/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpConnectionLimit.h"
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
void HttpConnectionLimit::setMaxConnectionNum(size_t num)
|
||||
{
|
||||
maxConnectionNum_ = num;
|
||||
}
|
||||
|
||||
void HttpConnectionLimit::setMaxConnectionNumPerIP(size_t num)
|
||||
{
|
||||
maxConnectionNumPerIP_ = num;
|
||||
}
|
||||
|
||||
bool HttpConnectionLimit::tryAddConnection(
|
||||
const trantor::TcpConnectionPtr &conn)
|
||||
{
|
||||
assert(conn->connected());
|
||||
if (connectionNum_.fetch_add(1, std::memory_order_relaxed) >
|
||||
maxConnectionNum_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (maxConnectionNumPerIP_ > 0)
|
||||
{
|
||||
std::string ip = conn->peerAddr().toIp();
|
||||
size_t numOnThisIp;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
numOnThisIp = (++ipConnectionsMap_[ip]);
|
||||
}
|
||||
if (numOnThisIp > maxConnectionNumPerIP_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpConnectionLimit::releaseConnection(
|
||||
const trantor::TcpConnectionPtr &conn)
|
||||
{
|
||||
assert(!conn->connected());
|
||||
if (!conn->hasContext())
|
||||
{
|
||||
// If the connection is connected to the SSL port and then
|
||||
// disconnected before the SSL handshake.
|
||||
return;
|
||||
}
|
||||
connectionNum_.fetch_sub(1, std::memory_order_relaxed);
|
||||
if (maxConnectionNumPerIP_ > 0)
|
||||
{
|
||||
std::string ip = conn->peerAddr().toIp();
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto iter = ipConnectionsMap_.find(ip);
|
||||
if (iter != ipConnectionsMap_.end())
|
||||
{
|
||||
if (--iter->second <= 0)
|
||||
{
|
||||
ipConnectionsMap_.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpConnectionLimit.h
|
||||
* @author Nitromelon
|
||||
*
|
||||
* Copyright 2023, Nitromelon. All rights reserved.
|
||||
* https://github.com/drogonframework/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpConnectionLimit
|
||||
{
|
||||
public:
|
||||
static HttpConnectionLimit &instance()
|
||||
{
|
||||
static HttpConnectionLimit inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
size_t getConnectionNum() const
|
||||
{
|
||||
return connectionNum_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// don't set after start
|
||||
void setMaxConnectionNum(size_t num);
|
||||
void setMaxConnectionNumPerIP(size_t num);
|
||||
|
||||
bool tryAddConnection(const trantor::TcpConnectionPtr &conn);
|
||||
void releaseConnection(const trantor::TcpConnectionPtr &conn);
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
|
||||
size_t maxConnectionNum_{100000};
|
||||
std::atomic<size_t> connectionNum_{0};
|
||||
|
||||
size_t maxConnectionNumPerIP_{0};
|
||||
std::unordered_map<std::string, size_t> ipConnectionsMap_;
|
||||
};
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpControllerBinder.cc
|
||||
* @author Nitromelon
|
||||
*
|
||||
* Copyright 2023, Nitromelon. All rights reserved.
|
||||
* https://github.com/drogonframework/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpControllerBinder.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
#include <drogon/WebSocketController.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
void HttpSimpleControllerBinder::handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
// Binders without controller should be removed after run()
|
||||
assert(controller_);
|
||||
|
||||
try
|
||||
{
|
||||
auto cb = callback; // copy
|
||||
controller_->asyncHandleHttpRequest(req, std::move(cb));
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
app().getExceptionHandler()(e, req, std::move(callback));
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR << "Exception not derived from std::exception";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpControllerBinder::handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
auto ¶msVector = req->getRoutingParameters();
|
||||
std::deque<std::string> params(paramsVector.begin(), paramsVector.end());
|
||||
binderPtr_->handleHttpRequest(params, req, std::move(callback));
|
||||
}
|
||||
|
||||
void WebsocketControllerBinder::handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
std::string wsKey = req->getHeaderBy("sec-websocket-key");
|
||||
wsKey.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
unsigned char accKey[20];
|
||||
auto sha1 = trantor::utils::sha1(wsKey.c_str(), wsKey.length());
|
||||
memcpy(accKey, &sha1, sizeof(sha1));
|
||||
auto base64Key = utils::base64Encode(accKey, sizeof(accKey));
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k101SwitchingProtocols);
|
||||
resp->addHeader("Upgrade", "websocket");
|
||||
resp->addHeader("Connection", "Upgrade");
|
||||
resp->addHeader("Sec-WebSocket-Accept", base64Key);
|
||||
callback(resp);
|
||||
}
|
||||
|
||||
void WebsocketControllerBinder::handleNewConnection(
|
||||
const HttpRequestImplPtr &req,
|
||||
const WebSocketConnectionImplPtr &wsConnPtr) const
|
||||
{
|
||||
auto ctrlPtr = controller_;
|
||||
assert(ctrlPtr);
|
||||
wsConnPtr->setMessageCallback(
|
||||
[ctrlPtr](std::string &&message,
|
||||
const WebSocketConnectionImplPtr &connPtr,
|
||||
const WebSocketMessageType &type) {
|
||||
ctrlPtr->handleNewMessage(connPtr, std::move(message), type);
|
||||
});
|
||||
wsConnPtr->setCloseCallback(
|
||||
[ctrlPtr](const WebSocketConnectionImplPtr &connPtr) {
|
||||
ctrlPtr->handleConnectionClosed(connPtr);
|
||||
});
|
||||
ctrlPtr->handleNewConnection(req, wsConnPtr);
|
||||
}
|
||||
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpControllerBinder.h
|
||||
* @author Nitromelon
|
||||
*
|
||||
* Copyright 2023, Nitromelon. All rights reserved.
|
||||
* https://github.com/drogonframework/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ControllerBinderBase.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "WebSocketConnectionImpl.h"
|
||||
#include <drogon/HttpBinder.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
class HttpSimpleControllerBinder : public ControllerBinderBase
|
||||
{
|
||||
public:
|
||||
void handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const override;
|
||||
|
||||
std::shared_ptr<HttpSimpleControllerBase> controller_;
|
||||
};
|
||||
|
||||
class HttpControllerBinder : public ControllerBinderBase
|
||||
{
|
||||
public:
|
||||
void handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const override;
|
||||
|
||||
bool isStreamHandler() const override
|
||||
{
|
||||
assert(binderPtr_);
|
||||
return binderPtr_->isStreamHandler();
|
||||
}
|
||||
|
||||
internal::HttpBinderBasePtr binderPtr_;
|
||||
std::vector<size_t> parameterPlaces_;
|
||||
std::vector<std::pair<std::string, size_t>> queryParametersPlaces_;
|
||||
};
|
||||
|
||||
struct WebsocketControllerBinder : public ControllerBinderBase
|
||||
{
|
||||
std::shared_ptr<WebSocketControllerBase> controller_;
|
||||
|
||||
void handleRequest(
|
||||
const HttpRequestImplPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const override;
|
||||
|
||||
void handleNewConnection(const HttpRequestImplPtr &req,
|
||||
const WebSocketConnectionImplPtr &wsConnPtr) const;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,764 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpControllersRouter.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpControllersRouter.h"
|
||||
#include "HttpControllerBinder.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "MiddlewaresFunction.h"
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
#include <drogon/WebSocketController.h>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
void HttpControllersRouter::init(
|
||||
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
|
||||
{
|
||||
auto initMiddlewaresAndCorsMethods = [](const auto &item) {
|
||||
auto corsMethods = std::make_shared<std::string>("OPTIONS,");
|
||||
for (size_t i = 0; i < Invalid; ++i)
|
||||
{
|
||||
auto &binder = item.binders_[i];
|
||||
if (binder)
|
||||
{
|
||||
binder->middlewares_ = middlewares_function::createMiddlewares(
|
||||
binder->middlewareNames_);
|
||||
binder->corsMethods_ = corsMethods;
|
||||
if (binder->isCORS_)
|
||||
{
|
||||
if (i == Get)
|
||||
{
|
||||
corsMethods->append("GET,HEAD,");
|
||||
}
|
||||
else if (i != Options)
|
||||
{
|
||||
corsMethods->append(to_string_view((HttpMethod)i));
|
||||
corsMethods->append(",");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
corsMethods->pop_back(); // remove last comma
|
||||
};
|
||||
|
||||
for (auto &iter : simpleCtrlMap_)
|
||||
{
|
||||
initMiddlewaresAndCorsMethods(iter.second);
|
||||
}
|
||||
|
||||
for (auto &iter : wsCtrlMap_)
|
||||
{
|
||||
initMiddlewaresAndCorsMethods(iter.second);
|
||||
}
|
||||
|
||||
for (auto &router : ctrlVector_)
|
||||
{
|
||||
router.regex_ = std::regex(router.pathParameterPattern_,
|
||||
std::regex_constants::icase);
|
||||
initMiddlewaresAndCorsMethods(router);
|
||||
}
|
||||
|
||||
for (auto &p : ctrlMap_)
|
||||
{
|
||||
auto &router = p.second;
|
||||
router.regex_ = std::regex(router.pathParameterPattern_,
|
||||
std::regex_constants::icase);
|
||||
initMiddlewaresAndCorsMethods(router);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpControllersRouter::reset()
|
||||
{
|
||||
simpleCtrlMap_.clear();
|
||||
ctrlMap_.clear();
|
||||
ctrlVector_.clear();
|
||||
wsCtrlMap_.clear();
|
||||
}
|
||||
|
||||
std::vector<HttpHandlerInfo> HttpControllersRouter::getHandlersInfo() const
|
||||
{
|
||||
std::vector<HttpHandlerInfo> ret;
|
||||
auto gatherInfo = [&ret](const std::string &path, const auto &item) {
|
||||
for (size_t i = 0; i < Invalid; ++i)
|
||||
{
|
||||
if (item.binders_[i])
|
||||
{
|
||||
std::string description;
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(item)>,
|
||||
SimpleControllerRouterItem>)
|
||||
|
||||
{
|
||||
description = std::string("HttpSimpleController: ") +
|
||||
item.binders_[i]->handlerName_;
|
||||
}
|
||||
else if constexpr (std::is_same_v<
|
||||
std::decay_t<decltype(item)>,
|
||||
WebSocketControllerRouterItem> ||
|
||||
std::is_same_v<
|
||||
std::decay_t<decltype(item)>,
|
||||
RegExWebSocketControllerRouterItem>)
|
||||
{
|
||||
description = std::string("WebsocketController: ") +
|
||||
item.binders_[i]->handlerName_;
|
||||
}
|
||||
else
|
||||
{
|
||||
description =
|
||||
item.binders_[i]->handlerName_.empty()
|
||||
? std::string("Handler: ") +
|
||||
item.binders_[i]->binderPtr_->handlerName()
|
||||
: std::string("HttpController: ") +
|
||||
item.binders_[i]->handlerName_;
|
||||
}
|
||||
ret.emplace_back(path, (HttpMethod)i, std::move(description));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &[path, item] : simpleCtrlMap_)
|
||||
{
|
||||
gatherInfo(path, item);
|
||||
}
|
||||
for (auto &item : ctrlVector_)
|
||||
{
|
||||
gatherInfo(item.pathPattern_, item);
|
||||
}
|
||||
for (auto &[key, item] : ctrlMap_)
|
||||
{
|
||||
gatherInfo(item.pathPattern_, item);
|
||||
}
|
||||
for (auto &[path, item] : wsCtrlMap_)
|
||||
{
|
||||
gatherInfo(path, item);
|
||||
}
|
||||
for (auto &item : wsCtrlVector_)
|
||||
{
|
||||
gatherInfo(item.pathPattern_, item);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Binder, typename RouterItem>
|
||||
static void addCtrlBinderToRouterItem(const std::shared_ptr<Binder> &binderPtr,
|
||||
RouterItem &router,
|
||||
const std::vector<HttpMethod> &methods)
|
||||
{
|
||||
if (!methods.empty())
|
||||
{
|
||||
for (const auto &method : methods)
|
||||
{
|
||||
router.binders_[method] = binderPtr;
|
||||
if (method == Options)
|
||||
{
|
||||
binderPtr->isCORS_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// All HTTP methods are valid
|
||||
binderPtr->isCORS_ = true;
|
||||
for (int i = 0; i < Invalid; ++i)
|
||||
{
|
||||
router.binders_[i] = binderPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleControllerProcessResult
|
||||
{
|
||||
std::string lowerPath;
|
||||
std::vector<HttpMethod> validMethods;
|
||||
std::vector<std::string> middlewares;
|
||||
};
|
||||
|
||||
static SimpleControllerProcessResult processSimpleControllerParams(
|
||||
const std::string &pathName,
|
||||
const std::vector<internal::HttpConstraint> &constraints)
|
||||
{
|
||||
std::string path(pathName);
|
||||
std::transform(pathName.begin(),
|
||||
pathName.end(),
|
||||
path.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
std::vector<HttpMethod> validMethods;
|
||||
std::vector<std::string> middlewareNames;
|
||||
for (const auto &constraint : constraints)
|
||||
{
|
||||
if (constraint.type() == internal::ConstraintType::HttpMiddleware)
|
||||
{
|
||||
middlewareNames.push_back(constraint.getMiddlewareName());
|
||||
}
|
||||
else if (constraint.type() == internal::ConstraintType::HttpMethod)
|
||||
{
|
||||
validMethods.push_back(constraint.getHttpMethod());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Invalid controller constraint type";
|
||||
// Used to call exit() here, but that's not very nice.
|
||||
}
|
||||
}
|
||||
return {
|
||||
std::move(path),
|
||||
std::move(validMethods),
|
||||
std::move(middlewareNames),
|
||||
};
|
||||
}
|
||||
|
||||
void HttpControllersRouter::registerHttpSimpleController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints)
|
||||
{
|
||||
assert(!pathName.empty());
|
||||
assert(!ctrlName.empty());
|
||||
// Note: some compiler version failed to handle structural bindings with
|
||||
// lambda capture
|
||||
auto result = processSimpleControllerParams(pathName, constraints);
|
||||
std::string path = std::move(result.lowerPath);
|
||||
|
||||
auto &item = simpleCtrlMap_[path];
|
||||
auto binder = std::make_shared<HttpSimpleControllerBinder>();
|
||||
binder->handlerName_ = ctrlName;
|
||||
binder->middlewareNames_ = result.middlewares;
|
||||
drogon::app().getLoop()->queueInLoop([this, binder, ctrlName, path]() {
|
||||
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
|
||||
auto controller =
|
||||
std::dynamic_pointer_cast<HttpSimpleControllerBase>(object_);
|
||||
if (!controller)
|
||||
{
|
||||
LOG_ERROR << "Controller class not found: " << ctrlName;
|
||||
simpleCtrlMap_.erase(path);
|
||||
return;
|
||||
}
|
||||
binder->controller_ = controller;
|
||||
// Recreate this with the correct number of threads.
|
||||
binder->responseCache_ = IOThreadStorage<HttpResponsePtr>();
|
||||
});
|
||||
|
||||
addCtrlBinderToRouterItem(binder, item, result.validMethods);
|
||||
}
|
||||
|
||||
void HttpControllersRouter::registerWebSocketController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints)
|
||||
{
|
||||
assert(!pathName.empty());
|
||||
assert(!ctrlName.empty());
|
||||
auto result = processSimpleControllerParams(pathName, constraints);
|
||||
std::string path = std::move(result.lowerPath);
|
||||
|
||||
auto &item = wsCtrlMap_[path];
|
||||
auto binder = std::make_shared<WebsocketControllerBinder>();
|
||||
binder->handlerName_ = ctrlName;
|
||||
binder->middlewareNames_ = result.middlewares;
|
||||
drogon::app().getLoop()->queueInLoop([this, binder, ctrlName, path]() {
|
||||
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
|
||||
auto controller =
|
||||
std::dynamic_pointer_cast<WebSocketControllerBase>(object_);
|
||||
if (!controller)
|
||||
{
|
||||
LOG_ERROR << "Websocket controller class not found: " << ctrlName;
|
||||
wsCtrlMap_.erase(path);
|
||||
return;
|
||||
}
|
||||
|
||||
binder->controller_ = controller;
|
||||
});
|
||||
|
||||
addCtrlBinderToRouterItem(binder, item, result.validMethods);
|
||||
}
|
||||
|
||||
void HttpControllersRouter::registerWebSocketControllerRegex(
|
||||
const std::string ®Exp,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints)
|
||||
{
|
||||
assert(!regExp.empty());
|
||||
assert(!ctrlName.empty());
|
||||
auto result = processSimpleControllerParams(regExp, constraints);
|
||||
auto binder = std::make_shared<WebsocketControllerBinder>();
|
||||
binder->handlerName_ = ctrlName;
|
||||
binder->middlewareNames_ = result.middlewares;
|
||||
drogon::app().getLoop()->queueInLoop([binder, ctrlName]() {
|
||||
auto &object_ = DrClassMap::getSingleInstance(ctrlName);
|
||||
auto controller =
|
||||
std::dynamic_pointer_cast<WebSocketControllerBase>(object_);
|
||||
binder->controller_ = controller;
|
||||
});
|
||||
struct RegExWebSocketControllerRouterItem router;
|
||||
router.pathPattern_ = regExp;
|
||||
router.regex_ = regExp;
|
||||
addCtrlBinderToRouterItem(binder, router, result.validMethods);
|
||||
wsCtrlVector_.push_back(std::move(router));
|
||||
}
|
||||
|
||||
void HttpControllersRouter::addHttpRegex(
|
||||
const std::string ®Exp,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &middlewareNames,
|
||||
const std::string &handlerName)
|
||||
{
|
||||
auto binderInfo = std::make_shared<HttpControllerBinder>();
|
||||
binderInfo->middlewareNames_ = middlewareNames;
|
||||
binderInfo->handlerName_ = handlerName;
|
||||
binderInfo->binderPtr_ = binder;
|
||||
drogon::app().getLoop()->queueInLoop([binderInfo]() {
|
||||
// Recreate this with the correct number of threads.
|
||||
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
|
||||
});
|
||||
|
||||
addRegexCtrlBinder(binderInfo, regExp, regExp, validMethods);
|
||||
}
|
||||
|
||||
void HttpControllersRouter::addHttpPath(
|
||||
const std::string &path,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &middlewareNames,
|
||||
const std::string &handlerName)
|
||||
{
|
||||
// Path is like /api/v1/service/method/{1}/{2}/xxx...
|
||||
std::vector<size_t> places;
|
||||
std::string tmpPath = path;
|
||||
std::string paras;
|
||||
static const std::regex regex("\\{([^/]*)\\}");
|
||||
std::smatch results;
|
||||
auto pos = tmpPath.find('?');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
paras = tmpPath.substr(pos + 1);
|
||||
tmpPath = tmpPath.substr(0, pos);
|
||||
}
|
||||
std::string originPath = tmpPath;
|
||||
size_t placeIndex = 1;
|
||||
// Process path parameter placeholders
|
||||
while (std::regex_search(tmpPath, results, regex))
|
||||
{
|
||||
if (results.size() > 1)
|
||||
{
|
||||
auto result = results[1].str();
|
||||
if (!result.empty() &&
|
||||
std::all_of(result.begin(), result.end(), [](const char c) {
|
||||
return std::isdigit(c);
|
||||
}))
|
||||
{
|
||||
auto place = (size_t)std::stoi(result);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
<< ") out of range (1 to " << binder->paramCount()
|
||||
<< ")";
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
if (!std::all_of(places.begin(),
|
||||
places.end(),
|
||||
[place](size_t i) { return i != place; }))
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholders are duplicated: index="
|
||||
<< place;
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
places.push_back(place);
|
||||
}
|
||||
else
|
||||
{
|
||||
static const std::regex regNumberAndName("([0-9]+):.*");
|
||||
std::smatch regexResult;
|
||||
if (std::regex_match(result, regexResult, regNumberAndName))
|
||||
{
|
||||
assert(regexResult.size() == 2 && regexResult[1].matched);
|
||||
auto num = regexResult[1].str();
|
||||
auto place = (size_t)std::stoi(num);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
<< ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
if (!std::all_of(places.begin(),
|
||||
places.end(),
|
||||
[place](size_t i) { return i != place; }))
|
||||
{
|
||||
LOG_ERROR
|
||||
<< "Parameter placeholders are duplicated: index="
|
||||
<< place;
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
places.push_back(place);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!std::all_of(places.begin(),
|
||||
places.end(),
|
||||
[placeIndex](size_t i) {
|
||||
return i != placeIndex;
|
||||
}))
|
||||
{
|
||||
LOG_ERROR
|
||||
<< "Parameter placeholders are duplicated: index="
|
||||
<< placeIndex;
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
places.push_back(placeIndex);
|
||||
}
|
||||
}
|
||||
++placeIndex;
|
||||
}
|
||||
tmpPath = results.suffix();
|
||||
}
|
||||
// Process query parameter placeholders
|
||||
std::vector<std::pair<std::string, size_t>> parametersPlaces;
|
||||
if (!paras.empty())
|
||||
{
|
||||
static const std::regex pregex("([^&]*)=\\{([^&]*)\\}&*");
|
||||
while (std::regex_search(paras, results, pregex))
|
||||
{
|
||||
if (results.size() > 2)
|
||||
{
|
||||
auto result = results[2].str();
|
||||
if (!result.empty() &&
|
||||
std::all_of(result.begin(), result.end(), [](const char c) {
|
||||
return std::isdigit(c);
|
||||
}))
|
||||
{
|
||||
auto place = (size_t)std::stoi(result);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
<< ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
if (!std::all_of(places.begin(),
|
||||
places.end(),
|
||||
[place](size_t i) {
|
||||
return i != place;
|
||||
}) ||
|
||||
!all_of(parametersPlaces.begin(),
|
||||
parametersPlaces.end(),
|
||||
[place](const std::pair<std::string, size_t>
|
||||
&item) {
|
||||
return item.second != place;
|
||||
}))
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholders are "
|
||||
"duplicated: index="
|
||||
<< place;
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
parametersPlaces.emplace_back(results[1].str(), place);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::regex regNumberAndName("([0-9]+):.*");
|
||||
std::smatch regexResult;
|
||||
if (std::regex_match(result, regexResult, regNumberAndName))
|
||||
{
|
||||
assert(regexResult.size() == 2 &&
|
||||
regexResult[1].matched);
|
||||
auto num = regexResult[1].str();
|
||||
auto place = (size_t)std::stoi(num);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
<< ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
if (!std::all_of(places.begin(),
|
||||
places.end(),
|
||||
[place](size_t i) {
|
||||
return i != place;
|
||||
}) ||
|
||||
!all_of(parametersPlaces.begin(),
|
||||
parametersPlaces.end(),
|
||||
[place](const std::pair<std::string, size_t>
|
||||
&item) {
|
||||
return item.second != place;
|
||||
}))
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholders are "
|
||||
"duplicated: index="
|
||||
<< place;
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
parametersPlaces.emplace_back(results[1].str(), place);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!std::all_of(places.begin(),
|
||||
places.end(),
|
||||
[placeIndex](size_t i) {
|
||||
return i != placeIndex;
|
||||
}) ||
|
||||
!all_of(parametersPlaces.begin(),
|
||||
parametersPlaces.end(),
|
||||
[placeIndex](
|
||||
const std::pair<std::string, size_t>
|
||||
&item) {
|
||||
return item.second != placeIndex;
|
||||
}))
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholders are "
|
||||
"duplicated: index="
|
||||
<< placeIndex;
|
||||
LOG_ERROR << "Path pattern: " << path;
|
||||
exit(1);
|
||||
}
|
||||
parametersPlaces.emplace_back(results[1].str(),
|
||||
placeIndex);
|
||||
}
|
||||
}
|
||||
++placeIndex;
|
||||
}
|
||||
paras = results.suffix();
|
||||
}
|
||||
}
|
||||
|
||||
// Create new ControllerBinder
|
||||
auto binderInfo = std::make_shared<HttpControllerBinder>();
|
||||
binderInfo->middlewareNames_ = middlewareNames;
|
||||
binderInfo->handlerName_ = handlerName;
|
||||
binderInfo->binderPtr_ = binder;
|
||||
binderInfo->parameterPlaces_ = std::move(places);
|
||||
binderInfo->queryParametersPlaces_ = std::move(parametersPlaces);
|
||||
drogon::app().getLoop()->queueInLoop([binderInfo]() {
|
||||
// Recreate this with the correct number of threads.
|
||||
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
|
||||
});
|
||||
|
||||
// Create or update RouterItem
|
||||
auto pathParameterPattern =
|
||||
std::regex_replace(originPath, regex, "([^/]*)");
|
||||
if (originPath != pathParameterPattern) // require regex
|
||||
{
|
||||
addRegexCtrlBinder(binderInfo,
|
||||
path,
|
||||
pathParameterPattern,
|
||||
validMethods);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string loweredPath;
|
||||
std::transform(originPath.begin(),
|
||||
originPath.end(),
|
||||
std::back_inserter(loweredPath),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
|
||||
HttpControllerRouterItem *routerItemPtr;
|
||||
// If exists another controllers on the same route, update them
|
||||
auto it = ctrlMap_.find(loweredPath);
|
||||
if (it != ctrlMap_.end())
|
||||
{
|
||||
routerItemPtr = &it->second;
|
||||
}
|
||||
// Create new router item if not exists
|
||||
else
|
||||
{
|
||||
struct HttpControllerRouterItem router;
|
||||
router.pathParameterPattern_ = pathParameterPattern;
|
||||
router.pathPattern_ = path;
|
||||
routerItemPtr =
|
||||
&ctrlMap_.emplace(loweredPath, std::move(router)).first->second;
|
||||
}
|
||||
|
||||
addCtrlBinderToRouterItem(binderInfo, *routerItemPtr, validMethods);
|
||||
}
|
||||
|
||||
RouteResult HttpControllersRouter::route(const HttpRequestImplPtr &req)
|
||||
{
|
||||
// Find simple controller
|
||||
std::string loweredPath(req->path().length(), 0);
|
||||
std::transform(req->path().begin(),
|
||||
req->path().end(),
|
||||
loweredPath.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
{
|
||||
auto it = simpleCtrlMap_.find(loweredPath);
|
||||
if (it != simpleCtrlMap_.end())
|
||||
{
|
||||
auto &ctrlInfo = it->second;
|
||||
req->setMatchedPathPattern(it->first);
|
||||
auto &binder = ctrlInfo.binders_[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
return {RouteResult::MethodNotAllowed, nullptr};
|
||||
}
|
||||
return {RouteResult::Success, binder};
|
||||
}
|
||||
}
|
||||
|
||||
// Find http controller
|
||||
HttpControllerRouterItem *routerItemPtr = nullptr;
|
||||
std::smatch result;
|
||||
auto it = ctrlMap_.find(loweredPath);
|
||||
// Try to find a controller in the hash map. If can't linear search
|
||||
// with regex.
|
||||
if (it != ctrlMap_.end())
|
||||
{
|
||||
routerItemPtr = &it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto &item : ctrlVector_)
|
||||
{
|
||||
const auto &ctrlRegex = item.regex_;
|
||||
if (item.binders_[req->method()] &&
|
||||
std::regex_match(req->path(), result, ctrlRegex))
|
||||
{
|
||||
routerItemPtr = &item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No handler found
|
||||
if (!routerItemPtr)
|
||||
{
|
||||
return {RouteResult::NotFound, nullptr};
|
||||
}
|
||||
HttpControllerRouterItem &routerItem = *routerItemPtr;
|
||||
assert(Invalid > req->method());
|
||||
req->setMatchedPathPattern(routerItem.pathPattern_);
|
||||
auto &binder = routerItem.binders_[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
return {RouteResult::MethodNotAllowed, nullptr};
|
||||
}
|
||||
std::vector<std::string> params;
|
||||
for (size_t j = 1; j < result.size(); ++j)
|
||||
{
|
||||
if (!result[j].matched)
|
||||
continue;
|
||||
size_t place = j;
|
||||
if (j <= binder->parameterPlaces_.size())
|
||||
{
|
||||
place = binder->parameterPlaces_[j - 1];
|
||||
}
|
||||
if (place > params.size())
|
||||
params.resize(place);
|
||||
params[place - 1] = result[j].str();
|
||||
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
|
||||
}
|
||||
|
||||
if (!binder->queryParametersPlaces_.empty())
|
||||
{
|
||||
auto &queryPara = req->getParameters();
|
||||
for (const auto ¶Place : binder->queryParametersPlaces_)
|
||||
{
|
||||
auto place = paraPlace.second;
|
||||
if (place > params.size())
|
||||
params.resize(place);
|
||||
auto iter = queryPara.find(paraPlace.first);
|
||||
if (iter != queryPara.end())
|
||||
{
|
||||
params[place - 1] = iter->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
params[place - 1] = std::string{};
|
||||
}
|
||||
}
|
||||
}
|
||||
req->setRoutingParameters(std::move(params));
|
||||
return {RouteResult::Success, binder};
|
||||
}
|
||||
|
||||
RouteResult HttpControllersRouter::routeWs(const HttpRequestImplPtr &req)
|
||||
{
|
||||
std::string wsKey = req->getHeaderBy("sec-websocket-key");
|
||||
if (!wsKey.empty())
|
||||
{
|
||||
std::string pathLower(req->path().length(), 0);
|
||||
std::transform(req->path().begin(),
|
||||
req->path().end(),
|
||||
pathLower.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
auto iter = wsCtrlMap_.find(pathLower);
|
||||
if (iter != wsCtrlMap_.end())
|
||||
{
|
||||
auto &ctrlInfo = iter->second;
|
||||
req->setMatchedPathPattern(iter->first);
|
||||
auto &binder = ctrlInfo.binders_[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
return {RouteResult::MethodNotAllowed, nullptr};
|
||||
}
|
||||
return {RouteResult::Success, binder};
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto &ctrlInfo : wsCtrlVector_)
|
||||
{
|
||||
auto const &wsCtrlRegex = ctrlInfo.regex_;
|
||||
std::smatch result;
|
||||
if (std::regex_match(req->path(), result, wsCtrlRegex))
|
||||
{
|
||||
req->setMatchedPathPattern(ctrlInfo.pathPattern_);
|
||||
auto &binder = ctrlInfo.binders_[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
return {RouteResult::MethodNotAllowed, nullptr};
|
||||
}
|
||||
return {RouteResult::Success, binder};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {RouteResult::NotFound, nullptr};
|
||||
}
|
||||
|
||||
void HttpControllersRouter::addRegexCtrlBinder(
|
||||
const std::shared_ptr<HttpControllerBinder> &binderPtr,
|
||||
const std::string &pathPattern,
|
||||
const std::string &pathParameterPattern,
|
||||
const std::vector<HttpMethod> &methods)
|
||||
{
|
||||
HttpControllerRouterItem *routerItemPtr;
|
||||
auto existRouter = std::find_if(ctrlVector_.begin(),
|
||||
ctrlVector_.end(),
|
||||
[&pathParameterPattern](const auto &item) {
|
||||
return item.pathParameterPattern_ ==
|
||||
pathParameterPattern;
|
||||
});
|
||||
if (existRouter == ctrlVector_.end())
|
||||
{
|
||||
struct HttpControllerRouterItem router;
|
||||
router.pathParameterPattern_ = pathParameterPattern;
|
||||
router.pathPattern_ = pathPattern;
|
||||
ctrlVector_.push_back(std::move(router));
|
||||
routerItemPtr = &ctrlVector_.back();
|
||||
}
|
||||
else
|
||||
{
|
||||
routerItemPtr = &(*existRouter);
|
||||
}
|
||||
|
||||
addCtrlBinderToRouterItem(binderPtr, *routerItemPtr, methods);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
*
|
||||
* HttpControllersRouter.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "impl_forwards.h"
|
||||
#include "ControllerBinderBase.h"
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpControllerBinder;
|
||||
class HttpSimpleControllerBinder;
|
||||
struct WebsocketControllerBinder;
|
||||
|
||||
class HttpControllersRouter : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
static HttpControllersRouter &instance()
|
||||
{
|
||||
static HttpControllersRouter inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void init(const std::vector<trantor::EventLoop *> &ioLoops);
|
||||
// clean all resources
|
||||
void reset();
|
||||
|
||||
void registerHttpSimpleController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints);
|
||||
void registerWebSocketController(
|
||||
const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints);
|
||||
void registerWebSocketControllerRegex(
|
||||
const std::string ®Exp,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<internal::HttpConstraint> &constraints);
|
||||
void addHttpPath(const std::string &path,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &middlewareNames,
|
||||
const std::string &handlerName = "");
|
||||
void addHttpRegex(const std::string ®Exp,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &middlewareNames,
|
||||
const std::string &handlerName = "");
|
||||
RouteResult route(const HttpRequestImplPtr &req);
|
||||
RouteResult routeWs(const HttpRequestImplPtr &req);
|
||||
std::vector<HttpHandlerInfo> getHandlersInfo() const;
|
||||
|
||||
private:
|
||||
void addRegexCtrlBinder(
|
||||
const std::shared_ptr<HttpControllerBinder> &binderPtr,
|
||||
const std::string &pathPattern,
|
||||
const std::string &pathParameterPattern,
|
||||
const std::vector<HttpMethod> &methods);
|
||||
|
||||
struct SimpleControllerRouterItem
|
||||
{
|
||||
std::shared_ptr<HttpSimpleControllerBinder> binders_[Invalid]{nullptr};
|
||||
};
|
||||
|
||||
struct HttpControllerRouterItem
|
||||
{
|
||||
std::string pathParameterPattern_;
|
||||
std::string pathPattern_;
|
||||
std::regex regex_;
|
||||
std::shared_ptr<HttpControllerBinder> binders_[Invalid]{nullptr};
|
||||
};
|
||||
|
||||
struct WebSocketControllerRouterItem
|
||||
{
|
||||
std::shared_ptr<WebsocketControllerBinder> binders_[Invalid]{nullptr};
|
||||
};
|
||||
|
||||
struct RegExWebSocketControllerRouterItem
|
||||
{
|
||||
std::string pathPattern_;
|
||||
std::regex regex_;
|
||||
std::shared_ptr<WebsocketControllerBinder> binders_[Invalid]{nullptr};
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, SimpleControllerRouterItem> simpleCtrlMap_;
|
||||
std::unordered_map<std::string, HttpControllerRouterItem> ctrlMap_;
|
||||
std::vector<HttpControllerRouterItem> ctrlVector_; // for regexp path
|
||||
std::unordered_map<std::string, WebSocketControllerRouterItem> wsCtrlMap_;
|
||||
std::vector<RegExWebSocketControllerRouterItem> wsCtrlVector_;
|
||||
};
|
||||
} // namespace drogon
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpFileImpl.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpFileImpl.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include <drogon/MultiPart.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
int HttpFileImpl::save() const noexcept
|
||||
{
|
||||
return save(HttpAppFrameworkImpl::instance().getUploadPath());
|
||||
}
|
||||
|
||||
int HttpFileImpl::save(const std::string &path) const noexcept
|
||||
{
|
||||
assert(!path.empty());
|
||||
if (fileName_.empty())
|
||||
return -1;
|
||||
std::filesystem::path fsUploadDir(utils::toNativePath(path));
|
||||
|
||||
if (fsUploadDir.is_absolute())
|
||||
{ // do nothing
|
||||
}
|
||||
else if ((!fsUploadDir.has_parent_path() ||
|
||||
(fsUploadDir.begin()->string() != "." &&
|
||||
fsUploadDir.begin()->string() != "..")))
|
||||
{
|
||||
fsUploadDir = utils::toNativePath(
|
||||
HttpAppFrameworkImpl::instance().getUploadPath()) /
|
||||
fsUploadDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
fsUploadDir = std::filesystem::current_path() / fsUploadDir;
|
||||
}
|
||||
|
||||
fsUploadDir = std::filesystem::weakly_canonical(fsUploadDir);
|
||||
|
||||
if (!std::filesystem::exists(fsUploadDir))
|
||||
{
|
||||
LOG_TRACE << "create path:" << fsUploadDir;
|
||||
std::error_code err;
|
||||
std::filesystem::create_directories(fsUploadDir, err);
|
||||
if (err)
|
||||
{
|
||||
LOG_SYSERR;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path fsSaveToPath(std::filesystem::weakly_canonical(
|
||||
fsUploadDir / utils::toNativePath(fileName_)));
|
||||
LOG_TRACE << "save to path:" << fsSaveToPath;
|
||||
if (!std::equal(fsUploadDir.begin(),
|
||||
fsUploadDir.end(),
|
||||
fsSaveToPath.begin()))
|
||||
{
|
||||
LOG_ERROR
|
||||
<< "Attempt writing outside of upload directory detected. Path: "
|
||||
<< fileName_;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return saveTo(fsSaveToPath);
|
||||
}
|
||||
|
||||
int HttpFileImpl::saveAs(const std::string &fileName) const noexcept
|
||||
{
|
||||
assert(!fileName.empty());
|
||||
std::filesystem::path fsFileName(utils::toNativePath(fileName));
|
||||
if (!fsFileName.is_absolute() && (!fsFileName.has_parent_path() ||
|
||||
(fsFileName.begin()->string() != "." &&
|
||||
fsFileName.begin()->string() != "..")))
|
||||
{
|
||||
std::filesystem::path fsUploadPath(utils::toNativePath(
|
||||
HttpAppFrameworkImpl::instance().getUploadPath()));
|
||||
fsFileName = fsUploadPath / fsFileName;
|
||||
}
|
||||
if (fsFileName.has_parent_path() &&
|
||||
!std::filesystem::exists(fsFileName.parent_path()))
|
||||
{
|
||||
LOG_TRACE << "create path:" << fsFileName.parent_path();
|
||||
std::error_code err;
|
||||
std::filesystem::create_directories(fsFileName.parent_path(), err);
|
||||
if (err)
|
||||
{
|
||||
LOG_SYSERR;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return saveTo(fsFileName);
|
||||
}
|
||||
|
||||
int HttpFileImpl::saveTo(
|
||||
const std::filesystem::path &pathAndFileName) const noexcept
|
||||
{
|
||||
LOG_TRACE << "save uploaded file:" << pathAndFileName;
|
||||
auto wPath = utils::toNativePath(pathAndFileName.native());
|
||||
std::ofstream file(wPath, std::ios::binary);
|
||||
if (file.is_open())
|
||||
{
|
||||
file.write(fileContent_.data(), fileContent_.size());
|
||||
file.close();
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "save failed!";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string HttpFileImpl::getMd5() const noexcept
|
||||
{
|
||||
return utils::getMd5(fileContent_.data(), fileContent_.size());
|
||||
}
|
||||
|
||||
std::string HttpFileImpl::getSha256() const noexcept
|
||||
{
|
||||
return utils::getSha256(fileContent_.data(), fileContent_.size());
|
||||
}
|
||||
|
||||
std::string HttpFileImpl::getSha3() const noexcept
|
||||
{
|
||||
return utils::getSha3(fileContent_.data(), fileContent_.size());
|
||||
}
|
||||
|
||||
const std::string &HttpFile::getFileName() const noexcept
|
||||
{
|
||||
return implPtr_->getFileName();
|
||||
}
|
||||
|
||||
void HttpFile::setFileName(const std::string &fileName) noexcept
|
||||
{
|
||||
implPtr_->setFileName(fileName);
|
||||
}
|
||||
|
||||
std::string_view HttpFile::getFileExtension() const noexcept
|
||||
{
|
||||
return implPtr_->getFileExtension();
|
||||
}
|
||||
|
||||
FileType HttpFile::getFileType() const noexcept
|
||||
{
|
||||
return implPtr_->getFileType();
|
||||
}
|
||||
|
||||
void HttpFile::setFile(const char *data, size_t length) noexcept
|
||||
{
|
||||
implPtr_->setFile(data, length);
|
||||
}
|
||||
|
||||
int HttpFile::save() const noexcept
|
||||
{
|
||||
return implPtr_->save();
|
||||
}
|
||||
|
||||
int HttpFile::save(const std::string &path) const noexcept
|
||||
{
|
||||
return implPtr_->save(path);
|
||||
}
|
||||
|
||||
int HttpFile::saveAs(const std::string &fileName) const noexcept
|
||||
{
|
||||
return implPtr_->saveAs(fileName);
|
||||
}
|
||||
|
||||
size_t HttpFile::fileLength() const noexcept
|
||||
{
|
||||
return implPtr_->fileLength();
|
||||
}
|
||||
|
||||
drogon::ContentType HttpFile::getContentType() const noexcept
|
||||
{
|
||||
return implPtr_->getContentType();
|
||||
}
|
||||
|
||||
const char *HttpFile::fileData() const noexcept
|
||||
{
|
||||
return implPtr_->fileData();
|
||||
}
|
||||
|
||||
std::string HttpFile::getMd5() const noexcept
|
||||
{
|
||||
return implPtr_->getMd5();
|
||||
}
|
||||
|
||||
const std::string &HttpFile::getContentTransferEncoding() const noexcept
|
||||
{
|
||||
return implPtr_->getContentTransferEncoding();
|
||||
}
|
||||
|
||||
HttpFile::HttpFile(std::shared_ptr<HttpFileImpl> &&implPtr) noexcept
|
||||
: implPtr_(std::move(implPtr))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &HttpFile::getItemName() const noexcept
|
||||
{
|
||||
return implPtr_->getItemName();
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpFileImpl.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpUtils.h"
|
||||
#include <drogon/HttpRequest.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpFileImpl
|
||||
{
|
||||
public:
|
||||
/// Return the file name;
|
||||
const std::string &getFileName() const noexcept
|
||||
{
|
||||
return fileName_;
|
||||
}
|
||||
|
||||
/// Set the file name, usually called by the MultiPartParser parser.
|
||||
void setFileName(const std::string &fileName) noexcept
|
||||
{
|
||||
fileName_ = fileName;
|
||||
}
|
||||
|
||||
void setFileName(std::string &&fileName) noexcept
|
||||
{
|
||||
fileName_ = std::move(fileName);
|
||||
}
|
||||
|
||||
/// Return the file extension;
|
||||
std::string_view getFileExtension() const noexcept
|
||||
{
|
||||
return drogon::getFileExtension(fileName_);
|
||||
}
|
||||
|
||||
/// Set the contents of the file, usually called by the MultiPartParser
|
||||
/// parser.
|
||||
void setFile(const char *data, size_t length) noexcept
|
||||
{
|
||||
fileContent_ = std::string_view{data, length};
|
||||
}
|
||||
|
||||
/// Save the file to the file system.
|
||||
/**
|
||||
* The folder saving the file is app().getUploadPath().
|
||||
* The full path is app().getUploadPath()+"/"+this->getFileName()
|
||||
*/
|
||||
int save() const noexcept;
|
||||
|
||||
/// Save the file to @param path
|
||||
/**
|
||||
* @param path if the parameter is prefixed with "/", "./" or "../", or is
|
||||
* "." or "..", the full path is path+"/"+this->getFileName(),
|
||||
* otherwise the file is saved as
|
||||
* app().getUploadPath()+"/"+path+"/"+this->getFileName()
|
||||
*/
|
||||
int save(const std::string &path) const noexcept;
|
||||
|
||||
/// Save the file to file system with a new name
|
||||
/**
|
||||
* @param fileName if the parameter isn't prefixed with "/", "./" or "../",
|
||||
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
|
||||
* is saved as the filename
|
||||
*/
|
||||
int saveAs(const std::string &fileName) const noexcept;
|
||||
|
||||
/// Return the file length.
|
||||
size_t fileLength() const noexcept
|
||||
{
|
||||
return fileContent_.length();
|
||||
}
|
||||
|
||||
const char *fileData() const noexcept
|
||||
{
|
||||
return fileContent_.data();
|
||||
}
|
||||
|
||||
const std::string_view &fileContent() const noexcept
|
||||
{
|
||||
return fileContent_;
|
||||
}
|
||||
|
||||
/// Return the name of the item in multiple parts.
|
||||
const std::string &getItemName() const noexcept
|
||||
{
|
||||
return itemName_;
|
||||
}
|
||||
|
||||
void setItemName(const std::string &itemName) noexcept
|
||||
{
|
||||
itemName_ = itemName;
|
||||
}
|
||||
|
||||
void setItemName(std::string &&itemName) noexcept
|
||||
{
|
||||
itemName_ = std::move(itemName);
|
||||
}
|
||||
|
||||
/// Return the type of file.
|
||||
FileType getFileType() const noexcept
|
||||
{
|
||||
auto ft = drogon::getFileType(contentType_);
|
||||
if ((ft != FT_UNKNOWN) && (ft != FT_CUSTOM))
|
||||
return ft;
|
||||
return parseFileType(getFileExtension());
|
||||
}
|
||||
|
||||
/// Return md5 hash of the file
|
||||
std::string getMd5() const noexcept;
|
||||
// Return sha1 hash of the file
|
||||
std::string getSha256() const noexcept;
|
||||
// Return sha512 hash of the file
|
||||
std::string getSha3() const noexcept;
|
||||
// int saveTo(const std::string &pathAndFileName) const;
|
||||
int saveTo(const std::filesystem::path &pathAndFileName) const noexcept;
|
||||
|
||||
void setRequest(const HttpRequestPtr &req) noexcept
|
||||
{
|
||||
requestPtr_ = req;
|
||||
}
|
||||
|
||||
drogon::ContentType getContentType() const noexcept
|
||||
{
|
||||
return contentType_;
|
||||
}
|
||||
|
||||
void setContentType(drogon::ContentType contentType) noexcept
|
||||
{
|
||||
contentType_ = contentType;
|
||||
}
|
||||
|
||||
void setContentTransferEncoding(
|
||||
const std::string &contentTransferEncoding) noexcept
|
||||
{
|
||||
transferEncoding_ = contentTransferEncoding;
|
||||
}
|
||||
|
||||
void setContentTransferEncoding(
|
||||
std::string &&contentTransferEncoding) noexcept
|
||||
{
|
||||
transferEncoding_ = std::move(contentTransferEncoding);
|
||||
}
|
||||
|
||||
const std::string &getContentTransferEncoding() const noexcept
|
||||
{
|
||||
return transferEncoding_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string fileName_;
|
||||
std::string itemName_;
|
||||
std::string transferEncoding_;
|
||||
std::string_view fileContent_;
|
||||
HttpRequestPtr requestPtr_;
|
||||
drogon::ContentType contentType_{drogon::CT_NONE};
|
||||
};
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
* HttpFileUploadRequest.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpFileUploadRequest.h"
|
||||
#include <drogon/UploadFile.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
HttpFileUploadRequest::HttpFileUploadRequest(
|
||||
const std::vector<UploadFile> &files)
|
||||
: HttpRequestImpl(nullptr),
|
||||
boundary_(utils::genRandomString(32)),
|
||||
files_(files)
|
||||
{
|
||||
setMethod(drogon::Post);
|
||||
setVersion(drogon::Version::kHttp11);
|
||||
setContentType("multipart/form-data; boundary=" + boundary_);
|
||||
contentType_ = CT_MULTIPART_FORM_DATA;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
*
|
||||
* HttpFileUploadRequest.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpRequestImpl.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpFileUploadRequest : public HttpRequestImpl
|
||||
{
|
||||
public:
|
||||
const std::string &boundary() const
|
||||
{
|
||||
return boundary_;
|
||||
}
|
||||
|
||||
const std::vector<UploadFile> &files() const
|
||||
{
|
||||
return files_;
|
||||
}
|
||||
|
||||
explicit HttpFileUploadRequest(const std::vector<UploadFile> &files);
|
||||
|
||||
private:
|
||||
std::string boundary_;
|
||||
std::vector<UploadFile> files_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
*
|
||||
* HttpMessageBody.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpMessageBody
|
||||
{
|
||||
public:
|
||||
enum class BodyType
|
||||
{
|
||||
kNone = 0,
|
||||
kString,
|
||||
kStringView
|
||||
};
|
||||
|
||||
BodyType bodyType()
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
virtual const char *data() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual char *data()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual size_t length() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual std::string_view getString() const = 0;
|
||||
|
||||
virtual void append(const char * /*buf*/, size_t /*len*/)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~HttpMessageBody()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
BodyType type_{BodyType::kNone};
|
||||
};
|
||||
|
||||
class HttpMessageStringBody : public HttpMessageBody
|
||||
{
|
||||
public:
|
||||
HttpMessageStringBody()
|
||||
{
|
||||
type_ = BodyType::kString;
|
||||
}
|
||||
|
||||
HttpMessageStringBody(const std::string &body) : body_(body)
|
||||
{
|
||||
type_ = BodyType::kString;
|
||||
}
|
||||
|
||||
HttpMessageStringBody(std::string &&body) : body_(std::move(body))
|
||||
{
|
||||
type_ = BodyType::kString;
|
||||
}
|
||||
|
||||
const char *data() const override
|
||||
{
|
||||
return body_.data();
|
||||
}
|
||||
|
||||
char *data() override
|
||||
{
|
||||
return const_cast<char *>(body_.data());
|
||||
}
|
||||
|
||||
size_t length() const override
|
||||
{
|
||||
return body_.length();
|
||||
}
|
||||
|
||||
std::string_view getString() const override
|
||||
{
|
||||
return std::string_view{body_.data(), body_.length()};
|
||||
}
|
||||
|
||||
void append(const char *buf, size_t len) override
|
||||
{
|
||||
body_.append(buf, len);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string body_;
|
||||
};
|
||||
|
||||
class HttpMessageStringViewBody : public HttpMessageBody
|
||||
{
|
||||
public:
|
||||
HttpMessageStringViewBody(const char *buf, size_t len) : body_(buf, len)
|
||||
{
|
||||
type_ = BodyType::kStringView;
|
||||
}
|
||||
|
||||
const char *data() const override
|
||||
{
|
||||
return body_.data();
|
||||
}
|
||||
|
||||
char *data() override
|
||||
{
|
||||
return const_cast<char *>(body_.data());
|
||||
}
|
||||
|
||||
size_t length() const override
|
||||
{
|
||||
return body_.length();
|
||||
}
|
||||
|
||||
std::string_view getString() const override
|
||||
{
|
||||
return body_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string_view body_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
+1116
File diff suppressed because it is too large
Load Diff
+750
@@ -0,0 +1,750 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpRequestImpl.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HttpUtils.h"
|
||||
#include "CacheFile.h"
|
||||
#include "impl_forwards.h"
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
#include <drogon/RequestStream.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/net/Certificate.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <future>
|
||||
#include <unordered_map>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
enum class StreamDecompressStatus
|
||||
{
|
||||
TooLarge,
|
||||
DecompressError,
|
||||
NotSupported,
|
||||
Ok
|
||||
};
|
||||
|
||||
enum class ReqStreamStatus
|
||||
{
|
||||
None = 0,
|
||||
Open = 1,
|
||||
Finish = 2,
|
||||
Error = 3
|
||||
};
|
||||
|
||||
class HttpRequestImpl : public HttpRequest
|
||||
{
|
||||
public:
|
||||
friend class HttpRequestParser;
|
||||
|
||||
explicit HttpRequestImpl(trantor::EventLoop *loop)
|
||||
: creationDate_(trantor::Date::now()), loop_(loop)
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
method_ = Invalid;
|
||||
previousMethod_ = Invalid;
|
||||
version_ = Version::kUnknown;
|
||||
flagForParsingJson_ = false;
|
||||
headers_.clear();
|
||||
cookies_.clear();
|
||||
contentLengthHeaderValue_.reset();
|
||||
realContentLength_ = 0;
|
||||
flagForParsingParameters_ = false;
|
||||
path_.clear();
|
||||
originalPath_.clear();
|
||||
pathEncode_ = true;
|
||||
matchedPathPattern_ = "";
|
||||
query_.clear();
|
||||
parameters_.clear();
|
||||
jsonPtr_.reset();
|
||||
sessionPtr_.reset();
|
||||
attributesPtr_.reset();
|
||||
cacheFilePtr_.reset();
|
||||
expectPtr_.reset();
|
||||
content_.clear();
|
||||
contentType_ = CT_TEXT_PLAIN;
|
||||
flagForParsingContentType_ = false;
|
||||
contentTypeString_.clear();
|
||||
keepAlive_ = true;
|
||||
jsonParsingErrorPtr_.reset();
|
||||
peerCertificate_.reset();
|
||||
routingParams_.clear();
|
||||
// stream
|
||||
streamStatus_ = ReqStreamStatus::None;
|
||||
streamReaderPtr_.reset();
|
||||
streamFinishCb_ = nullptr;
|
||||
streamExceptionPtr_ = nullptr;
|
||||
startProcessing_ = false;
|
||||
connPtr_.reset();
|
||||
}
|
||||
|
||||
trantor::EventLoop *getLoop()
|
||||
{
|
||||
return loop_;
|
||||
}
|
||||
|
||||
void setVersion(Version v)
|
||||
{
|
||||
version_ = v;
|
||||
if (version_ == Version::kHttp10)
|
||||
{
|
||||
keepAlive_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
Version version() const override
|
||||
{
|
||||
return version_;
|
||||
}
|
||||
|
||||
const char *versionString() const override;
|
||||
|
||||
bool setMethod(const char *start, const char *end);
|
||||
|
||||
void setSecure(bool secure)
|
||||
{
|
||||
isOnSecureConnection_ = secure;
|
||||
}
|
||||
|
||||
void setMethod(const HttpMethod method) override
|
||||
{
|
||||
previousMethod_ = method_;
|
||||
method_ = method;
|
||||
return;
|
||||
}
|
||||
|
||||
HttpMethod method() const override
|
||||
{
|
||||
return method_;
|
||||
}
|
||||
|
||||
bool isHead() const override
|
||||
{
|
||||
return (method_ == HttpMethod::Head) ||
|
||||
((method_ == HttpMethod::Get) &&
|
||||
(previousMethod_ == HttpMethod::Head));
|
||||
}
|
||||
|
||||
const char *methodString() const override;
|
||||
|
||||
void setPath(const char *start, const char *end)
|
||||
{
|
||||
if (utils::needUrlDecoding(start, end))
|
||||
{
|
||||
originalPath_.append(start, end);
|
||||
path_ = utils::urlDecode(start, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
path_.append(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> &getRoutingParameters() const override
|
||||
{
|
||||
return routingParams_;
|
||||
}
|
||||
|
||||
void setRoutingParameters(std::vector<std::string> &¶ms) override
|
||||
{
|
||||
routingParams_ = std::move(params);
|
||||
}
|
||||
|
||||
void setPath(const std::string &path) override
|
||||
{
|
||||
path_ = path;
|
||||
}
|
||||
|
||||
void setPath(std::string &&path) override
|
||||
{
|
||||
path_ = std::move(path);
|
||||
}
|
||||
|
||||
void setPathEncode(bool pathEncode) override
|
||||
{
|
||||
pathEncode_ = pathEncode;
|
||||
}
|
||||
|
||||
const SafeStringMap<std::string> ¶meters() const override
|
||||
{
|
||||
parseParametersOnce();
|
||||
return parameters_;
|
||||
}
|
||||
|
||||
const std::string &getParameter(const std::string &key) const override
|
||||
{
|
||||
static const std::string defaultVal;
|
||||
parseParametersOnce();
|
||||
auto iter = parameters_.find(key);
|
||||
if (iter != parameters_.end())
|
||||
return iter->second;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
const std::string &path() const override
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
const std::string &getOriginalPath() const override
|
||||
{
|
||||
return originalPath_.empty() ? path_ : originalPath_;
|
||||
}
|
||||
|
||||
void setQuery(const char *start, const char *end)
|
||||
{
|
||||
query_.assign(start, end);
|
||||
}
|
||||
|
||||
void setQuery(const std::string &query)
|
||||
{
|
||||
query_ = query;
|
||||
}
|
||||
|
||||
std::string_view bodyView() const
|
||||
{
|
||||
if (isStreamMode())
|
||||
{
|
||||
return emptySv_;
|
||||
}
|
||||
if (cacheFilePtr_)
|
||||
{
|
||||
return cacheFilePtr_->getStringView();
|
||||
}
|
||||
return content_;
|
||||
}
|
||||
|
||||
const char *bodyData() const override
|
||||
{
|
||||
if (isStreamMode())
|
||||
{
|
||||
return emptySv_.data();
|
||||
}
|
||||
if (cacheFilePtr_)
|
||||
{
|
||||
return cacheFilePtr_->getStringView().data();
|
||||
}
|
||||
return content_.data();
|
||||
}
|
||||
|
||||
size_t bodyLength() const override
|
||||
{
|
||||
if (isStreamMode())
|
||||
{
|
||||
return emptySv_.length();
|
||||
}
|
||||
if (cacheFilePtr_)
|
||||
{
|
||||
return cacheFilePtr_->getStringView().length();
|
||||
}
|
||||
return content_.length();
|
||||
}
|
||||
|
||||
void appendToBody(const char *data, size_t length);
|
||||
|
||||
void reserveBodySize(size_t length);
|
||||
|
||||
std::string_view queryView() const
|
||||
{
|
||||
return query_;
|
||||
}
|
||||
|
||||
std::string_view contentView() const
|
||||
{
|
||||
if (isStreamMode())
|
||||
{
|
||||
return emptySv_;
|
||||
}
|
||||
if (cacheFilePtr_)
|
||||
return cacheFilePtr_->getStringView();
|
||||
return content_;
|
||||
}
|
||||
|
||||
const std::string &query() const override
|
||||
{
|
||||
return query_;
|
||||
}
|
||||
|
||||
const trantor::InetAddress &peerAddr() const override
|
||||
{
|
||||
return peer_;
|
||||
}
|
||||
|
||||
const trantor::InetAddress &localAddr() const override
|
||||
{
|
||||
return local_;
|
||||
}
|
||||
|
||||
const trantor::Date &creationDate() const override
|
||||
{
|
||||
return creationDate_;
|
||||
}
|
||||
|
||||
const trantor::CertificatePtr &peerCertificate() const override
|
||||
{
|
||||
return peerCertificate_;
|
||||
}
|
||||
|
||||
void setCreationDate(const trantor::Date &date)
|
||||
{
|
||||
creationDate_ = date;
|
||||
}
|
||||
|
||||
void setPeerAddr(const trantor::InetAddress &peer)
|
||||
{
|
||||
peer_ = peer;
|
||||
}
|
||||
|
||||
void setLocalAddr(const trantor::InetAddress &local)
|
||||
{
|
||||
local_ = local;
|
||||
}
|
||||
|
||||
void setPeerCertificate(const trantor::CertificatePtr &cert)
|
||||
{
|
||||
peerCertificate_ = cert;
|
||||
}
|
||||
|
||||
void setConnectionPtr(const std::shared_ptr<trantor::TcpConnection> &ptr)
|
||||
{
|
||||
connPtr_ = ptr;
|
||||
}
|
||||
|
||||
void addHeader(const char *start, const char *colon, const char *end);
|
||||
|
||||
void removeHeader(std::string key) override
|
||||
{
|
||||
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
|
||||
return tolower(c);
|
||||
});
|
||||
removeHeaderBy(key);
|
||||
}
|
||||
|
||||
void removeHeaderBy(const std::string &lowerKey)
|
||||
{
|
||||
headers_.erase(lowerKey);
|
||||
}
|
||||
|
||||
const std::string &getHeader(std::string field) const override
|
||||
{
|
||||
std::transform(field.begin(),
|
||||
field.end(),
|
||||
field.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
return getHeaderBy(field);
|
||||
}
|
||||
|
||||
const std::string &getHeaderBy(const std::string &lowerField) const
|
||||
{
|
||||
static const std::string defaultVal;
|
||||
auto it = headers_.find(lowerField);
|
||||
if (it != headers_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
const std::string &getCookie(const std::string &field) const override
|
||||
{
|
||||
static const std::string defaultVal;
|
||||
auto it = cookies_.find(field);
|
||||
if (it != cookies_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
const SafeStringMap<std::string> &headers() const override
|
||||
{
|
||||
return headers_;
|
||||
}
|
||||
|
||||
const SafeStringMap<std::string> &cookies() const override
|
||||
{
|
||||
return cookies_;
|
||||
}
|
||||
|
||||
std::optional<size_t> getContentLengthHeaderValue() const
|
||||
{
|
||||
return contentLengthHeaderValue_;
|
||||
}
|
||||
|
||||
size_t realContentLength() const override
|
||||
{
|
||||
return realContentLength_;
|
||||
}
|
||||
|
||||
void setParameter(const std::string &key, const std::string &value) override
|
||||
{
|
||||
flagForParsingParameters_ = true;
|
||||
parameters_[key] = value;
|
||||
}
|
||||
|
||||
const std::string &getContent() const
|
||||
{
|
||||
return content_;
|
||||
}
|
||||
|
||||
void swap(HttpRequestImpl &that) noexcept;
|
||||
|
||||
void setContent(const std::string &content)
|
||||
{
|
||||
content_ = content;
|
||||
}
|
||||
|
||||
void setBody(const std::string &body) override
|
||||
{
|
||||
content_ = body;
|
||||
}
|
||||
|
||||
void setBody(std::string &&body) override
|
||||
{
|
||||
content_ = std::move(body);
|
||||
}
|
||||
|
||||
void addHeader(std::string field, const std::string &value) override
|
||||
{
|
||||
transform(field.begin(),
|
||||
field.end(),
|
||||
field.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
headers_[std::move(field)] = value;
|
||||
}
|
||||
|
||||
void addHeader(std::string field, std::string &&value) override
|
||||
{
|
||||
transform(field.begin(),
|
||||
field.end(),
|
||||
field.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
headers_[std::move(field)] = std::move(value);
|
||||
}
|
||||
|
||||
void addCookie(std::string key, std::string value) override
|
||||
{
|
||||
cookies_[std::move(key)] = std::move(value);
|
||||
}
|
||||
|
||||
void setPassThrough(bool flag) override
|
||||
{
|
||||
passThrough_ = flag;
|
||||
}
|
||||
|
||||
bool passThrough() const
|
||||
{
|
||||
return passThrough_;
|
||||
}
|
||||
|
||||
void appendToBuffer(trantor::MsgBuffer *output) const;
|
||||
|
||||
const SessionPtr &session() const override
|
||||
{
|
||||
return sessionPtr_;
|
||||
}
|
||||
|
||||
void setSession(const SessionPtr &session)
|
||||
{
|
||||
sessionPtr_ = session;
|
||||
}
|
||||
|
||||
const AttributesPtr &attributes() const override
|
||||
{
|
||||
if (!attributesPtr_)
|
||||
{
|
||||
attributesPtr_ = std::make_shared<Attributes>();
|
||||
}
|
||||
return attributesPtr_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Json::Value> &jsonObject() const override
|
||||
{
|
||||
// Not multi-thread safe but good, because we basically call this
|
||||
// function in a single thread
|
||||
if (!flagForParsingJson_)
|
||||
{
|
||||
flagForParsingJson_ = true;
|
||||
parseJson();
|
||||
}
|
||||
return jsonPtr_;
|
||||
}
|
||||
|
||||
void setCustomContentTypeString(const std::string &type) override
|
||||
{
|
||||
contentType_ = CT_NONE;
|
||||
flagForParsingContentType_ = true;
|
||||
bool haveHeader = type.find("content-type: ") == 0;
|
||||
bool haveCRLF = type.rfind("\r\n") == type.size() - 2;
|
||||
|
||||
size_t endOffset = 0;
|
||||
if (haveHeader)
|
||||
endOffset += 14;
|
||||
if (haveCRLF)
|
||||
endOffset += 2;
|
||||
contentTypeString_ = std::string(type.begin() + (haveHeader ? 14 : 0),
|
||||
type.end() - endOffset);
|
||||
}
|
||||
|
||||
void setContentTypeCode(const ContentType type) override
|
||||
{
|
||||
contentType_ = type;
|
||||
flagForParsingContentType_ = true;
|
||||
auto &typeStr = contentTypeToMime(type);
|
||||
setContentType(std::string(typeStr.data(), typeStr.length()));
|
||||
}
|
||||
|
||||
void setContentTypeString(const char *typeString,
|
||||
size_t typeStringLength) override;
|
||||
|
||||
// void setContentTypeCodeAndCharacterSet(ContentType type, const
|
||||
// std::string &charSet = "utf-8") override
|
||||
// {
|
||||
// contentType_ = type;
|
||||
// setContentType(webContentTypeAndCharsetToString(type, charSet));
|
||||
// }
|
||||
|
||||
ContentType contentType() const override
|
||||
{
|
||||
parseContentTypeAndString();
|
||||
return contentType_;
|
||||
}
|
||||
|
||||
const char *matchedPathPatternData() const override
|
||||
{
|
||||
return matchedPathPattern_.data();
|
||||
}
|
||||
|
||||
size_t matchedPathPatternLength() const override
|
||||
{
|
||||
return matchedPathPattern_.length();
|
||||
}
|
||||
|
||||
void setMatchedPathPattern(const std::string &pathPattern)
|
||||
{
|
||||
matchedPathPattern_ = pathPattern;
|
||||
}
|
||||
|
||||
const std::string &expect() const
|
||||
{
|
||||
static const std::string none{""};
|
||||
if (expectPtr_)
|
||||
return *expectPtr_;
|
||||
return none;
|
||||
}
|
||||
|
||||
bool keepAlive() const
|
||||
{
|
||||
return keepAlive_;
|
||||
}
|
||||
|
||||
bool connected() const noexcept override
|
||||
{
|
||||
if (auto conn = connPtr_.lock())
|
||||
{
|
||||
return conn->connected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
|
||||
const noexcept override
|
||||
{
|
||||
return connPtr_;
|
||||
}
|
||||
|
||||
bool isOnSecureConnection() const noexcept override
|
||||
{
|
||||
return isOnSecureConnection_;
|
||||
}
|
||||
|
||||
const std::string &getJsonError() const override
|
||||
{
|
||||
static const std::string none{""};
|
||||
if (jsonParsingErrorPtr_)
|
||||
return *jsonParsingErrorPtr_;
|
||||
return none;
|
||||
}
|
||||
|
||||
StreamDecompressStatus decompressBody();
|
||||
|
||||
// Stream mode api
|
||||
ReqStreamStatus streamStatus() const
|
||||
{
|
||||
return streamStatus_;
|
||||
}
|
||||
|
||||
bool isStreamMode() const
|
||||
{
|
||||
return streamStatus_ > ReqStreamStatus::None;
|
||||
}
|
||||
|
||||
void streamStart();
|
||||
void streamFinish();
|
||||
void streamError(std::exception_ptr ex);
|
||||
|
||||
void setStreamReader(RequestStreamReaderPtr reader);
|
||||
void waitForStreamFinish(std::function<void()> &&cb);
|
||||
void quitStreamMode();
|
||||
|
||||
void startProcessing()
|
||||
{
|
||||
startProcessing_ = true;
|
||||
}
|
||||
|
||||
bool isProcessingStarted() const
|
||||
{
|
||||
return startProcessing_;
|
||||
}
|
||||
|
||||
~HttpRequestImpl() override;
|
||||
|
||||
protected:
|
||||
friend class HttpRequest;
|
||||
|
||||
void setContentType(const std::string &contentType)
|
||||
{
|
||||
contentTypeString_ = contentType;
|
||||
}
|
||||
|
||||
void setContentType(std::string &&contentType)
|
||||
{
|
||||
contentTypeString_ = std::move(contentType);
|
||||
}
|
||||
|
||||
void parseContentTypeAndString() const
|
||||
{
|
||||
if (!flagForParsingContentType_)
|
||||
{
|
||||
flagForParsingContentType_ = true;
|
||||
auto &contentTypeString = getHeaderBy("content-type");
|
||||
if (contentTypeString == "")
|
||||
{
|
||||
contentType_ = CT_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos = contentTypeString.find(';');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
contentType_ = parseContentType(
|
||||
std::string_view(contentTypeString.data(), pos));
|
||||
}
|
||||
else
|
||||
{
|
||||
contentType_ =
|
||||
parseContentType(std::string_view(contentTypeString));
|
||||
}
|
||||
|
||||
if (contentType_ == CT_NONE)
|
||||
contentType_ = CT_CUSTOM;
|
||||
contentTypeString_ = contentTypeString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void parseParameters() const;
|
||||
|
||||
void parseParametersOnce() const
|
||||
{
|
||||
// Not multi-thread safe but good, because we basically call this
|
||||
// function in a single thread
|
||||
if (!flagForParsingParameters_)
|
||||
{
|
||||
flagForParsingParameters_ = true;
|
||||
parseParameters();
|
||||
}
|
||||
}
|
||||
|
||||
void createTmpFile();
|
||||
void parseJson() const;
|
||||
#ifdef USE_BROTLI
|
||||
StreamDecompressStatus decompressBodyBrotli() noexcept;
|
||||
#endif
|
||||
StreamDecompressStatus decompressBodyGzip() noexcept;
|
||||
|
||||
static constexpr const std::string_view emptySv_{""};
|
||||
|
||||
mutable bool flagForParsingParameters_{false};
|
||||
mutable bool flagForParsingJson_{false};
|
||||
HttpMethod method_{Invalid};
|
||||
HttpMethod previousMethod_{Invalid};
|
||||
Version version_{Version::kUnknown};
|
||||
std::string path_;
|
||||
/// Contains the encoded `path_` if and only if `path_` is set in encoded
|
||||
/// form. If path is in a normal form and needed no decoding, then this will
|
||||
/// be empty, as we do not need to store a duplicate.
|
||||
std::string originalPath_;
|
||||
bool pathEncode_{true};
|
||||
std::string_view matchedPathPattern_{""};
|
||||
std::string query_;
|
||||
SafeStringMap<std::string> headers_;
|
||||
SafeStringMap<std::string> cookies_;
|
||||
std::optional<size_t> contentLengthHeaderValue_;
|
||||
size_t realContentLength_{0};
|
||||
mutable SafeStringMap<std::string> parameters_;
|
||||
mutable std::shared_ptr<Json::Value> jsonPtr_;
|
||||
SessionPtr sessionPtr_;
|
||||
mutable AttributesPtr attributesPtr_;
|
||||
trantor::InetAddress peer_;
|
||||
trantor::InetAddress local_;
|
||||
trantor::Date creationDate_;
|
||||
trantor::CertificatePtr peerCertificate_;
|
||||
std::unique_ptr<CacheFile> cacheFilePtr_;
|
||||
mutable std::unique_ptr<std::string> jsonParsingErrorPtr_;
|
||||
std::unique_ptr<std::string> expectPtr_;
|
||||
bool keepAlive_{true};
|
||||
bool isOnSecureConnection_{false};
|
||||
bool passThrough_{false};
|
||||
std::vector<std::string> routingParams_;
|
||||
|
||||
ReqStreamStatus streamStatus_{ReqStreamStatus::None};
|
||||
std::function<void()> streamFinishCb_;
|
||||
RequestStreamReaderPtr streamReaderPtr_;
|
||||
std::exception_ptr streamExceptionPtr_;
|
||||
bool startProcessing_{false};
|
||||
std::weak_ptr<trantor::TcpConnection> connPtr_;
|
||||
|
||||
protected:
|
||||
std::string content_;
|
||||
trantor::EventLoop *loop_;
|
||||
mutable ContentType contentType_{CT_TEXT_PLAIN};
|
||||
mutable bool flagForParsingContentType_{false};
|
||||
mutable std::string contentTypeString_;
|
||||
};
|
||||
|
||||
using HttpRequestImplPtr = std::shared_ptr<HttpRequestImpl>;
|
||||
|
||||
inline void swap(HttpRequestImpl &one, HttpRequestImpl &two) noexcept
|
||||
{
|
||||
one.swap(two);
|
||||
}
|
||||
|
||||
} // namespace drogon
|
||||
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
*
|
||||
* HttpRequestParser.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpRequestParser.h"
|
||||
#include <drogon/HttpTypes.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <iostream>
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include "HttpUtils.h"
|
||||
|
||||
using namespace trantor;
|
||||
using namespace drogon;
|
||||
|
||||
static constexpr size_t CRLF_LEN = 2; // strlen("crlf")
|
||||
static constexpr size_t METHOD_MAX_LEN = 7; // strlen("OPTIONS")
|
||||
static constexpr size_t TRUNK_LEN_MAX_LEN = 16; // 0xFFFFFFFF,FFFFFFFF
|
||||
|
||||
HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr)
|
||||
: status_(HttpRequestParseStatus::kExpectMethod),
|
||||
loop_(connPtr->getLoop()),
|
||||
conn_(connPtr)
|
||||
{
|
||||
}
|
||||
|
||||
bool HttpRequestParser::processRequestLine(const char *begin, const char *end)
|
||||
{
|
||||
bool succeed = false;
|
||||
const char *start = begin;
|
||||
const char *space = std::find(start, end, ' ');
|
||||
if (space != end)
|
||||
{
|
||||
const char *slash = std::find(start, space, '/');
|
||||
if (slash != start && slash + 1 < space && *(slash + 1) == '/')
|
||||
{
|
||||
// scheme precedents
|
||||
slash = std::find(slash + 2, space, '/');
|
||||
}
|
||||
const char *question = std::find(slash, space, '?');
|
||||
if (slash != space)
|
||||
{
|
||||
request_->setPath(slash, question);
|
||||
}
|
||||
else
|
||||
{
|
||||
// An empty abs_path is equivalent to an abs_path of "/"
|
||||
request_->setPath("/");
|
||||
}
|
||||
if (question != space)
|
||||
{
|
||||
request_->setQuery(question + 1, space);
|
||||
}
|
||||
start = space + 1;
|
||||
succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1.");
|
||||
if (succeed)
|
||||
{
|
||||
if (*(end - 1) == '1')
|
||||
{
|
||||
request_->setVersion(Version::kHttp11);
|
||||
}
|
||||
else if (*(end - 1) == '0')
|
||||
{
|
||||
request_->setVersion(Version::kHttp10);
|
||||
}
|
||||
else
|
||||
{
|
||||
succeed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return succeed;
|
||||
}
|
||||
|
||||
HttpRequestImplPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr)
|
||||
{
|
||||
return std::shared_ptr<HttpRequestImpl>(
|
||||
ptr, [weakPtr = weak_from_this()](HttpRequestImpl *p) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (thisPtr)
|
||||
{
|
||||
if (thisPtr->loop_->isInLoopThread())
|
||||
{
|
||||
p->reset();
|
||||
thisPtr->requestsPool_.emplace_back(
|
||||
thisPtr->makeRequestForPool(p));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &loop = thisPtr->loop_;
|
||||
loop->queueInLoop([thisPtr = std::move(thisPtr), p]() {
|
||||
p->reset();
|
||||
thisPtr->requestsPool_.emplace_back(
|
||||
thisPtr->makeRequestForPool(p));
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
delete p;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HttpRequestParser::reset()
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
remainContentLength_ = 0;
|
||||
status_ = HttpRequestParseStatus::kExpectMethod;
|
||||
if (requestsPool_.empty())
|
||||
{
|
||||
request_ = makeRequestForPool(new HttpRequestImpl(loop_));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto req = std::move(requestsPool_.back());
|
||||
requestsPool_.pop_back();
|
||||
request_ = std::move(req);
|
||||
request_->setCreationDate(trantor::Date::now());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return -HttpStatusCode if encounters any http errors in request
|
||||
* @return return -1 if encounters any other errors in request
|
||||
* @return return 0 if request is not ready
|
||||
* @return return 1 if request is ready
|
||||
* @return return 2 if request is ready and entering stream mode
|
||||
* @return return 3 if request header is ready and entering stream mode
|
||||
*/
|
||||
int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch (status_)
|
||||
{
|
||||
case (HttpRequestParseStatus::kExpectMethod):
|
||||
{
|
||||
auto *space = std::find(buf->peek(),
|
||||
(const char *)buf->beginWrite(),
|
||||
' ');
|
||||
// no space in buffer
|
||||
if (space == buf->beginWrite())
|
||||
{
|
||||
if (buf->readableBytes() > METHOD_MAX_LEN)
|
||||
{
|
||||
return -k400BadRequest;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// try read method
|
||||
if (!request_->setMethod(buf->peek(), space))
|
||||
{
|
||||
return -k405MethodNotAllowed;
|
||||
}
|
||||
status_ = HttpRequestParseStatus::kExpectRequestLine;
|
||||
buf->retrieveUntil(space + 1);
|
||||
continue;
|
||||
}
|
||||
case HttpRequestParseStatus::kExpectRequestLine:
|
||||
{
|
||||
const char *crlf = buf->findCRLF();
|
||||
if (!crlf)
|
||||
{
|
||||
if (buf->readableBytes() >= 64 * 1024)
|
||||
{
|
||||
/// The limit for request line is 64K bytes. response
|
||||
/// k414RequestURITooLarge
|
||||
/// TODO: Make this configurable?
|
||||
return -k414RequestURITooLarge;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!processRequestLine(buf->peek(), crlf))
|
||||
{
|
||||
// error
|
||||
return -k400BadRequest;
|
||||
}
|
||||
buf->retrieveUntil(crlf + CRLF_LEN);
|
||||
status_ = HttpRequestParseStatus::kExpectHeaders;
|
||||
continue;
|
||||
}
|
||||
case HttpRequestParseStatus::kExpectHeaders:
|
||||
{
|
||||
const char *crlf = buf->findCRLF();
|
||||
if (!crlf)
|
||||
{
|
||||
if (buf->readableBytes() >= 64 * 1024)
|
||||
{
|
||||
/// The limit for every request header is 64K bytes;
|
||||
/// TODO: Make this configurable?
|
||||
return -k400BadRequest;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *colon = std::find(buf->peek(), crlf, ':');
|
||||
// found colon
|
||||
if (colon != crlf)
|
||||
{
|
||||
request_->addHeader(buf->peek(), colon, crlf);
|
||||
buf->retrieveUntil(crlf + CRLF_LEN);
|
||||
continue;
|
||||
}
|
||||
buf->retrieveUntil(crlf + CRLF_LEN);
|
||||
// end of headers
|
||||
|
||||
// We might want a kProcessHeaders status for code readability
|
||||
// and maintainability.
|
||||
|
||||
// process header information
|
||||
auto &len = request_->getHeaderBy("content-length");
|
||||
if (!len.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
remainContentLength_ =
|
||||
static_cast<size_t>(std::stoull(len));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return -k400BadRequest;
|
||||
}
|
||||
request_->contentLengthHeaderValue_ = remainContentLength_;
|
||||
if (remainContentLength_ == 0)
|
||||
{
|
||||
// content-length = 0, request is over.
|
||||
status_ = HttpRequestParseStatus::kGotAll;
|
||||
}
|
||||
else
|
||||
{
|
||||
status_ = HttpRequestParseStatus::kExpectBody;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string &encode =
|
||||
request_->getHeaderBy("transfer-encoding");
|
||||
if (encode.empty())
|
||||
{
|
||||
// no content-length and no transfer-encoding,
|
||||
// request is over.
|
||||
status_ = HttpRequestParseStatus::kGotAll;
|
||||
}
|
||||
else if (encode == "chunked")
|
||||
{
|
||||
status_ = HttpRequestParseStatus::kExpectChunkLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -k501NotImplemented;
|
||||
}
|
||||
}
|
||||
|
||||
// Check max body size
|
||||
if (remainContentLength_ >
|
||||
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
|
||||
{
|
||||
return -k413RequestEntityTooLarge;
|
||||
}
|
||||
|
||||
// Check expect:100-continue
|
||||
auto &expect = request_->expect();
|
||||
if (expect == "100-continue" &&
|
||||
request_->getVersion() >= Version::kHttp11)
|
||||
{
|
||||
if (remainContentLength_ == 0)
|
||||
{
|
||||
// error
|
||||
return -k400BadRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
// rfc2616-8.2.3
|
||||
// TODO: consider adding an AOP for expect header
|
||||
auto connPtr = conn_.lock(); // ugly
|
||||
if (!connPtr)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k100Continue);
|
||||
auto httpString =
|
||||
static_cast<HttpResponseImpl *>(resp.get())
|
||||
->renderToBuffer();
|
||||
connPtr->send(std::move(*httpString));
|
||||
}
|
||||
}
|
||||
else if (!expect.empty())
|
||||
{
|
||||
LOG_WARN << "417ExpectationFailed for \"" << expect << "\"";
|
||||
return -k417ExpectationFailed;
|
||||
}
|
||||
|
||||
assert(status_ == HttpRequestParseStatus::kGotAll ||
|
||||
status_ == HttpRequestParseStatus::kExpectBody ||
|
||||
status_ == HttpRequestParseStatus::kExpectChunkLen);
|
||||
|
||||
if (app().isRequestStreamEnabled())
|
||||
{
|
||||
request_->streamStart();
|
||||
if (status_ == HttpRequestParseStatus::kGotAll)
|
||||
{
|
||||
++requestsCounter_;
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve space for full body in non-stream mode.
|
||||
// For stream mode requests that match a non-stream handler,
|
||||
// we will reserve full body before waitForStreamFinish().
|
||||
if (remainContentLength_)
|
||||
{
|
||||
request_->reserveBodySize(remainContentLength_);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case HttpRequestParseStatus::kExpectBody:
|
||||
{
|
||||
size_t bytesToConsume =
|
||||
remainContentLength_ <= buf->readableBytes()
|
||||
? remainContentLength_
|
||||
: buf->readableBytes();
|
||||
if (bytesToConsume)
|
||||
{
|
||||
request_->appendToBody(buf->peek(), bytesToConsume);
|
||||
buf->retrieve(bytesToConsume);
|
||||
remainContentLength_ -= bytesToConsume;
|
||||
}
|
||||
|
||||
if (remainContentLength_ == 0)
|
||||
{
|
||||
status_ = HttpRequestParseStatus::kGotAll;
|
||||
++requestsCounter_;
|
||||
return 1;
|
||||
}
|
||||
// readableBytes() == 0, function should return.
|
||||
return 0;
|
||||
}
|
||||
case HttpRequestParseStatus::kExpectChunkLen:
|
||||
{
|
||||
const char *crlf = buf->findCRLF();
|
||||
if (!crlf)
|
||||
{
|
||||
if (buf->readableBytes() > TRUNK_LEN_MAX_LEN + CRLF_LEN)
|
||||
{
|
||||
return -k400BadRequest;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// chunk length line
|
||||
std::string len(buf->peek(), crlf - buf->peek());
|
||||
char *end;
|
||||
currentChunkLength_ = strtol(len.c_str(), &end, 16);
|
||||
if (currentChunkLength_ != 0)
|
||||
{
|
||||
if (currentChunkLength_ + remainContentLength_ >
|
||||
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
|
||||
{
|
||||
return -k413RequestEntityTooLarge;
|
||||
}
|
||||
status_ = HttpRequestParseStatus::kExpectChunkBody;
|
||||
}
|
||||
else
|
||||
{
|
||||
status_ = HttpRequestParseStatus::kExpectLastEmptyChunk;
|
||||
}
|
||||
buf->retrieveUntil(crlf + CRLF_LEN);
|
||||
continue;
|
||||
}
|
||||
case HttpRequestParseStatus::kExpectChunkBody:
|
||||
{
|
||||
if (buf->readableBytes() < (currentChunkLength_ + CRLF_LEN))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (*(buf->peek() + currentChunkLength_) != '\r' ||
|
||||
*(buf->peek() + currentChunkLength_ + 1) != '\n')
|
||||
{
|
||||
// error!
|
||||
return -k400BadRequest;
|
||||
}
|
||||
request_->appendToBody(buf->peek(), currentChunkLength_);
|
||||
buf->retrieve(currentChunkLength_ + CRLF_LEN);
|
||||
remainContentLength_ += currentChunkLength_;
|
||||
currentChunkLength_ = 0;
|
||||
status_ = HttpRequestParseStatus::kExpectChunkLen;
|
||||
continue;
|
||||
}
|
||||
case HttpRequestParseStatus::kExpectLastEmptyChunk:
|
||||
{
|
||||
// last empty chunk
|
||||
if (buf->readableBytes() < CRLF_LEN)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (*(buf->peek()) != '\r' || *(buf->peek() + 1) != '\n')
|
||||
{
|
||||
// error!
|
||||
return -k400BadRequest;
|
||||
}
|
||||
buf->retrieve(CRLF_LEN);
|
||||
|
||||
if (!request_->isStreamMode())
|
||||
{
|
||||
// Previously we only have non-stream mode, drogon handled
|
||||
// chunked encoding internally, and give user a regular
|
||||
// request as if it has a content-length header.
|
||||
//
|
||||
// We have to keep compatibility for non-stream mode.
|
||||
//
|
||||
// But I don't think it's a good implementation. We should
|
||||
// instead add an api to access real content-length of
|
||||
// requests.
|
||||
// Now HttpRequest::realContentLength() is added, and user
|
||||
// should no longer parse content-length header by
|
||||
// themselves.
|
||||
//
|
||||
// NOTE: request forward behavior may be infected in stream
|
||||
// mode, we should check it out.
|
||||
request_->addHeader("content-length",
|
||||
std::to_string(
|
||||
request_->realContentLength()));
|
||||
request_->removeHeaderBy("transfer-encoding");
|
||||
}
|
||||
status_ = HttpRequestParseStatus::kGotAll;
|
||||
++requestsCounter_;
|
||||
return 1;
|
||||
}
|
||||
case HttpRequestParseStatus::kGotAll:
|
||||
{
|
||||
++requestsCounter_;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // won't reach here, just to make compiler happy
|
||||
}
|
||||
|
||||
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req,
|
||||
bool isHeadMethod)
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
requestPipelining_.push_back({req, {nullptr, isHeadMethod}});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return returns true if the the response is the first in pipeline
|
||||
*/
|
||||
bool HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req,
|
||||
HttpResponsePtr resp)
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
for (size_t i = 0; i != requestPipelining_.size(); ++i)
|
||||
{
|
||||
if (requestPipelining_[i].first == req)
|
||||
{
|
||||
requestPipelining_[i].second.first = std::move(resp);
|
||||
return i == 0;
|
||||
}
|
||||
}
|
||||
assert(false); // Should always find a match
|
||||
return false;
|
||||
}
|
||||
|
||||
void HttpRequestParser::popReadyResponses(
|
||||
std::vector<std::pair<HttpResponsePtr, bool>> &buffer)
|
||||
{
|
||||
while (!requestPipelining_.empty() &&
|
||||
requestPipelining_.front().second.first)
|
||||
{
|
||||
buffer.push_back(std::move(requestPipelining_.front().second));
|
||||
requestPipelining_.pop_front();
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
*
|
||||
* HttpRequestParser.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <drogon/HttpTypes.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "impl_forwards.h"
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpRequestParser : public trantor::NonCopyable,
|
||||
public std::enable_shared_from_this<HttpRequestParser>
|
||||
{
|
||||
public:
|
||||
enum class HttpRequestParseStatus
|
||||
{
|
||||
kExpectMethod,
|
||||
kExpectRequestLine,
|
||||
kExpectHeaders,
|
||||
kExpectBody,
|
||||
kExpectChunkLen,
|
||||
kExpectChunkBody,
|
||||
kExpectLastEmptyChunk,
|
||||
kGotAll,
|
||||
};
|
||||
|
||||
explicit HttpRequestParser(const trantor::TcpConnectionPtr &connPtr);
|
||||
|
||||
int parseRequest(trantor::MsgBuffer *buf);
|
||||
|
||||
bool gotAll() const
|
||||
{
|
||||
return status_ == HttpRequestParseStatus::kGotAll;
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
const HttpRequestImplPtr &requestImpl() const
|
||||
{
|
||||
return request_;
|
||||
}
|
||||
|
||||
bool firstReq()
|
||||
{
|
||||
if (firstRequest_)
|
||||
{
|
||||
firstRequest_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const WebSocketConnectionImplPtr &webSocketConn() const
|
||||
{
|
||||
return websockConnPtr_;
|
||||
}
|
||||
|
||||
void setWebsockConnection(const WebSocketConnectionImplPtr &conn)
|
||||
{
|
||||
websockConnPtr_ = conn;
|
||||
}
|
||||
|
||||
// to support request pipelining(rfc2616-8.1.2.2)
|
||||
void pushRequestToPipelining(const HttpRequestPtr &, bool isHeadMethod);
|
||||
bool pushResponseToPipelining(const HttpRequestPtr &, HttpResponsePtr);
|
||||
void popReadyResponses(std::vector<std::pair<HttpResponsePtr, bool>> &);
|
||||
|
||||
size_t numberOfRequestsInPipelining() const
|
||||
{
|
||||
return requestPipelining_.size();
|
||||
}
|
||||
|
||||
bool emptyPipelining()
|
||||
{
|
||||
return requestPipelining_.empty();
|
||||
}
|
||||
|
||||
bool isStop() const
|
||||
{
|
||||
return stopWorking_;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
stopWorking_ = true;
|
||||
}
|
||||
|
||||
size_t numberOfRequestsParsed() const
|
||||
{
|
||||
return requestsCounter_;
|
||||
}
|
||||
|
||||
trantor::MsgBuffer &getBuffer()
|
||||
{
|
||||
return sendBuffer_;
|
||||
}
|
||||
|
||||
std::vector<std::pair<HttpResponsePtr, bool>> &getResponseBuffer()
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
if (!responseBuffer_)
|
||||
{
|
||||
responseBuffer_ =
|
||||
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>(
|
||||
new std::vector<std::pair<HttpResponsePtr, bool>>);
|
||||
}
|
||||
return *responseBuffer_;
|
||||
}
|
||||
|
||||
std::vector<HttpRequestImplPtr> &getRequestBuffer()
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
if (!requestBuffer_)
|
||||
{
|
||||
requestBuffer_ = std::unique_ptr<std::vector<HttpRequestImplPtr>>(
|
||||
new std::vector<HttpRequestImplPtr>);
|
||||
}
|
||||
return *requestBuffer_;
|
||||
}
|
||||
|
||||
private:
|
||||
HttpRequestImplPtr makeRequestForPool(HttpRequestImpl *p);
|
||||
bool processRequestLine(const char *begin, const char *end);
|
||||
HttpRequestParseStatus status_;
|
||||
trantor::EventLoop *loop_;
|
||||
HttpRequestImplPtr request_;
|
||||
bool firstRequest_{true};
|
||||
WebSocketConnectionImplPtr websockConnPtr_;
|
||||
std::deque<std::pair<HttpRequestPtr, std::pair<HttpResponsePtr, bool>>>
|
||||
requestPipelining_;
|
||||
size_t requestsCounter_{0};
|
||||
std::weak_ptr<trantor::TcpConnection> conn_;
|
||||
bool stopWorking_{false};
|
||||
trantor::MsgBuffer sendBuffer_;
|
||||
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>
|
||||
responseBuffer_;
|
||||
std::unique_ptr<std::vector<HttpRequestImplPtr>> requestBuffer_;
|
||||
std::vector<HttpRequestImplPtr> requestsPool_;
|
||||
size_t currentChunkLength_{0};
|
||||
size_t remainContentLength_{0};
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
+983
@@ -0,0 +1,983 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpResponseImpl.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpResponseImpl.h"
|
||||
#include "AOPAdvice.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "HttpUtils.h"
|
||||
#include <drogon/HttpViewData.h>
|
||||
#include <drogon/IOThreadStorage.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
using namespace trantor;
|
||||
using namespace drogon;
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::placeholders;
|
||||
#ifdef _WIN32
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
// "Fri, 23 Aug 2019 12:58:03 GMT" length = 29
|
||||
static const size_t httpFullDateStringLength = 29;
|
||||
|
||||
static inline HttpResponsePtr genHttpResponse(const std::string &viewName,
|
||||
const HttpViewData &data,
|
||||
const HttpRequestPtr &req)
|
||||
{
|
||||
auto templ = DrTemplateBase::newTemplate(viewName);
|
||||
if (templ)
|
||||
{
|
||||
auto res = HttpResponse::newHttpResponse();
|
||||
res->setBody(templ->genText(data));
|
||||
return res;
|
||||
}
|
||||
return drogon::HttpResponse::newNotFoundResponse(req);
|
||||
}
|
||||
} // namespace drogon
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpResponse()
|
||||
{
|
||||
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_TEXT_HTML);
|
||||
AopAdvice::instance().passResponseCreationAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpResponse(HttpStatusCode code,
|
||||
ContentType type)
|
||||
{
|
||||
auto res = std::make_shared<HttpResponseImpl>(code, type);
|
||||
AopAdvice::instance().passResponseCreationAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
|
||||
{
|
||||
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);
|
||||
res->setJsonObject(data);
|
||||
AopAdvice::instance().passResponseCreationAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data)
|
||||
{
|
||||
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);
|
||||
res->setJsonObject(std::move(data));
|
||||
AopAdvice::instance().passResponseCreationAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
const char *HttpResponseImpl::versionString() const
|
||||
{
|
||||
const char *result = "UNKNOWN";
|
||||
switch (version_)
|
||||
{
|
||||
case Version::kHttp10:
|
||||
result = "HTTP/1.0";
|
||||
break;
|
||||
|
||||
case Version::kHttp11:
|
||||
result = "HTTP/1.1";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HttpResponseImpl::generateBodyFromJson() const
|
||||
{
|
||||
if (!jsonPtr_ || flagForSerializingJson_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
flagForSerializingJson_ = true;
|
||||
static std::once_flag once;
|
||||
static Json::StreamWriterBuilder builder;
|
||||
std::call_once(once, []() {
|
||||
builder["commentStyle"] = "None";
|
||||
builder["indentation"] = "";
|
||||
if (!app().isUnicodeEscapingUsedInJson())
|
||||
{
|
||||
builder["emitUTF8"] = true;
|
||||
}
|
||||
auto &precision = app().getFloatPrecisionInJson();
|
||||
if (precision.first != 0)
|
||||
{
|
||||
builder["precision"] = precision.first;
|
||||
builder["precisionType"] = precision.second;
|
||||
}
|
||||
});
|
||||
bodyPtr_ = std::make_shared<HttpMessageStringBody>(
|
||||
writeString(builder, *jsonPtr_));
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newNotFoundResponse(const HttpRequestPtr &req)
|
||||
{
|
||||
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
|
||||
auto &resp = HttpAppFrameworkImpl::instance().getCustom404Page();
|
||||
if (resp)
|
||||
{
|
||||
if (loop && loop->index() < app().getThreadNum())
|
||||
{
|
||||
return resp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponsePtr{new HttpResponseImpl(
|
||||
*static_cast<HttpResponseImpl *>(resp.get()))};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpAppFrameworkImpl::instance().isUsingCustomErrorHandler())
|
||||
{
|
||||
return app().getCustomErrorHandler()(k404NotFound, req);
|
||||
}
|
||||
else if (loop && loop->index() < app().getThreadNum())
|
||||
{
|
||||
// If the current thread is an IO thread
|
||||
static std::once_flag threadOnce;
|
||||
static IOThreadStorage<HttpResponsePtr> thread404Pages;
|
||||
std::call_once(threadOnce, [req = req] {
|
||||
thread404Pages.init([req = req](drogon::HttpResponsePtr &resp,
|
||||
size_t /*index*/) {
|
||||
HttpViewData data;
|
||||
data.insert("version", drogon::getVersion());
|
||||
resp = HttpResponse::newHttpViewResponse("drogon::NotFound",
|
||||
data);
|
||||
resp->setStatusCode(k404NotFound);
|
||||
resp->setExpiredTime(0);
|
||||
});
|
||||
});
|
||||
LOG_TRACE << "Use cached 404 response";
|
||||
return thread404Pages.getThreadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpViewData data;
|
||||
data.insert("version", drogon::getVersion());
|
||||
auto notFoundResp =
|
||||
HttpResponse::newHttpViewResponse("drogon::NotFound", data);
|
||||
notFoundResp->setStatusCode(k404NotFound);
|
||||
return notFoundResp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newRedirectionResponse(
|
||||
const std::string &location,
|
||||
HttpStatusCode status)
|
||||
{
|
||||
auto res = std::make_shared<HttpResponseImpl>();
|
||||
res->setStatusCode(status);
|
||||
res->redirect(location);
|
||||
AopAdvice::instance().passResponseCreationAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName,
|
||||
const HttpViewData &data,
|
||||
const HttpRequestPtr &req)
|
||||
{
|
||||
return genHttpResponse(viewName, data, req);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newFileResponse(
|
||||
const unsigned char *pBuffer,
|
||||
size_t bufferLength,
|
||||
const std::string &attachmentFileName,
|
||||
ContentType type,
|
||||
const std::string &typeString)
|
||||
{
|
||||
// Make Raw HttpResponse
|
||||
auto resp = std::make_shared<HttpResponseImpl>();
|
||||
|
||||
// Set response body and length
|
||||
resp->setBody(
|
||||
std::string(reinterpret_cast<const char *>(pBuffer), bufferLength));
|
||||
|
||||
// Set status of message
|
||||
resp->setStatusCode(k200OK);
|
||||
|
||||
// Check for type and assign proper content type in header
|
||||
if (!typeString.empty())
|
||||
{
|
||||
if (type == CT_NONE)
|
||||
type = parseContentType(typeString);
|
||||
if (type == CT_NONE)
|
||||
type = CT_APPLICATION_OCTET_STREAM; // XXX: Is this Ok?
|
||||
static_cast<HttpResponse *>(resp.get())
|
||||
->setContentTypeCodeAndCustomString(type,
|
||||
typeString.c_str(),
|
||||
typeString.size());
|
||||
}
|
||||
else if (type != CT_NONE)
|
||||
{
|
||||
resp->setContentTypeCode(type);
|
||||
}
|
||||
else if (!attachmentFileName.empty())
|
||||
{
|
||||
resp->setContentTypeCode(drogon::getContentType(attachmentFileName));
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setContentTypeCode(
|
||||
CT_APPLICATION_OCTET_STREAM); // default content-type for file;
|
||||
}
|
||||
|
||||
// Add additional header values
|
||||
if (!attachmentFileName.empty())
|
||||
{
|
||||
resp->addHeader("Content-Disposition",
|
||||
"attachment; filename=" + attachmentFileName);
|
||||
}
|
||||
|
||||
// Finalize and return response
|
||||
AopAdvice::instance().passResponseCreationAdvices(resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newFileResponse(
|
||||
const std::string &fullPath,
|
||||
const std::string &attachmentFileName,
|
||||
ContentType type,
|
||||
const std::string &typeString,
|
||||
const HttpRequestPtr &req)
|
||||
{
|
||||
return newFileResponse(
|
||||
fullPath, 0, 0, false, attachmentFileName, type, typeString, req);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newFileResponse(
|
||||
const std::string &fullPath,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
bool setContentRange,
|
||||
const std::string &attachmentFileName,
|
||||
ContentType type,
|
||||
const std::string &typeString,
|
||||
const HttpRequestPtr &req)
|
||||
{
|
||||
std::ifstream infile(utils::toNativePath(fullPath), std::ifstream::binary);
|
||||
LOG_TRACE << "send http file:" << fullPath << " offset " << offset
|
||||
<< " length " << length;
|
||||
if (!infile)
|
||||
{
|
||||
auto resp = HttpResponse::newNotFoundResponse(req);
|
||||
return resp;
|
||||
}
|
||||
auto resp = std::make_shared<HttpResponseImpl>();
|
||||
std::streambuf *pbuf = infile.rdbuf();
|
||||
size_t filesize =
|
||||
static_cast<size_t>(pbuf->pubseekoff(0, std::ifstream::end));
|
||||
if (offset > filesize || length > filesize || // in case of overflow
|
||||
offset + length > filesize)
|
||||
{
|
||||
resp->setStatusCode(k416RequestedRangeNotSatisfiable);
|
||||
if (setContentRange)
|
||||
{
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "bytes */%zu", filesize);
|
||||
resp->addHeader("Content-Range", std::string(buf));
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
if (length == 0)
|
||||
{
|
||||
length = filesize - offset;
|
||||
}
|
||||
pbuf->pubseekoff(offset, std::ifstream::beg); // rewind
|
||||
|
||||
if (HttpAppFrameworkImpl::instance().useSendfile() && length > 1024 * 200)
|
||||
// TODO : Is 200k an appropriate value? Or set it to be configurable
|
||||
{
|
||||
// The advantages of sendfile() can only be reflected in sending large
|
||||
// files.
|
||||
resp->setSendfile(fullPath);
|
||||
// Must set length with the right value! Content-Length header relies on
|
||||
// this value.
|
||||
resp->setSendfileRange(offset, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string str;
|
||||
str.resize(length);
|
||||
pbuf->sgetn(&str[0], length);
|
||||
resp->setBody(std::move(str));
|
||||
resp->setSendfileRange(offset, length);
|
||||
}
|
||||
|
||||
// Set correct status code
|
||||
if (length < filesize)
|
||||
{
|
||||
resp->setStatusCode(k206PartialContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setStatusCode(k200OK);
|
||||
}
|
||||
|
||||
// Infer content type
|
||||
if (type == CT_NONE)
|
||||
{
|
||||
if (!typeString.empty())
|
||||
{
|
||||
auto r = static_cast<HttpResponse *>(resp.get());
|
||||
if (type == CT_NONE)
|
||||
type = parseContentType(typeString);
|
||||
if (type == CT_NONE)
|
||||
type = CT_CUSTOM; // XXX: Is this Ok?
|
||||
r->setContentTypeCodeAndCustomString(type, typeString);
|
||||
}
|
||||
else if (!attachmentFileName.empty())
|
||||
{
|
||||
resp->setContentTypeCode(
|
||||
drogon::getContentType(attachmentFileName));
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setContentTypeCode(drogon::getContentType(fullPath));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeString.empty())
|
||||
resp->setContentTypeCode(type);
|
||||
else
|
||||
{
|
||||
auto r = static_cast<HttpResponse *>(resp.get());
|
||||
if (type == CT_NONE)
|
||||
type = parseContentType(typeString);
|
||||
if (type == CT_NONE)
|
||||
type = CT_CUSTOM; // XXX: Is this Ok?
|
||||
r->setContentTypeCodeAndCustomString(type, typeString);
|
||||
}
|
||||
}
|
||||
|
||||
// Set headers
|
||||
if (!attachmentFileName.empty())
|
||||
{
|
||||
resp->addHeader("Content-Disposition",
|
||||
"attachment; filename=" + attachmentFileName);
|
||||
}
|
||||
if (setContentRange && length > 0)
|
||||
{
|
||||
char buf[128];
|
||||
snprintf(buf,
|
||||
sizeof(buf),
|
||||
"bytes %zu-%zu/%zu",
|
||||
offset,
|
||||
offset + length - 1,
|
||||
filesize);
|
||||
resp->addHeader("Content-Range", std::string(buf));
|
||||
}
|
||||
AopAdvice::instance().passResponseCreationAdvices(resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newStreamResponse(
|
||||
const std::function<std::size_t(char *, std::size_t)> &callback,
|
||||
const std::string &attachmentFileName,
|
||||
ContentType type,
|
||||
const std::string &typeString,
|
||||
const HttpRequestPtr &req)
|
||||
{
|
||||
LOG_TRACE << "send stream as "s
|
||||
<< (attachmentFileName.empty() ? "raw data"s
|
||||
: "file: "s + attachmentFileName);
|
||||
if (!callback)
|
||||
{
|
||||
auto resp = HttpResponse::newNotFoundResponse();
|
||||
return resp;
|
||||
}
|
||||
auto resp = std::make_shared<HttpResponseImpl>();
|
||||
resp->setStreamCallback(callback);
|
||||
resp->setStatusCode(k200OK);
|
||||
|
||||
// Infer content type
|
||||
if (type == CT_NONE)
|
||||
{
|
||||
if (!typeString.empty())
|
||||
{
|
||||
auto r = static_cast<HttpResponse *>(resp.get());
|
||||
if (type == CT_NONE)
|
||||
type = parseContentType(typeString);
|
||||
if (type == CT_NONE)
|
||||
type = CT_CUSTOM; // XXX: Is this Ok?
|
||||
r->setContentTypeCodeAndCustomString(type, typeString);
|
||||
}
|
||||
else if (!attachmentFileName.empty())
|
||||
{
|
||||
resp->setContentTypeCode(
|
||||
drogon::getContentType(attachmentFileName));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeString.empty())
|
||||
resp->setContentTypeCode(type);
|
||||
else
|
||||
{
|
||||
auto r = static_cast<HttpResponse *>(resp.get());
|
||||
if (type == CT_NONE)
|
||||
type = parseContentType(typeString);
|
||||
if (type == CT_NONE)
|
||||
type = CT_CUSTOM; // XXX: Is this Ok?
|
||||
r->setContentTypeCodeAndCustomString(type, typeString);
|
||||
}
|
||||
}
|
||||
|
||||
// Set headers
|
||||
if (!attachmentFileName.empty())
|
||||
{
|
||||
resp->addHeader("Content-Disposition",
|
||||
"attachment; filename=" + attachmentFileName);
|
||||
}
|
||||
AopAdvice::instance().passResponseCreationAdvices(resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newAsyncStreamResponse(
|
||||
const std::function<void(ResponseStreamPtr)> &callback,
|
||||
bool disableKickoffTimeout)
|
||||
{
|
||||
if (!callback)
|
||||
{
|
||||
auto resp = HttpResponse::newNotFoundResponse();
|
||||
return resp;
|
||||
}
|
||||
auto resp = std::make_shared<HttpResponseImpl>();
|
||||
resp->setAsyncStreamCallback(callback, disableKickoffTimeout);
|
||||
resp->setStatusCode(k200OK);
|
||||
AopAdvice::instance().passResponseCreationAdvices(resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
void HttpResponseImpl::makeHeaderString(trantor::MsgBuffer &buffer)
|
||||
{
|
||||
buffer.ensureWritableBytes(128);
|
||||
int len{0};
|
||||
if (version_ == Version::kHttp11)
|
||||
{
|
||||
if (customStatusCode_ >= 0)
|
||||
{
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
"HTTP/1.1 %d ",
|
||||
customStatusCode_);
|
||||
}
|
||||
else
|
||||
{
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
"HTTP/1.1 %d ",
|
||||
statusCode_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (customStatusCode_ >= 0)
|
||||
{
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
"HTTP/1.0 %d ",
|
||||
customStatusCode_);
|
||||
}
|
||||
else
|
||||
{
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
"HTTP/1.0 %d ",
|
||||
statusCode_);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.hasWritten(len);
|
||||
|
||||
if (!statusMessage_.empty())
|
||||
buffer.append(statusMessage_.data(), statusMessage_.length());
|
||||
buffer.append("\r\n");
|
||||
generateBodyFromJson();
|
||||
if (!passThrough_)
|
||||
{
|
||||
buffer.ensureWritableBytes(64);
|
||||
if (!contentLengthIsAllowed())
|
||||
{
|
||||
len = 0;
|
||||
if ((bodyPtr_ && bodyPtr_->length() > 0) ||
|
||||
!sendfileName_.empty() || streamCallback_ ||
|
||||
asyncStreamCallback_)
|
||||
{
|
||||
LOG_ERROR << "The body should be empty when the content-length "
|
||||
"is not allowed!";
|
||||
}
|
||||
}
|
||||
else if (streamCallback_ || asyncStreamCallback_)
|
||||
{
|
||||
// When the headers are created, it is time to set the transfer
|
||||
// encoding to chunked if the contents size is not specified
|
||||
if (!ifCloseConnection() &&
|
||||
headers_.find("content-length") == headers_.end())
|
||||
{
|
||||
LOG_DEBUG << "send stream with transfer-encoding chunked";
|
||||
headers_["transfer-encoding"] = "chunked";
|
||||
}
|
||||
len = 0;
|
||||
}
|
||||
else if (sendfileName_.empty())
|
||||
{
|
||||
auto bodyLength = bodyPtr_ ? bodyPtr_->length() : 0;
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
contentLengthFormatString<decltype(bodyLength)>(),
|
||||
bodyLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto bodyLength = sendfileRange_.second;
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
contentLengthFormatString<decltype(bodyLength)>(),
|
||||
bodyLength);
|
||||
}
|
||||
buffer.hasWritten(len);
|
||||
if (headers_.find("connection") == headers_.end())
|
||||
{
|
||||
if (closeConnection_)
|
||||
{
|
||||
buffer.append("connection: close\r\n");
|
||||
}
|
||||
else if (version_ == Version::kHttp10)
|
||||
{
|
||||
buffer.append("connection: Keep-Alive\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!contentTypeString_.empty())
|
||||
{
|
||||
buffer.append("content-type: ");
|
||||
buffer.append(contentTypeString_);
|
||||
buffer.append("\r\n");
|
||||
}
|
||||
if (HttpAppFrameworkImpl::instance().sendServerHeader())
|
||||
{
|
||||
buffer.append(
|
||||
HttpAppFrameworkImpl::instance().getServerHeaderString());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = headers_.begin(); it != headers_.end(); ++it)
|
||||
{
|
||||
buffer.append(it->first);
|
||||
buffer.append(": ");
|
||||
buffer.append(it->second);
|
||||
buffer.append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponseImpl::renderToBuffer(trantor::MsgBuffer &buffer)
|
||||
{
|
||||
if (expriedTime_ >= 0)
|
||||
{
|
||||
auto strPtr = renderToBuffer();
|
||||
buffer.append(strPtr->peek(), strPtr->readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fullHeaderString_)
|
||||
{
|
||||
makeHeaderString(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.append(*fullHeaderString_);
|
||||
}
|
||||
|
||||
// output cookies
|
||||
if (!cookies_.empty())
|
||||
{
|
||||
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
||||
{
|
||||
buffer.append(it->second.cookieString());
|
||||
}
|
||||
}
|
||||
|
||||
// output Date header
|
||||
if (!passThrough_ &&
|
||||
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
||||
{
|
||||
buffer.append("date: ");
|
||||
buffer.append(utils::getHttpFullDateStr(trantor::Date::date()));
|
||||
buffer.append("\r\n\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.append("\r\n");
|
||||
}
|
||||
if (bodyPtr_ && contentLengthIsAllowed())
|
||||
buffer.append(bodyPtr_->data(), bodyPtr_->length());
|
||||
}
|
||||
|
||||
std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::renderToBuffer()
|
||||
{
|
||||
if (expriedTime_ >= 0)
|
||||
{
|
||||
if (!passThrough_ &&
|
||||
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
||||
{
|
||||
if (datePos_ != static_cast<size_t>(-1))
|
||||
{
|
||||
auto now = trantor::Date::now();
|
||||
bool isDateChanged =
|
||||
((now.microSecondsSinceEpoch() /
|
||||
trantor::Date::MICRO_SECONDS_PER_SEC) != httpStringDate_);
|
||||
assert(httpString_);
|
||||
if (isDateChanged)
|
||||
{
|
||||
httpStringDate_ = now.microSecondsSinceEpoch() /
|
||||
trantor::Date::MICRO_SECONDS_PER_SEC;
|
||||
auto newDate = utils::getHttpFullDate(now);
|
||||
|
||||
httpString_ =
|
||||
std::make_shared<trantor::MsgBuffer>(*httpString_);
|
||||
memcpy((void *)&(*httpString_)[datePos_],
|
||||
newDate,
|
||||
httpFullDateStringLength);
|
||||
return httpString_;
|
||||
}
|
||||
|
||||
return httpString_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (httpString_)
|
||||
return httpString_;
|
||||
}
|
||||
}
|
||||
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
|
||||
if (!fullHeaderString_)
|
||||
{
|
||||
makeHeaderString(*httpString);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpString->append(*fullHeaderString_);
|
||||
}
|
||||
|
||||
// output cookies
|
||||
if (!cookies_.empty())
|
||||
{
|
||||
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
||||
{
|
||||
httpString->append(it->second.cookieString());
|
||||
}
|
||||
}
|
||||
|
||||
// output Date header
|
||||
if (!passThrough_ &&
|
||||
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
||||
{
|
||||
httpString->append("date: ");
|
||||
auto datePos = httpString->readableBytes();
|
||||
httpString->append(utils::getHttpFullDateStr(trantor::Date::date()));
|
||||
httpString->append("\r\n\r\n");
|
||||
datePos_ = datePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
httpString->append("\r\n");
|
||||
}
|
||||
|
||||
LOG_TRACE << "response(no body):"
|
||||
<< std::string_view{httpString->peek(),
|
||||
httpString->readableBytes()};
|
||||
if (bodyPtr_)
|
||||
httpString->append(bodyPtr_->data(), bodyPtr_->length());
|
||||
if (expriedTime_ >= 0)
|
||||
{
|
||||
httpString_ = httpString;
|
||||
}
|
||||
return httpString;
|
||||
}
|
||||
|
||||
std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::
|
||||
renderHeaderForHeadMethod()
|
||||
{
|
||||
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
|
||||
if (!fullHeaderString_)
|
||||
{
|
||||
makeHeaderString(*httpString);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpString->append(*fullHeaderString_);
|
||||
}
|
||||
|
||||
// output cookies
|
||||
if (!cookies_.empty())
|
||||
{
|
||||
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
||||
{
|
||||
httpString->append(it->second.cookieString());
|
||||
}
|
||||
}
|
||||
|
||||
// output Date header
|
||||
if (!passThrough_ &&
|
||||
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
||||
{
|
||||
httpString->append("date: ");
|
||||
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
|
||||
httpFullDateStringLength);
|
||||
httpString->append("\r\n\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
httpString->append("\r\n");
|
||||
}
|
||||
|
||||
return httpString;
|
||||
}
|
||||
|
||||
void HttpResponseImpl::addHeader(const char *start,
|
||||
const char *colon,
|
||||
const char *end)
|
||||
{
|
||||
fullHeaderString_.reset();
|
||||
std::string field(start, colon);
|
||||
transform(field.begin(), field.end(), field.begin(), [](unsigned char c) {
|
||||
return tolower(c);
|
||||
});
|
||||
++colon;
|
||||
while (colon < end && isspace(static_cast<unsigned char>(*colon)))
|
||||
{
|
||||
++colon;
|
||||
}
|
||||
std::string value(colon, end);
|
||||
while (!value.empty() &&
|
||||
isspace(static_cast<unsigned char>(value[value.size() - 1])))
|
||||
{
|
||||
value.resize(value.size() - 1);
|
||||
}
|
||||
|
||||
if (field == "set-cookie")
|
||||
{
|
||||
// LOG_INFO<<"cookies!!!:"<<value;
|
||||
auto values = utils::splitString(value, ";");
|
||||
Cookie cookie;
|
||||
cookie.setHttpOnly(false);
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
std::string &coo = values[i];
|
||||
std::string cookie_name;
|
||||
std::string cookie_value;
|
||||
auto epos = coo.find('=');
|
||||
if (epos != std::string::npos)
|
||||
{
|
||||
cookie_name = coo.substr(0, epos);
|
||||
std::string::size_type cpos = 0;
|
||||
while (cpos < cookie_name.length() &&
|
||||
isspace(static_cast<unsigned char>(cookie_name[cpos])))
|
||||
++cpos;
|
||||
cookie_name = cookie_name.substr(cpos);
|
||||
++epos;
|
||||
while (epos < coo.length() &&
|
||||
isspace(static_cast<unsigned char>(coo[epos])))
|
||||
++epos;
|
||||
cookie_value = coo.substr(epos);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string::size_type cpos = 0;
|
||||
while (cpos < coo.length() &&
|
||||
isspace(static_cast<unsigned char>(coo[cpos])))
|
||||
++cpos;
|
||||
cookie_name = coo.substr(cpos);
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
cookie.setKey(cookie_name);
|
||||
cookie.setValue(cookie_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::transform(cookie_name.begin(),
|
||||
cookie_name.end(),
|
||||
cookie_name.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
if (cookie_name == "path")
|
||||
{
|
||||
cookie.setPath(cookie_value);
|
||||
}
|
||||
else if (cookie_name == "domain")
|
||||
{
|
||||
cookie.setDomain(cookie_value);
|
||||
}
|
||||
else if (cookie_name == "expires")
|
||||
{
|
||||
cookie.setExpiresDate(utils::getHttpDate(cookie_value));
|
||||
}
|
||||
else if (cookie_name == "secure")
|
||||
{
|
||||
cookie.setSecure(true);
|
||||
}
|
||||
else if (cookie_name == "httponly")
|
||||
{
|
||||
cookie.setHttpOnly(true);
|
||||
}
|
||||
else if (cookie_name == "samesite")
|
||||
{
|
||||
cookie.setSameSite(
|
||||
cookie.convertString2SameSite(cookie_value));
|
||||
}
|
||||
else if (cookie_name == "max-age")
|
||||
{
|
||||
cookie.setMaxAge(std::stoi(cookie_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cookie.key().empty())
|
||||
{
|
||||
cookies_[cookie.key()] = std::move(cookie);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headers_[std::move(field)] = std::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponseImpl::swap(HttpResponseImpl &that) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
headers_.swap(that.headers_);
|
||||
cookies_.swap(that.cookies_);
|
||||
swap(statusCode_, that.statusCode_);
|
||||
swap(version_, that.version_);
|
||||
swap(statusMessage_, that.statusMessage_);
|
||||
swap(closeConnection_, that.closeConnection_);
|
||||
bodyPtr_.swap(that.bodyPtr_);
|
||||
swap(contentType_, that.contentType_);
|
||||
swap(flagForParsingContentType_, that.flagForParsingContentType_);
|
||||
swap(flagForParsingJson_, that.flagForParsingJson_);
|
||||
swap(sendfileName_, that.sendfileName_);
|
||||
swap(streamCallback_, that.streamCallback_);
|
||||
swap(asyncStreamCallback_, that.asyncStreamCallback_);
|
||||
jsonPtr_.swap(that.jsonPtr_);
|
||||
fullHeaderString_.swap(that.fullHeaderString_);
|
||||
httpString_.swap(that.httpString_);
|
||||
swap(datePos_, that.datePos_);
|
||||
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
|
||||
}
|
||||
|
||||
void HttpResponseImpl::clear()
|
||||
{
|
||||
statusCode_ = kUnknown;
|
||||
version_ = Version::kHttp11;
|
||||
statusMessage_ = std::string_view{};
|
||||
fullHeaderString_.reset();
|
||||
jsonParsingErrorPtr_.reset();
|
||||
sendfileName_.clear();
|
||||
if (streamCallback_)
|
||||
{
|
||||
LOG_TRACE << "Cleanup HttpResponse stream callback";
|
||||
streamCallback_(nullptr, 0); // callback internal cleanup
|
||||
streamCallback_ = {};
|
||||
}
|
||||
if (asyncStreamCallback_)
|
||||
{
|
||||
// asyncStreamCallback_(nullptr);
|
||||
asyncStreamCallback_ = {};
|
||||
}
|
||||
headers_.clear();
|
||||
cookies_.clear();
|
||||
bodyPtr_.reset();
|
||||
jsonPtr_.reset();
|
||||
expriedTime_ = -1;
|
||||
datePos_ = std::string::npos;
|
||||
flagForParsingContentType_ = false;
|
||||
flagForParsingJson_ = false;
|
||||
}
|
||||
|
||||
void HttpResponseImpl::parseJson() const
|
||||
{
|
||||
static std::once_flag once;
|
||||
static Json::CharReaderBuilder builder;
|
||||
std::call_once(once, []() {
|
||||
builder["collectComments"] = false;
|
||||
builder["stackLimit"] =
|
||||
static_cast<Json::UInt>(drogon::app().getJsonParserStackLimit());
|
||||
});
|
||||
JSONCPP_STRING errs;
|
||||
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
|
||||
if (bodyPtr_)
|
||||
{
|
||||
jsonPtr_ = std::make_shared<Json::Value>();
|
||||
if (!reader->parse(bodyPtr_->data(),
|
||||
bodyPtr_->data() + bodyPtr_->length(),
|
||||
jsonPtr_.get(),
|
||||
&errs))
|
||||
{
|
||||
LOG_ERROR << errs;
|
||||
LOG_ERROR << "body: " << bodyPtr_->getString();
|
||||
jsonPtr_.reset();
|
||||
jsonParsingErrorPtr_ =
|
||||
std::make_shared<std::string>(std::move(errs));
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonParsingErrorPtr_.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonPtr_.reset();
|
||||
jsonParsingErrorPtr_ =
|
||||
std::make_shared<std::string>("empty response body");
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpResponseImpl::shouldBeCompressed() const
|
||||
{
|
||||
if (streamCallback_ || asyncStreamCallback_ || !sendfileName_.empty() ||
|
||||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
|
||||
getBody().length() < 1024 ||
|
||||
!(getHeaderBy("content-encoding").empty()) || !contentLengthIsAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpResponseImpl::setContentTypeString(const char *typeString,
|
||||
size_t typeStringLength)
|
||||
{
|
||||
std::string sv(typeString, typeStringLength);
|
||||
auto contentType = parseContentType(sv);
|
||||
if (contentType == CT_NONE)
|
||||
contentType = CT_CUSTOM;
|
||||
contentType_ = contentType;
|
||||
contentTypeString_ = std::string(sv);
|
||||
flagForParsingContentType_ = true;
|
||||
}
|
||||
+559
@@ -0,0 +1,559 @@
|
||||
/**
|
||||
*
|
||||
* @file HttpResponseImpl.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "HttpUtils.h"
|
||||
#include "HttpMessageBody.h"
|
||||
#include <drogon/exports.h>
|
||||
#include <drogon/HttpResponse.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Date.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class DROGON_EXPORT HttpResponseImpl : public HttpResponse
|
||||
{
|
||||
friend class HttpResponseParser;
|
||||
|
||||
public:
|
||||
HttpResponseImpl() : creationDate_(trantor::Date::now())
|
||||
{
|
||||
}
|
||||
|
||||
HttpResponseImpl(HttpStatusCode code, ContentType type)
|
||||
: statusCode_(code),
|
||||
statusMessage_(statusCodeToString(code)),
|
||||
creationDate_(trantor::Date::now()),
|
||||
contentType_(type),
|
||||
flagForParsingContentType_(true),
|
||||
contentTypeString_(contentTypeToMime(type))
|
||||
{
|
||||
}
|
||||
|
||||
void setPassThrough(bool flag) override
|
||||
{
|
||||
passThrough_ = flag;
|
||||
}
|
||||
|
||||
HttpStatusCode statusCode() const override
|
||||
{
|
||||
return statusCode_;
|
||||
}
|
||||
|
||||
const trantor::Date &creationDate() const override
|
||||
{
|
||||
return creationDate_;
|
||||
}
|
||||
|
||||
void setStatusCode(HttpStatusCode code) override
|
||||
{
|
||||
statusCode_ = code;
|
||||
setStatusMessage(statusCodeToString(code));
|
||||
}
|
||||
|
||||
void setVersion(const Version v) override
|
||||
{
|
||||
version_ = v;
|
||||
if (version_ == Version::kHttp10)
|
||||
{
|
||||
closeConnection_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
Version version() const override
|
||||
{
|
||||
return version_;
|
||||
}
|
||||
|
||||
const char *versionString() const override;
|
||||
|
||||
void setCloseConnection(bool on) override
|
||||
{
|
||||
closeConnection_ = on;
|
||||
}
|
||||
|
||||
bool ifCloseConnection() const override
|
||||
{
|
||||
return closeConnection_;
|
||||
}
|
||||
|
||||
void setContentTypeCode(ContentType type) override
|
||||
{
|
||||
contentType_ = type;
|
||||
auto ct = contentTypeToMime(type);
|
||||
contentTypeString_ = std::string(ct.data(), ct.size());
|
||||
flagForParsingContentType_ = true;
|
||||
}
|
||||
|
||||
// void setContentTypeCodeAndCharacterSet(ContentType type, const
|
||||
// std::string &charSet = "utf-8") override
|
||||
// {
|
||||
// contentType_ = type;
|
||||
// setContentType(webContentTypeAndCharsetToString(type, charSet));
|
||||
// }
|
||||
|
||||
ContentType contentType() const override
|
||||
{
|
||||
parseContentTypeAndString();
|
||||
return contentType_;
|
||||
}
|
||||
|
||||
const std::string &getHeader(std::string key) const override
|
||||
{
|
||||
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
|
||||
return tolower(c);
|
||||
});
|
||||
return getHeaderBy(key);
|
||||
}
|
||||
|
||||
void removeHeader(std::string key) override
|
||||
{
|
||||
transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
|
||||
return tolower(c);
|
||||
});
|
||||
removeHeaderBy(key);
|
||||
}
|
||||
|
||||
const SafeStringMap<std::string> &headers() const override
|
||||
{
|
||||
return headers_;
|
||||
}
|
||||
|
||||
const std::string &getHeaderBy(const std::string &lowerKey) const
|
||||
{
|
||||
static const std::string defaultVal;
|
||||
auto iter = headers_.find(lowerKey);
|
||||
if (iter == headers_.end())
|
||||
{
|
||||
return defaultVal;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void removeHeaderBy(const std::string &lowerKey)
|
||||
{
|
||||
fullHeaderString_.reset();
|
||||
headers_.erase(lowerKey);
|
||||
}
|
||||
|
||||
void addHeader(std::string field, const std::string &value) override
|
||||
{
|
||||
fullHeaderString_.reset();
|
||||
transform(field.begin(),
|
||||
field.end(),
|
||||
field.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
headers_[std::move(field)] = value;
|
||||
}
|
||||
|
||||
void addHeader(std::string field, std::string &&value) override
|
||||
{
|
||||
fullHeaderString_.reset();
|
||||
transform(field.begin(),
|
||||
field.end(),
|
||||
field.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
headers_[std::move(field)] = std::move(value);
|
||||
}
|
||||
|
||||
void addHeader(const char *start, const char *colon, const char *end);
|
||||
|
||||
void addCookie(const std::string &key, const std::string &value) override
|
||||
{
|
||||
cookies_[key] = Cookie(key, value);
|
||||
}
|
||||
|
||||
void addCookie(const Cookie &cookie) override
|
||||
{
|
||||
cookies_[cookie.key()] = cookie;
|
||||
}
|
||||
|
||||
void addCookie(Cookie &&cookie) override
|
||||
{
|
||||
cookies_[cookie.key()] = std::move(cookie);
|
||||
}
|
||||
|
||||
const Cookie &getCookie(const std::string &key) const override
|
||||
{
|
||||
static const Cookie defaultCookie;
|
||||
auto it = cookies_.find(key);
|
||||
if (it != cookies_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return defaultCookie;
|
||||
}
|
||||
|
||||
const SafeStringMap<Cookie> &cookies() const override
|
||||
{
|
||||
return cookies_;
|
||||
}
|
||||
|
||||
void removeCookie(const std::string &key) override
|
||||
{
|
||||
cookies_.erase(key);
|
||||
}
|
||||
|
||||
void setBody(const std::string &body) override
|
||||
{
|
||||
bodyPtr_ = std::make_shared<HttpMessageStringBody>(body);
|
||||
if (passThrough_)
|
||||
{
|
||||
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
||||
}
|
||||
}
|
||||
|
||||
void setBody(std::string &&body) override
|
||||
{
|
||||
bodyPtr_ = std::make_shared<HttpMessageStringBody>(std::move(body));
|
||||
if (passThrough_)
|
||||
{
|
||||
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
||||
}
|
||||
}
|
||||
|
||||
void redirect(const std::string &url)
|
||||
{
|
||||
headers_["location"] = url;
|
||||
}
|
||||
|
||||
std::shared_ptr<trantor::MsgBuffer> renderToBuffer();
|
||||
void renderToBuffer(trantor::MsgBuffer &buffer);
|
||||
std::shared_ptr<trantor::MsgBuffer> renderHeaderForHeadMethod();
|
||||
void clear() override;
|
||||
|
||||
void setExpiredTime(ssize_t expiredTime) override
|
||||
{
|
||||
expriedTime_ = expiredTime;
|
||||
datePos_ = std::string::npos;
|
||||
if (expriedTime_ < 0 && version_ == Version::kHttp10)
|
||||
{
|
||||
fullHeaderString_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t expiredTime() const override
|
||||
{
|
||||
return expriedTime_;
|
||||
}
|
||||
|
||||
const char *getBodyData() const override
|
||||
{
|
||||
if (!flagForSerializingJson_ && jsonPtr_)
|
||||
{
|
||||
generateBodyFromJson();
|
||||
}
|
||||
else if (!bodyPtr_)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return bodyPtr_->data();
|
||||
}
|
||||
|
||||
size_t getBodyLength() const override
|
||||
{
|
||||
if (bodyPtr_)
|
||||
return bodyPtr_->length();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void swap(HttpResponseImpl &that) noexcept;
|
||||
void parseJson() const;
|
||||
|
||||
const std::shared_ptr<Json::Value> &jsonObject() const override
|
||||
{
|
||||
// Not multi-thread safe but good, because we basically call this
|
||||
// function in a single thread
|
||||
if (!flagForParsingJson_)
|
||||
{
|
||||
flagForParsingJson_ = true;
|
||||
parseJson();
|
||||
}
|
||||
return jsonPtr_;
|
||||
}
|
||||
|
||||
const std::string &getJsonError() const override
|
||||
{
|
||||
static const std::string none;
|
||||
if (jsonParsingErrorPtr_)
|
||||
return *jsonParsingErrorPtr_;
|
||||
return none;
|
||||
}
|
||||
|
||||
void setJsonObject(const Json::Value &pJson)
|
||||
{
|
||||
flagForParsingJson_ = true;
|
||||
flagForSerializingJson_ = false;
|
||||
jsonPtr_ = std::make_shared<Json::Value>(pJson);
|
||||
}
|
||||
|
||||
void setJsonObject(Json::Value &&pJson)
|
||||
{
|
||||
flagForParsingJson_ = true;
|
||||
flagForSerializingJson_ = false;
|
||||
jsonPtr_ = std::make_shared<Json::Value>(std::move(pJson));
|
||||
}
|
||||
|
||||
bool shouldBeCompressed() const;
|
||||
void generateBodyFromJson() const;
|
||||
|
||||
const std::string &sendfileName() const override
|
||||
{
|
||||
return sendfileName_;
|
||||
}
|
||||
|
||||
const SendfileRange &sendfileRange() const override
|
||||
{
|
||||
return sendfileRange_;
|
||||
}
|
||||
|
||||
const trantor::CertificatePtr &peerCertificate() const override
|
||||
{
|
||||
return peerCertificate_;
|
||||
}
|
||||
|
||||
void setPeerCertificate(const trantor::CertificatePtr &cert)
|
||||
{
|
||||
peerCertificate_ = cert;
|
||||
}
|
||||
|
||||
void setSendfile(const std::string &filename)
|
||||
{
|
||||
sendfileName_ = filename;
|
||||
}
|
||||
|
||||
void setSendfileRange(size_t offset, size_t len)
|
||||
{
|
||||
sendfileRange_.first = offset;
|
||||
sendfileRange_.second = len;
|
||||
}
|
||||
|
||||
const std::function<std::size_t(char *, std::size_t)> &streamCallback()
|
||||
const override
|
||||
{
|
||||
return streamCallback_;
|
||||
}
|
||||
|
||||
void setStreamCallback(
|
||||
const std::function<std::size_t(char *, std::size_t)> &callback)
|
||||
{
|
||||
streamCallback_ = callback;
|
||||
}
|
||||
|
||||
const std::function<void(ResponseStreamPtr)> &asyncStreamCallback()
|
||||
const override
|
||||
{
|
||||
return asyncStreamCallback_;
|
||||
}
|
||||
|
||||
void setAsyncStreamCallback(
|
||||
const std::function<void(ResponseStreamPtr)> &callback,
|
||||
bool disableKickoffTimeout)
|
||||
{
|
||||
asyncStreamCallback_ = callback;
|
||||
asyncStreamDisableKickoff_ = disableKickoffTimeout;
|
||||
}
|
||||
|
||||
bool asyncStreamKickoffDisabled() const
|
||||
{
|
||||
return asyncStreamDisableKickoff_;
|
||||
}
|
||||
|
||||
void makeHeaderString()
|
||||
{
|
||||
fullHeaderString_ = std::make_shared<trantor::MsgBuffer>(128);
|
||||
makeHeaderString(*fullHeaderString_);
|
||||
}
|
||||
|
||||
std::string contentTypeString() const override
|
||||
{
|
||||
parseContentTypeAndString();
|
||||
return contentTypeString_;
|
||||
}
|
||||
|
||||
void gunzip()
|
||||
{
|
||||
if (bodyPtr_)
|
||||
{
|
||||
auto gunzipBody =
|
||||
utils::gzipDecompress(bodyPtr_->data(), bodyPtr_->length());
|
||||
removeHeaderBy("content-encoding");
|
||||
bodyPtr_ =
|
||||
std::make_shared<HttpMessageStringBody>(std::move(gunzipBody));
|
||||
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
||||
}
|
||||
}
|
||||
|
||||
bool contentLengthIsAllowed() const
|
||||
{
|
||||
int statusCode =
|
||||
customStatusCode_ >= 0 ? customStatusCode_ : statusCode_;
|
||||
|
||||
// return false if status code is 1xx or 204
|
||||
return (statusCode >= k200OK || statusCode < k100Continue) &&
|
||||
statusCode != k204NoContent;
|
||||
}
|
||||
#ifdef USE_BROTLI
|
||||
void brDecompress()
|
||||
{
|
||||
if (bodyPtr_)
|
||||
{
|
||||
auto gunzipBody =
|
||||
utils::brotliDecompress(bodyPtr_->data(), bodyPtr_->length());
|
||||
removeHeaderBy("content-encoding");
|
||||
bodyPtr_ =
|
||||
std::make_shared<HttpMessageStringBody>(std::move(gunzipBody));
|
||||
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
~HttpResponseImpl() override = default;
|
||||
|
||||
protected:
|
||||
void makeHeaderString(trantor::MsgBuffer &headerString);
|
||||
|
||||
void parseContentTypeAndString() const
|
||||
{
|
||||
if (!flagForParsingContentType_)
|
||||
{
|
||||
flagForParsingContentType_ = true;
|
||||
auto &contentTypeString = getHeaderBy("content-type");
|
||||
if (contentTypeString == "")
|
||||
{
|
||||
contentType_ = CT_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos = contentTypeString.find(';');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
contentType_ = parseContentType(
|
||||
std::string_view(contentTypeString.data(), pos));
|
||||
}
|
||||
else
|
||||
{
|
||||
contentType_ =
|
||||
parseContentType(std::string_view(contentTypeString));
|
||||
}
|
||||
|
||||
if (contentType_ == CT_NONE)
|
||||
contentType_ = CT_CUSTOM;
|
||||
contentTypeString_ = contentTypeString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void setBody(const char *body, size_t len) override
|
||||
{
|
||||
bodyPtr_ = std::make_shared<HttpMessageStringViewBody>(body, len);
|
||||
if (passThrough_)
|
||||
{
|
||||
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
||||
}
|
||||
}
|
||||
|
||||
void setContentTypeCodeAndCustomString(ContentType type,
|
||||
const char *typeString,
|
||||
size_t typeStringLength) override
|
||||
{
|
||||
contentType_ = type;
|
||||
flagForParsingContentType_ = true;
|
||||
|
||||
std::string_view sv(typeString, typeStringLength);
|
||||
bool haveHeader = sv.find("content-type: ") == 0;
|
||||
bool haveCRLF = sv.rfind("\r\n") == sv.size() - 2;
|
||||
|
||||
size_t endOffset = 0;
|
||||
if (haveHeader)
|
||||
endOffset += 14;
|
||||
if (haveCRLF)
|
||||
endOffset += 2;
|
||||
setContentType(std::string_view{typeString + (haveHeader ? 14 : 0),
|
||||
typeStringLength - endOffset});
|
||||
}
|
||||
|
||||
void setContentTypeString(const char *typeString,
|
||||
size_t typeStringLength) override;
|
||||
|
||||
void setCustomStatusCode(int code,
|
||||
const char *message,
|
||||
size_t messageLength) override
|
||||
{
|
||||
assert(code >= 0);
|
||||
customStatusCode_ = code;
|
||||
statusMessage_ = std::string_view{message, messageLength};
|
||||
}
|
||||
|
||||
SafeStringMap<std::string> headers_;
|
||||
SafeStringMap<Cookie> cookies_;
|
||||
|
||||
int customStatusCode_{-1};
|
||||
HttpStatusCode statusCode_{kUnknown};
|
||||
std::string_view statusMessage_;
|
||||
|
||||
trantor::Date creationDate_;
|
||||
Version version_{Version::kHttp11};
|
||||
bool closeConnection_{false};
|
||||
mutable std::shared_ptr<HttpMessageBody> bodyPtr_;
|
||||
ssize_t expriedTime_{-1};
|
||||
std::string sendfileName_;
|
||||
SendfileRange sendfileRange_{0, 0};
|
||||
std::function<std::size_t(char *, std::size_t)> streamCallback_;
|
||||
std::function<void(ResponseStreamPtr)> asyncStreamCallback_;
|
||||
bool asyncStreamDisableKickoff_{false};
|
||||
|
||||
mutable std::shared_ptr<Json::Value> jsonPtr_;
|
||||
|
||||
std::shared_ptr<trantor::MsgBuffer> fullHeaderString_;
|
||||
trantor::CertificatePtr peerCertificate_;
|
||||
mutable std::shared_ptr<trantor::MsgBuffer> httpString_;
|
||||
mutable size_t datePos_{static_cast<size_t>(-1)};
|
||||
mutable int64_t httpStringDate_{-1};
|
||||
mutable bool flagForParsingJson_{false};
|
||||
mutable bool flagForSerializingJson_{true};
|
||||
mutable ContentType contentType_{CT_TEXT_PLAIN};
|
||||
mutable bool flagForParsingContentType_{false};
|
||||
mutable std::shared_ptr<std::string> jsonParsingErrorPtr_;
|
||||
mutable std::string contentTypeString_{"text/html; charset=utf-8"};
|
||||
bool passThrough_{false};
|
||||
|
||||
void setContentType(const std::string_view &contentType)
|
||||
{
|
||||
contentTypeString_ =
|
||||
std::string(contentType.data(), contentType.size());
|
||||
}
|
||||
|
||||
void setStatusMessage(const std::string_view &message)
|
||||
{
|
||||
statusMessage_ = message;
|
||||
}
|
||||
};
|
||||
|
||||
using HttpResponseImplPtr = std::shared_ptr<HttpResponseImpl>;
|
||||
|
||||
inline void swap(HttpResponseImpl &one, HttpResponseImpl &two) noexcept
|
||||
{
|
||||
one.swap(two);
|
||||
}
|
||||
|
||||
} // namespace drogon
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user