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

View File

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

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
{
"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};

View File

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

View File

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

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/plugins/SecureSSLRedirector.h>
#include <drogon/plugins/Redirector.h>
#include <string>
using namespace drogon;
@ -42,14 +43,24 @@ void SecureSSLRedirector::initAndStart(const Json::Value &config)
}
secureHost_ = config.get("secure_ssl_host", "").asString();
std::weak_ptr<SecureSSLRedirector> 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<Redirector>();
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;
}
}

View File

@ -1,11 +1,13 @@
#include "drogon/plugins/SlashRemover.h"
#include "drogon/HttpAppFramework.h"
#include <drogon/plugins/SlashRemover.h>
#include <drogon/plugins/Redirector.h>
#include <drogon/HttpAppFramework.h>
#include "drogon/utils/FunctionTraits.h"
#include <functional>
#include <string>
#include <regex>
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<void(const HttpResponsePtr&)>&& 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<Redirector>();
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()