mirror of
https://gitee.com/an-tao/drogon.git
synced 2024-11-29 18:27:43 +08:00
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:
parent
cedeeb59f4
commit
112d19ff12
@ -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
|
||||
|
@ -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
|
||||
|
119
lib/inc/drogon/plugins/Redirector.h
Normal file
119
lib/inc/drogon/plugins/Redirector.h
Normal 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
|
@ -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};
|
||||
|
@ -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
|
||||
|
@ -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
87
lib/src/Redirector.cc
Normal 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!";
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user