Optimize plugins with redirection functions (#1776)

Note: after this submission, users who use the SecureSSLRedirector plugin and the SlashRemover plugin should add the following line to the configuration file:
   {
      "name": "drogon::plugin::Redirector",
      "dependencies": [],
      "config": {
      }
   }
 and add the plugin name "drogon::plugin::Redirector" to the dependencies list of the SecureSSLRedirector plugin and the SlashRemover plugin.
This commit is contained in:
An Tao 2023-09-16 17:33:38 +08:00 committed by GitHub
parent cedeeb59f4
commit 112d19ff12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 313 additions and 69 deletions

View File

@ -268,6 +268,7 @@ set(DROGON_SOURCES
lib/src/RateLimiter.cc lib/src/RateLimiter.cc
lib/src/RealIpResolver.cc lib/src/RealIpResolver.cc
lib/src/SecureSSLRedirector.cc lib/src/SecureSSLRedirector.cc
lib/src/Redirector.cc
lib/src/SessionManager.cc lib/src/SessionManager.cc
lib/src/SlashRemover.cc lib/src/SlashRemover.cc
lib/src/SlidingWindowRateLimiter.cc lib/src/SlidingWindowRateLimiter.cc
@ -710,6 +711,7 @@ install(FILES ${DROGON_MONITORING_HEADERS}
set(DROGON_PLUGIN_HEADERS set(DROGON_PLUGIN_HEADERS
lib/inc/drogon/plugins/Plugin.h lib/inc/drogon/plugins/Plugin.h
lib/inc/drogon/plugins/Redirector.h
lib/inc/drogon/plugins/SecureSSLRedirector.h lib/inc/drogon/plugins/SecureSSLRedirector.h
lib/inc/drogon/plugins/AccessLogger.h lib/inc/drogon/plugins/AccessLogger.h
lib/inc/drogon/plugins/RealIpResolver.h lib/inc/drogon/plugins/RealIpResolver.h

View File

@ -416,6 +416,7 @@ class DROGON_EXPORT HttpRequest
/// Set the path of the request /// Set the path of the request
virtual void setPath(const std::string &path) = 0; 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 * @brief The default behavior is to encode the value of setPath

View File

@ -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

View File

@ -25,7 +25,7 @@ namespace plugin
* @code * @code
{ {
"name": "drogon::plugin::SecureSSLRedirector", "name": "drogon::plugin::SecureSSLRedirector",
"dependencies": [], "dependencies": ["drogon::plugin::Redirector"],
"config": { "config": {
"ssl_redirect_exempt": ["^/.*\\.jpg", ...], "ssl_redirect_exempt": ["^/.*\\.jpg", ...],
"secure_ssl_host": "localhost:8849" "secure_ssl_host": "localhost:8849"
@ -64,8 +64,12 @@ class DROGON_EXPORT SecureSSLRedirector
void shutdown() override; void shutdown() override;
private: private:
HttpResponsePtr redirectingAdvice(const HttpRequestPtr &) const; bool redirectingAdvice(const HttpRequestPtr &,
HttpResponsePtr redirectToSSL(const HttpRequestPtr &) const; std::string &,
std::string &) const;
bool redirectToSSL(const HttpRequestPtr &,
std::string &,
std::string &) const;
std::regex exemptPegex_; std::regex exemptPegex_;
bool regexFlag_{false}; bool regexFlag_{false};

View File

@ -27,7 +27,7 @@ namespace drogon::plugin
* @code * @code
{ {
"name": "drogon::plugin::SlashRemover", "name": "drogon::plugin::SlashRemover",
"dependencies": [], "dependencies": ["drogon::plugin::Redirector"],
"config": { "config": {
// If true, it removes all trailing slashes, e.g. // If true, it removes all trailing slashes, e.g.
///home// -> ///home ///home// -> ///home

View File

@ -159,6 +159,11 @@ class HttpRequestImpl : public HttpRequest
path_ = path; path_ = path;
} }
void setPath(std::string &&path) override
{
path_ = std::move(path);
}
void setPathEncode(bool pathEncode) override void setPathEncode(bool pathEncode) override
{ {
pathEncode_ = pathEncode; pathEncode_ = pathEncode;

87
lib/src/Redirector.cc Normal file
View File

@ -0,0 +1,87 @@
/**
*
* @file Redirector.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/drogon.h>
#include <drogon/plugins/Redirector.h>
using namespace drogon;
using namespace drogon::plugin;
void Redirector::initAndStart(const Json::Value &config)
{
auto weakPtr = std::weak_ptr<Redirector>(shared_from_this());
drogon::app().registerSyncAdvice(
[weakPtr](const HttpRequestPtr &req) -> HttpResponsePtr {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
return HttpResponsePtr{};
}
std::string protocol, host;
bool pathChanged{false};
for (auto &handler : thisPtr->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!";
}

View File

@ -5,6 +5,7 @@
*/ */
#include <drogon/drogon.h> #include <drogon/drogon.h>
#include <drogon/plugins/SecureSSLRedirector.h> #include <drogon/plugins/SecureSSLRedirector.h>
#include <drogon/plugins/Redirector.h>
#include <string> #include <string>
using namespace drogon; using namespace drogon;
@ -42,14 +43,24 @@ void SecureSSLRedirector::initAndStart(const Json::Value &config)
} }
secureHost_ = config.get("secure_ssl_host", "").asString(); secureHost_ = config.get("secure_ssl_host", "").asString();
std::weak_ptr<SecureSSLRedirector> weakPtr = shared_from_this(); std::weak_ptr<SecureSSLRedirector> weakPtr = shared_from_this();
app().registerSyncAdvice([weakPtr](const HttpRequestPtr &req) { auto redirector = drogon::app().getPlugin<Redirector>();
auto thisPtr = weakPtr.lock(); if (!redirector)
if (!thisPtr) {
{ LOG_ERROR << "Redirector plugin is not found!";
return HttpResponsePtr{}; return;
} }
return thisPtr->redirectingAdvice(req); 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() void SecureSSLRedirector::shutdown()
@ -57,60 +68,58 @@ void SecureSSLRedirector::shutdown()
/// Shutdown the plugin /// Shutdown the plugin
} }
HttpResponsePtr SecureSSLRedirector::redirectingAdvice( bool SecureSSLRedirector::redirectingAdvice(const HttpRequestPtr &req,
const HttpRequestPtr &req) const std::string &protocol,
std::string &host) const
{ {
if (req->isOnSecureConnection()) if (req->isOnSecureConnection() || protocol == "https://")
{ {
return HttpResponsePtr{}; return true;
} }
else if (regexFlag_) else if (regexFlag_)
{ {
std::smatch regexResult; std::smatch regexResult;
if (std::regex_match(req->path(), regexResult, exemptPegex_)) if (std::regex_match(req->path(), regexResult, exemptPegex_))
{ {
return HttpResponsePtr{}; return true;
} }
else else
{ {
return redirectToSSL(req); return redirectToSSL(req, protocol, host);
} }
} }
else else
{ {
return redirectToSSL(req); return redirectToSSL(req, protocol, host);
} }
} }
HttpResponsePtr SecureSSLRedirector::redirectToSSL( bool SecureSSLRedirector::redirectToSSL(const HttpRequestPtr &req,
const HttpRequestPtr &req) const std::string &protocol,
std::string &host) const
{ {
if (!secureHost_.empty()) if (!secureHost_.empty())
{ {
static std::string urlPrefix{"https://" + secureHost_}; host = secureHost_;
std::string query{urlPrefix + req->path()}; protocol = "https://";
if (!req->query().empty()) return true;
{
query += "?" + req->query();
}
return HttpResponse::newRedirectionResponse(query);
} }
else else if (host.empty())
{ {
const auto &host = req->getHeader("host"); const auto &reqHost = req->getHeader("host");
if (!host.empty()) if (!reqHost.empty())
{ {
std::string query{"https://" + host}; protocol = "https://";
query += req->path(); return true;
if (!req->query().empty())
{
query += "?" + req->query();
}
return HttpResponse::newRedirectionResponse(query);
} }
else else
{ {
return HttpResponse::newNotFoundResponse(); return false;
} }
} }
else
{
protocol = "https://";
return true;
}
} }

View File

@ -1,11 +1,13 @@
#include "drogon/plugins/SlashRemover.h" #include <drogon/plugins/SlashRemover.h>
#include "drogon/HttpAppFramework.h" #include <drogon/plugins/Redirector.h>
#include <drogon/HttpAppFramework.h>
#include "drogon/utils/FunctionTraits.h" #include "drogon/utils/FunctionTraits.h"
#include <functional> #include <functional>
#include <string> #include <string>
#include <regex>
using namespace drogon; using namespace drogon;
using namespace plugin; using namespace drogon::plugin;
using std::string; using std::string;
#define TRAILING_SLASH_REGEX ".+\\/$" #define TRAILING_SLASH_REGEX ".+\\/$"
@ -64,6 +66,31 @@ static inline void removeExcessiveSlashes(string& url)
removeDuplicateSlashes(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) void SlashRemover::initAndStart(const Json::Value& config)
{ {
trailingSlashes_ = config.get("remove_trailing_slashes", true).asBool(); trailingSlashes_ = config.get("remove_trailing_slashes", true).asBool();
@ -73,33 +100,23 @@ void SlashRemover::initAndStart(const Json::Value& config)
(trailingSlashes_ * trailing) | (duplicateSlashes_ * duplicate); (trailingSlashes_ * trailing) | (duplicateSlashes_ * duplicate);
if (!removeMode) if (!removeMode)
return; return;
app().registerHandlerViaRegex( auto redirector = app().getPlugin<Redirector>();
regexes[removeMode - 1], if (!redirector)
[removeMode, {
this](const HttpRequestPtr& req, LOG_ERROR << "Redirector plugin is not found!";
std::function<void(const HttpResponsePtr&)>&& callback) { return;
string newPath = req->path(); }
switch (removeMode) auto func = [removeMode](const HttpRequestPtr& req) -> bool {
{ return handleReq(req, removeMode);
case trailing: };
removeTrailingSlashes(newPath); if (redirect_)
break; {
case duplicate: redirector->registerPathRewriteHandler(std::move(func));
removeDuplicateSlashes(newPath); }
break; else
case both: {
default: redirector->registerForwardHandler(std::move(func));
removeExcessiveSlashes(newPath); }
break;
}
if (redirect_)
callback(HttpResponse::newRedirectionResponse(newPath));
else
{
req->setPath(newPath);
app().forward(req, std::move(callback));
}
});
} }
void SlashRemover::shutdown() void SlashRemover::shutdown()