diff --git a/CMakeLists.txt b/CMakeLists.txt index 88026025..e181b28c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,7 @@ set(DROGON_SOURCES lib/src/RateLimiter.cc lib/src/RealIpResolver.cc lib/src/SecureSSLRedirector.cc + lib/src/Redirector.cc lib/src/SessionManager.cc lib/src/SlashRemover.cc lib/src/SlidingWindowRateLimiter.cc @@ -710,6 +711,7 @@ install(FILES ${DROGON_MONITORING_HEADERS} set(DROGON_PLUGIN_HEADERS lib/inc/drogon/plugins/Plugin.h + lib/inc/drogon/plugins/Redirector.h lib/inc/drogon/plugins/SecureSSLRedirector.h lib/inc/drogon/plugins/AccessLogger.h lib/inc/drogon/plugins/RealIpResolver.h diff --git a/lib/inc/drogon/HttpRequest.h b/lib/inc/drogon/HttpRequest.h index a40fafa0..e1728200 100644 --- a/lib/inc/drogon/HttpRequest.h +++ b/lib/inc/drogon/HttpRequest.h @@ -416,6 +416,7 @@ class DROGON_EXPORT HttpRequest /// 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 diff --git a/lib/inc/drogon/plugins/Redirector.h b/lib/inc/drogon/plugins/Redirector.h new file mode 100644 index 00000000..4bbea15d --- /dev/null +++ b/lib/inc/drogon/plugins/Redirector.h @@ -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 +#include +#include + +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; // 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; + +/** + * @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; + +/** + * @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, + public std::enable_shared_from_this +{ + 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 handlers_; + std::vector pathRewriteHandlers_; + std::vector forwardHandlers_; +}; + +} // namespace plugin +} // namespace drogon \ No newline at end of file diff --git a/lib/inc/drogon/plugins/SecureSSLRedirector.h b/lib/inc/drogon/plugins/SecureSSLRedirector.h index 704ee011..0493e733 100644 --- a/lib/inc/drogon/plugins/SecureSSLRedirector.h +++ b/lib/inc/drogon/plugins/SecureSSLRedirector.h @@ -25,7 +25,7 @@ namespace plugin * @code { "name": "drogon::plugin::SecureSSLRedirector", - "dependencies": [], + "dependencies": ["drogon::plugin::Redirector"], "config": { "ssl_redirect_exempt": ["^/.*\\.jpg", ...], "secure_ssl_host": "localhost:8849" @@ -64,8 +64,12 @@ class DROGON_EXPORT SecureSSLRedirector void shutdown() override; private: - HttpResponsePtr redirectingAdvice(const HttpRequestPtr &) const; - HttpResponsePtr redirectToSSL(const HttpRequestPtr &) const; + bool redirectingAdvice(const HttpRequestPtr &, + std::string &, + std::string &) const; + bool redirectToSSL(const HttpRequestPtr &, + std::string &, + std::string &) const; std::regex exemptPegex_; bool regexFlag_{false}; diff --git a/lib/inc/drogon/plugins/SlashRemover.h b/lib/inc/drogon/plugins/SlashRemover.h index db5e91ca..27c541ae 100644 --- a/lib/inc/drogon/plugins/SlashRemover.h +++ b/lib/inc/drogon/plugins/SlashRemover.h @@ -27,7 +27,7 @@ namespace drogon::plugin * @code { "name": "drogon::plugin::SlashRemover", - "dependencies": [], + "dependencies": ["drogon::plugin::Redirector"], "config": { // If true, it removes all trailing slashes, e.g. ///home// -> ///home diff --git a/lib/src/HttpRequestImpl.h b/lib/src/HttpRequestImpl.h index dde3767b..1cbb452a 100644 --- a/lib/src/HttpRequestImpl.h +++ b/lib/src/HttpRequestImpl.h @@ -159,6 +159,11 @@ class HttpRequestImpl : public HttpRequest path_ = path; } + void setPath(std::string &&path) override + { + path_ = std::move(path); + } + void setPathEncode(bool pathEncode) override { pathEncode_ = pathEncode; diff --git a/lib/src/Redirector.cc b/lib/src/Redirector.cc new file mode 100644 index 00000000..447dac66 --- /dev/null +++ b/lib/src/Redirector.cc @@ -0,0 +1,87 @@ +/** + * + * @file Redirector.cc + * An Tao + * + * Copyright 2018, An Tao. All rights reserved. + * https://github.com/an-tao/drogon + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Drogon + * + */ + +#include +#include + +using namespace drogon; +using namespace drogon::plugin; + +void Redirector::initAndStart(const Json::Value &config) +{ + auto weakPtr = std::weak_ptr(shared_from_this()); + drogon::app().registerSyncAdvice( + [weakPtr](const HttpRequestPtr &req) -> HttpResponsePtr { + auto thisPtr = weakPtr.lock(); + if (!thisPtr) + { + return HttpResponsePtr{}; + } + std::string protocol, host; + bool pathChanged{false}; + for (auto &handler : thisPtr->handlers_) + { + if (!handler(req, protocol, host, pathChanged)) + { + return HttpResponse::newNotFoundResponse(); + } + } + for (auto &handler : thisPtr->pathRewriteHandlers_) + { + pathChanged |= handler(req); + } + if (!protocol.empty() || !host.empty() || pathChanged) + { + std::string url; + if (protocol.empty()) + { + if (!host.empty()) + { + url = req->isOnSecureConnection() ? "https://" + : "http://"; + url.append(host); + } + } + else + { + url = std::move(protocol); + if (!host.empty()) + { + url.append(host); + } + else + { + url.append(req->getHeader("host")); + } + } + url.append(req->path()); + auto &query = req->query(); + if (!query.empty()) + { + url.append("?").append(query); + } + return HttpResponse::newRedirectionResponse(url); + } + for (auto &handler : thisPtr->forwardHandlers_) + { + handler(req); + } + return HttpResponsePtr{}; + }); +} + +void Redirector::shutdown() +{ + LOG_TRACE << "Redirector plugin is shutdown!"; +} diff --git a/lib/src/SecureSSLRedirector.cc b/lib/src/SecureSSLRedirector.cc index da5d0e31..f715f3e0 100644 --- a/lib/src/SecureSSLRedirector.cc +++ b/lib/src/SecureSSLRedirector.cc @@ -5,6 +5,7 @@ */ #include #include +#include #include using namespace drogon; @@ -42,14 +43,24 @@ void SecureSSLRedirector::initAndStart(const Json::Value &config) } secureHost_ = config.get("secure_ssl_host", "").asString(); std::weak_ptr weakPtr = shared_from_this(); - app().registerSyncAdvice([weakPtr](const HttpRequestPtr &req) { - auto thisPtr = weakPtr.lock(); - if (!thisPtr) - { - return HttpResponsePtr{}; - } - return thisPtr->redirectingAdvice(req); - }); + auto redirector = drogon::app().getPlugin(); + if (!redirector) + { + LOG_ERROR << "Redirector plugin is not found!"; + return; + } + redirector->registerRedirectHandler( + [weakPtr](const drogon::HttpRequestPtr &req, + std::string &protocol, + std::string &host, + bool &) -> bool { + auto thisPtr = weakPtr.lock(); + if (!thisPtr) + { + return false; + } + return thisPtr->redirectingAdvice(req, protocol, host); + }); } void SecureSSLRedirector::shutdown() @@ -57,60 +68,58 @@ void SecureSSLRedirector::shutdown() /// Shutdown the plugin } -HttpResponsePtr SecureSSLRedirector::redirectingAdvice( - const HttpRequestPtr &req) const +bool SecureSSLRedirector::redirectingAdvice(const HttpRequestPtr &req, + std::string &protocol, + std::string &host) const { - if (req->isOnSecureConnection()) + if (req->isOnSecureConnection() || protocol == "https://") { - return HttpResponsePtr{}; + return true; } else if (regexFlag_) { std::smatch regexResult; if (std::regex_match(req->path(), regexResult, exemptPegex_)) { - return HttpResponsePtr{}; + return true; } else { - return redirectToSSL(req); + return redirectToSSL(req, protocol, host); } } else { - return redirectToSSL(req); + return redirectToSSL(req, protocol, host); } } -HttpResponsePtr SecureSSLRedirector::redirectToSSL( - const HttpRequestPtr &req) const +bool SecureSSLRedirector::redirectToSSL(const HttpRequestPtr &req, + std::string &protocol, + std::string &host) const { if (!secureHost_.empty()) { - static std::string urlPrefix{"https://" + secureHost_}; - std::string query{urlPrefix + req->path()}; - if (!req->query().empty()) - { - query += "?" + req->query(); - } - return HttpResponse::newRedirectionResponse(query); + host = secureHost_; + protocol = "https://"; + return true; } - else + else if (host.empty()) { - const auto &host = req->getHeader("host"); - if (!host.empty()) + const auto &reqHost = req->getHeader("host"); + if (!reqHost.empty()) { - std::string query{"https://" + host}; - query += req->path(); - if (!req->query().empty()) - { - query += "?" + req->query(); - } - return HttpResponse::newRedirectionResponse(query); + protocol = "https://"; + return true; } else { - return HttpResponse::newNotFoundResponse(); + return false; } } + else + { + protocol = "https://"; + return true; + } } diff --git a/lib/src/SlashRemover.cc b/lib/src/SlashRemover.cc index d52f7710..9582d4f8 100644 --- a/lib/src/SlashRemover.cc +++ b/lib/src/SlashRemover.cc @@ -1,11 +1,13 @@ -#include "drogon/plugins/SlashRemover.h" -#include "drogon/HttpAppFramework.h" +#include +#include +#include #include "drogon/utils/FunctionTraits.h" #include #include +#include using namespace drogon; -using namespace plugin; +using namespace drogon::plugin; using std::string; #define TRAILING_SLASH_REGEX ".+\\/$" @@ -64,6 +66,31 @@ static inline void removeExcessiveSlashes(string& url) removeDuplicateSlashes(url); } +static inline bool handleReq(const drogon::HttpRequestPtr& req, int removeMode) +{ + static const std::regex regex(regexes[removeMode - 1]); + if (std::regex_match(req->path(), regex)) + { + string newPath = req->path(); + switch (removeMode) + { + case trailing: + removeTrailingSlashes(newPath); + break; + case duplicate: + removeDuplicateSlashes(newPath); + break; + case both: + default: + removeExcessiveSlashes(newPath); + break; + } + req->setPath(std::move(newPath)); + return true; + } + return false; +} + void SlashRemover::initAndStart(const Json::Value& config) { trailingSlashes_ = config.get("remove_trailing_slashes", true).asBool(); @@ -73,33 +100,23 @@ void SlashRemover::initAndStart(const Json::Value& config) (trailingSlashes_ * trailing) | (duplicateSlashes_ * duplicate); if (!removeMode) return; - app().registerHandlerViaRegex( - regexes[removeMode - 1], - [removeMode, - this](const HttpRequestPtr& req, - std::function&& callback) { - string newPath = req->path(); - switch (removeMode) - { - case trailing: - removeTrailingSlashes(newPath); - break; - case duplicate: - removeDuplicateSlashes(newPath); - break; - case both: - default: - removeExcessiveSlashes(newPath); - break; - } - if (redirect_) - callback(HttpResponse::newRedirectionResponse(newPath)); - else - { - req->setPath(newPath); - app().forward(req, std::move(callback)); - } - }); + auto redirector = app().getPlugin(); + if (!redirector) + { + LOG_ERROR << "Redirector plugin is not found!"; + return; + } + auto func = [removeMode](const HttpRequestPtr& req) -> bool { + return handleReq(req, removeMode); + }; + if (redirect_) + { + redirector->registerPathRewriteHandler(std::move(func)); + } + else + { + redirector->registerForwardHandler(std::move(func)); + } } void SlashRemover::shutdown()