add class 'UrlRouter'.
This commit is contained in:
parent
4c1db38c8e
commit
a6b3939777
@ -1,5 +1,12 @@
|
||||
add_library(HttpProxy
|
||||
NetworkUtility.h NetworkUtility.cpp
|
||||
ProxyHttpSession.h ProxyHttpSession.cpp
|
||||
ProxyListener.h ProxyListener.cpp
|
||||
ProxyTcpSession.h ProxyTcpSession.cpp
|
||||
TemplateMatchs.h TemplateMatchs.cpp
|
||||
TemplateSegmentRule.h TemplateSegmentRule.cpp
|
||||
UrlRouter.h UrlRouter.cpp
|
||||
UrlRouterPrivate.h UrlRouterPrivate.cpp
|
||||
)
|
||||
|
||||
target_include_directories(HttpProxy
|
||||
|
94
HttpProxy/ProxyHttpSession.cpp
Normal file
94
HttpProxy/ProxyHttpSession.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include "ProxyHttpSession.h"
|
||||
#include "BoostLog.h"
|
||||
#include "ProxyTcpSession.h"
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast/http/read.hpp>
|
||||
#include <boost/beast/http/write.hpp>
|
||||
|
||||
ProxyHttpSession::ProxyHttpSession(boost::asio::io_context &ioContext, boost::asio::ip::tcp::socket &&socket)
|
||||
: m_resolver(ioContext), m_clientStream(std::move(socket)), m_serverStream(boost::asio::make_strand(ioContext)) {
|
||||
}
|
||||
|
||||
void ProxyHttpSession::run() {
|
||||
doRead();
|
||||
}
|
||||
|
||||
void ProxyHttpSession::doRead() {
|
||||
// Construct a new parser for each message
|
||||
m_parser.emplace();
|
||||
|
||||
// Apply a reasonable limit to the allowed size
|
||||
// of the body in bytes to prevent abuse.
|
||||
m_parser->body_limit(std::numeric_limits<std::uint64_t>::max());
|
||||
m_parser->header_limit(std::numeric_limits<std::uint32_t>::max());
|
||||
m_buffer.clear();
|
||||
|
||||
// Set the timeout.
|
||||
// m_stream.expires_after(std::chrono::seconds(30));
|
||||
m_clientStream.expires_never();
|
||||
boost::beast::http::async_read(
|
||||
m_clientStream, m_buffer, *m_parser,
|
||||
[self{shared_from_this()}](const boost::system::error_code &ec, std::size_t bytes_transferred) {
|
||||
self->onRead(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
|
||||
void ProxyHttpSession::onRead(boost::beast::error_code ec, size_t) { // This means they closed the connection
|
||||
if (ec == boost::beast::http::error::end_of_stream) {
|
||||
LOG(info) << ec << " : " << ec.message();
|
||||
m_clientStream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
|
||||
return;
|
||||
}
|
||||
if (ec) {
|
||||
if (ec == boost::asio::error::operation_aborted) return;
|
||||
LOG(info) << ec << " : " << ec.message();
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = m_parser->release();
|
||||
if (request.count("Host") <= 0) return;
|
||||
auto host = request.at("Host");
|
||||
if (host.empty()) {
|
||||
LOG(error) << "Host field not existed.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto colonPos = host.find_first_of(':');
|
||||
|
||||
std::string_view serverAddress;
|
||||
std::string_view serverPort;
|
||||
if (colonPos != std::string_view::npos) {
|
||||
serverAddress = host.substr(0, colonPos);
|
||||
serverPort = host.substr(colonPos + 1);
|
||||
} else {
|
||||
serverAddress = host;
|
||||
serverPort = "80";
|
||||
}
|
||||
|
||||
boost::system::error_code ee;
|
||||
auto const results = m_resolver.resolve(serverAddress, serverPort, ee);
|
||||
if (ee) {
|
||||
LOG(error) << ee.message();
|
||||
return;
|
||||
}
|
||||
m_serverStream.connect(results, ee);
|
||||
if (ee) {
|
||||
LOG(error) << ee.message();
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method() == boost::beast::http::verb::connect) {
|
||||
LOG(info) << "proxy host [" << serverAddress << "], port [" << serverPort << "]";
|
||||
boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::ok,
|
||||
request.version()};
|
||||
res.reason("Connection Established");
|
||||
res.keep_alive(request.keep_alive());
|
||||
res.set("Proxy-agent", "amass");
|
||||
res.prepare_payload();
|
||||
boost::beast::http::write(m_clientStream, res);
|
||||
} else {
|
||||
boost::beast::http::write(m_serverStream, request);
|
||||
}
|
||||
auto tcp = std::make_shared<ProxyTcpSession>(m_clientStream.release_socket(), m_serverStream.release_socket());
|
||||
tcp->run();
|
||||
}
|
27
HttpProxy/ProxyHttpSession.h
Normal file
27
HttpProxy/ProxyHttpSession.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef PROXYHTTPSESSION_H
|
||||
#define PROXYHTTPSESSION_H
|
||||
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <optional>
|
||||
|
||||
class ProxyHttpSession : public std::enable_shared_from_this<ProxyHttpSession> {
|
||||
public:
|
||||
ProxyHttpSession(boost::asio::io_context &ioContext, boost::asio::ip::tcp::socket &&socket);
|
||||
void run();
|
||||
|
||||
protected:
|
||||
void doRead();
|
||||
void onRead(boost::beast::error_code ec, std::size_t);
|
||||
|
||||
private:
|
||||
boost::asio::ip::tcp::resolver m_resolver;
|
||||
std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser;
|
||||
boost::beast::flat_buffer m_buffer{std::numeric_limits<std::uint64_t>::max()};
|
||||
boost::beast::tcp_stream m_clientStream;
|
||||
boost::beast::tcp_stream m_serverStream;
|
||||
};
|
||||
|
||||
#endif // PROXYHTTPSESSION_H
|
51
HttpProxy/ProxyListener.cpp
Normal file
51
HttpProxy/ProxyListener.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "ProxyListener.h"
|
||||
#include "BoostLog.h"
|
||||
#include "ProxyHttpSession.h"
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
|
||||
ProxyListener::ProxyListener(boost::asio::io_context &ioContext, boost::asio::ip::tcp::endpoint endpoint)
|
||||
: m_ioContext(ioContext), m_acceptor(m_ioContext), m_endpoint(std::move(endpoint)) {
|
||||
}
|
||||
|
||||
void ProxyListener::run(boost::system::error_code &error) {
|
||||
m_acceptor.open(m_endpoint.protocol(), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
m_acceptor.set_option(boost::asio::ip::tcp::socket::reuse_address(true), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
|
||||
m_acceptor.bind(m_endpoint, error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
|
||||
m_acceptor.listen(boost::asio::ip::tcp::socket::max_connections, error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
doAccept();
|
||||
}
|
||||
|
||||
void ProxyListener::doAccept() {
|
||||
m_acceptor.async_accept(
|
||||
boost::asio::make_strand(m_ioContext),
|
||||
[ptr{weak_from_this()}](boost::beast::error_code error, boost::asio::ip::tcp::socket socket) {
|
||||
if (ptr.expired()) return;
|
||||
auto self = ptr.lock();
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
auto session = std::make_shared<ProxyHttpSession>(self->m_ioContext, std::move(socket));
|
||||
session->run();
|
||||
self->doAccept();
|
||||
});
|
||||
}
|
21
HttpProxy/ProxyListener.h
Normal file
21
HttpProxy/ProxyListener.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef PROXYLISTENER_H
|
||||
#define PROXYLISTENER_H
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
class ProxyListener : public std::enable_shared_from_this<ProxyListener> {
|
||||
public:
|
||||
ProxyListener(boost::asio::io_context &ioContext, boost::asio::ip::tcp::endpoint endpoint);
|
||||
void run(boost::system::error_code &error);
|
||||
|
||||
protected:
|
||||
void doAccept();
|
||||
|
||||
private:
|
||||
boost::asio::io_context &m_ioContext;
|
||||
boost::asio::ip::tcp::acceptor m_acceptor;
|
||||
boost::asio::ip::tcp::endpoint m_endpoint;
|
||||
};
|
||||
|
||||
#endif // PROXYLISTENER_H
|
50
HttpProxy/ProxyTcpSession.cpp
Normal file
50
HttpProxy/ProxyTcpSession.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "ProxyTcpSession.h"
|
||||
#include "BoostLog.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <sstream>
|
||||
|
||||
ProxyTcpSession::ProxyTcpSession(boost::asio::ip::tcp::socket &&client, boost::asio::ip::tcp::socket &&server)
|
||||
: m_client(std::move(client)), m_server(std::move(server)) {
|
||||
}
|
||||
|
||||
ProxyTcpSession::~ProxyTcpSession() {
|
||||
}
|
||||
|
||||
void ProxyTcpSession::run() {
|
||||
clientRead();
|
||||
serverRead();
|
||||
}
|
||||
|
||||
void ProxyTcpSession::clientRead() {
|
||||
m_client.async_read_some(
|
||||
boost::asio::buffer(m_clientBuffer),
|
||||
[self = shared_from_this()](boost::system::error_code error, std::size_t bytes_transferred) {
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
self->m_server.close();
|
||||
return;
|
||||
}
|
||||
boost::asio::write(self->m_server, boost::asio::buffer(self->m_clientBuffer, bytes_transferred), error);
|
||||
if (error) {
|
||||
LOG(info) << error.message();
|
||||
}
|
||||
self->clientRead();
|
||||
});
|
||||
}
|
||||
|
||||
void ProxyTcpSession::serverRead() {
|
||||
m_server.async_read_some(
|
||||
boost::asio::buffer(m_serverBuffer),
|
||||
[self = shared_from_this()](boost::system::error_code error, std::size_t bytes_transferred) {
|
||||
if (error) {
|
||||
self->m_client.close();
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
boost::asio::write(self->m_client, boost::asio::buffer(self->m_serverBuffer, bytes_transferred), error);
|
||||
if (error) {
|
||||
LOG(info) << error.message();
|
||||
}
|
||||
self->serverRead();
|
||||
});
|
||||
}
|
24
HttpProxy/ProxyTcpSession.h
Normal file
24
HttpProxy/ProxyTcpSession.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef PROXYTCPSESSION_H
|
||||
#define PROXYTCPSESSION_H
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
class ProxyTcpSession : public std::enable_shared_from_this<ProxyTcpSession> {
|
||||
public:
|
||||
ProxyTcpSession(boost::asio::ip::tcp::socket &&client, boost::asio::ip::tcp::socket &&server);
|
||||
~ProxyTcpSession();
|
||||
void run();
|
||||
|
||||
protected:
|
||||
void clientRead();
|
||||
void serverRead();
|
||||
|
||||
private:
|
||||
boost::asio::ip::tcp::socket m_client;
|
||||
boost::asio::ip::tcp::socket m_server;
|
||||
|
||||
std::array<uint8_t, 4096> m_clientBuffer;
|
||||
std::array<uint8_t, 4096> m_serverBuffer;
|
||||
};
|
||||
|
||||
#endif // PROXYTCPSESSION_H
|
12
HttpProxy/TemplateMatchs.cpp
Normal file
12
HttpProxy/TemplateMatchs.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "TemplateMatchs.h"
|
||||
|
||||
TemplateMatchStorageBase::const_reference TemplateMatchStorageBase::at(boost::urls::string_view id) const {
|
||||
for (std::size_t i = 0; i < size(); ++i) {
|
||||
if (ids()[i] == id) return matches()[i];
|
||||
}
|
||||
boost::throw_exception(std::out_of_range(""));
|
||||
}
|
||||
|
||||
TemplateMatchStorageBase::const_reference TemplateMatchStorageBase::operator[](boost::urls::string_view id) const {
|
||||
return at(id);
|
||||
}
|
57
HttpProxy/TemplateMatchs.h
Normal file
57
HttpProxy/TemplateMatchs.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef __TEMPLATEMATCHS_H__
|
||||
#define __TEMPLATEMATCHS_H__
|
||||
|
||||
#include <boost/url/string_view.hpp>
|
||||
|
||||
class TemplateMatchStorageBase {
|
||||
public:
|
||||
using const_reference = boost::urls::string_view const &;
|
||||
|
||||
virtual boost::urls::string_view *matches() = 0;
|
||||
virtual const boost::urls::string_view *matches() const = 0;
|
||||
|
||||
virtual boost::urls::string_view *ids() = 0;
|
||||
virtual const boost::urls::string_view *ids() const = 0;
|
||||
virtual std::size_t size() const = 0;
|
||||
virtual void resize(std::size_t) = 0;
|
||||
|
||||
const_reference at(boost::urls::string_view id) const;
|
||||
|
||||
const_reference operator[](boost::urls::string_view id) const;
|
||||
};
|
||||
|
||||
template <std::size_t N = 20>
|
||||
class TemplateMatchStorage : public TemplateMatchStorageBase {
|
||||
public:
|
||||
boost::urls::string_view *matches() final {
|
||||
return m_matches;
|
||||
}
|
||||
|
||||
virtual const boost::urls::string_view *matches() const final {
|
||||
return m_matches;
|
||||
}
|
||||
|
||||
boost::urls::string_view *ids() final {
|
||||
return m_ids;
|
||||
}
|
||||
|
||||
const boost::urls::string_view *ids() const final {
|
||||
return m_ids;
|
||||
}
|
||||
|
||||
std::size_t size() const final {
|
||||
return m_size;
|
||||
}
|
||||
void resize(std::size_t n) final {
|
||||
m_size = n;
|
||||
}
|
||||
|
||||
private:
|
||||
boost::urls::string_view m_matches[N];
|
||||
boost::urls::string_view m_ids[N];
|
||||
std::size_t m_size;
|
||||
};
|
||||
|
||||
using TemplateMatches = TemplateMatchStorage<20>;
|
||||
|
||||
#endif // __TEMPLATEMATCHS_H__
|
96
HttpProxy/TemplateSegmentRule.cpp
Normal file
96
HttpProxy/TemplateSegmentRule.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "TemplateSegmentRule.h"
|
||||
#include "BoostLog.h"
|
||||
#include <boost/url/decode_view.hpp>
|
||||
#include <boost/url/detail/replacement_field_rule.hpp>
|
||||
#include <boost/url/error.hpp>
|
||||
#include <boost/url/grammar.hpp>
|
||||
#include <boost/url/rfc/detail/path_rules.hpp>
|
||||
#include <string_view>
|
||||
|
||||
boost::urls::result<TemplateSegmentRule::value_type> TemplateSegmentRule::parse(const char *&iterator,
|
||||
const char *end) const noexcept {
|
||||
TemplateSegmentRule::value_type ret;
|
||||
bool isTemplate = false;
|
||||
if (iterator != end && *iterator == '{') {
|
||||
auto it0 = iterator;
|
||||
++iterator;
|
||||
auto rightBraces = boost::urls::grammar::find_if(iterator, end, boost::urls::grammar::lut_chars('}'));
|
||||
if (rightBraces != end) {
|
||||
boost::urls::string_view segment(iterator, rightBraces);
|
||||
static constexpr auto modifiers_cs = boost::urls::grammar::lut_chars("?*+");
|
||||
static constexpr auto id_rule = boost::urls::grammar::tuple_rule(
|
||||
boost::urls::grammar::optional_rule(boost::urls::detail::arg_id_rule),
|
||||
boost::urls::grammar::optional_rule(boost::urls::grammar::delim_rule(modifiers_cs)));
|
||||
if (segment.empty() || boost::urls::grammar::parse(segment, id_rule)) {
|
||||
isTemplate = true;
|
||||
iterator = rightBraces + 1;
|
||||
ret.m_string = boost::urls::string_view(it0, rightBraces + 1);
|
||||
ret.m_isLiteral = false;
|
||||
if (segment.ends_with('?'))
|
||||
ret.modifier = TemplateSegment::Modifier::Optional;
|
||||
else if (segment.ends_with('*'))
|
||||
ret.modifier = TemplateSegment::Modifier::Star;
|
||||
else if (segment.ends_with('+'))
|
||||
ret.modifier = TemplateSegment::Modifier::Plus;
|
||||
}
|
||||
}
|
||||
if (!isTemplate) iterator = it0;
|
||||
}
|
||||
if (!isTemplate) {
|
||||
auto rv = boost::urls::grammar::parse(iterator, end, boost::urls::detail::segment_rule);
|
||||
BOOST_ASSERT(rv);
|
||||
rv->decode({}, boost::urls::string_token::assign_to(ret.m_string));
|
||||
ret.m_isLiteral = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TemplateSegment::isLiteral() const {
|
||||
return m_isLiteral;
|
||||
}
|
||||
|
||||
bool TemplateSegment::isStar() const {
|
||||
return modifier == Modifier::Star;
|
||||
}
|
||||
|
||||
bool TemplateSegment::isPlus() const {
|
||||
return modifier == Modifier::Plus;
|
||||
}
|
||||
|
||||
bool TemplateSegment::isOptional() const {
|
||||
return modifier == Modifier::Optional;
|
||||
}
|
||||
|
||||
bool TemplateSegment::hasModifier() const {
|
||||
return !m_isLiteral && modifier != Modifier::None;
|
||||
}
|
||||
|
||||
boost::urls::string_view TemplateSegment::id() const {
|
||||
BOOST_ASSERT(!isLiteral());
|
||||
boost::urls::string_view r = {m_string};
|
||||
r.remove_prefix(1);
|
||||
r.remove_suffix(1);
|
||||
if (r.ends_with('?') || r.ends_with('+') || r.ends_with('*')) r.remove_suffix(1);
|
||||
return r;
|
||||
}
|
||||
|
||||
boost::urls::string_view TemplateSegment::string() const {
|
||||
return m_string;
|
||||
}
|
||||
|
||||
bool TemplateSegment::match(boost::urls::pct_string_view segement) const {
|
||||
if (m_isLiteral) return *segement == m_string;
|
||||
return true; // other nodes match any string
|
||||
}
|
||||
|
||||
bool TemplateSegment::operator==(const TemplateSegment &other) const {
|
||||
if (m_isLiteral != other.m_isLiteral) return false;
|
||||
if (m_isLiteral) return m_string == other.m_string;
|
||||
return modifier == other.modifier;
|
||||
}
|
||||
|
||||
bool TemplateSegment::operator<(const TemplateSegment &other) const {
|
||||
if (other.m_isLiteral) return false;
|
||||
if (m_isLiteral) return !other.m_isLiteral;
|
||||
return modifier < other.modifier;
|
||||
}
|
62
HttpProxy/TemplateSegmentRule.h
Normal file
62
HttpProxy/TemplateSegmentRule.h
Normal file
@ -0,0 +1,62 @@
|
||||
#ifndef __TEMPLATESEGMENTRULE_H__
|
||||
#define __TEMPLATESEGMENTRULE_H__
|
||||
|
||||
#include <boost/url/error_types.hpp>
|
||||
#include <boost/url/grammar/delim_rule.hpp>
|
||||
#include <boost/url/grammar/optional_rule.hpp>
|
||||
#include <boost/url/grammar/range_rule.hpp>
|
||||
#include <boost/url/grammar/tuple_rule.hpp>
|
||||
#include <boost/url/pct_string_view.hpp>
|
||||
|
||||
class TemplateSegment {
|
||||
friend class TemplateSegmentRule;
|
||||
|
||||
public:
|
||||
enum class Modifier {
|
||||
None,
|
||||
Optional, // {id?}
|
||||
Star, // {id*}
|
||||
Plus // {id+}
|
||||
};
|
||||
|
||||
public:
|
||||
bool isLiteral() const;
|
||||
bool isStar() const;
|
||||
bool isPlus() const;
|
||||
bool isOptional() const;
|
||||
bool hasModifier() const;
|
||||
boost::urls::string_view id() const;
|
||||
boost::urls::string_view string() const;
|
||||
bool match(boost::urls::pct_string_view segement) const;
|
||||
bool operator==(const TemplateSegment &other) const;
|
||||
|
||||
// segments have precedence:
|
||||
// - literal
|
||||
// - unique
|
||||
// - optional
|
||||
// - plus
|
||||
// - star
|
||||
bool operator<(const TemplateSegment &other) const;
|
||||
|
||||
private:
|
||||
Modifier modifier = Modifier::None;
|
||||
std::string m_string;
|
||||
bool m_isLiteral = true;
|
||||
};
|
||||
|
||||
class TemplateSegmentRule {
|
||||
public:
|
||||
using value_type = TemplateSegment;
|
||||
boost::urls::result<value_type> parse(char const *&iterator, char const *end) const noexcept;
|
||||
};
|
||||
|
||||
constexpr auto templateSegmentRule = TemplateSegmentRule{};
|
||||
|
||||
constexpr auto templatePathRule = boost::urls::grammar::tuple_rule(
|
||||
boost::urls::grammar::squelch(boost::urls::grammar::optional_rule(boost::urls::grammar::delim_rule('/'))),
|
||||
boost::urls::grammar::range_rule(
|
||||
templateSegmentRule,
|
||||
boost::urls::grammar::tuple_rule(boost::urls::grammar::squelch(boost::urls::grammar::delim_rule('/')),
|
||||
templateSegmentRule)));
|
||||
|
||||
#endif // __TEMPLATESEGMENTRULE_H__
|
1
HttpProxy/UrlRouter.cpp
Normal file
1
HttpProxy/UrlRouter.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "UrlRouter.h"
|
47
HttpProxy/UrlRouter.h
Normal file
47
HttpProxy/UrlRouter.h
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef __URLROUTER_H__
|
||||
#define __URLROUTER_H__
|
||||
|
||||
#include "TemplateMatchs.h"
|
||||
#include "UrlRouterPrivate.h"
|
||||
#include <boost/url/segments_encoded_view.hpp>
|
||||
|
||||
template <typename Resource>
|
||||
class UrlRouter : public UrlRouterPrivate {
|
||||
public:
|
||||
template <class U>
|
||||
void insert(std::string_view pattern, U &&v) {
|
||||
BOOST_STATIC_ASSERT(std::is_same<Resource, U>::value || std::is_convertible<U, Resource>::value ||
|
||||
std::is_base_of<Resource, U>::value);
|
||||
using ResourceType =
|
||||
typename std::decay_t<typename std::conditional_t<std::is_base_of_v<Resource, U>, U, Resource>>;
|
||||
|
||||
class Implementation : public AnyResource {
|
||||
public:
|
||||
explicit Implementation(U &&u_) : resource(std::forward<U>(u_)) {
|
||||
}
|
||||
void const *get() const noexcept override {
|
||||
return static_cast<Resource const *>(&resource);
|
||||
}
|
||||
|
||||
private:
|
||||
ResourceType resource;
|
||||
};
|
||||
auto resource = std::make_shared<Implementation>(std::forward<U>(v));
|
||||
insertImpl(pattern, std::dynamic_pointer_cast<AnyResource>(resource));
|
||||
}
|
||||
|
||||
const Resource *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &matches) const noexcept {
|
||||
boost::urls::string_view *matches_it = matches.matches();
|
||||
boost::urls::string_view *ids_it = matches.ids();
|
||||
AnyResource const *p = findImpl(path, matches_it, ids_it);
|
||||
if (p) {
|
||||
BOOST_ASSERT(matches_it >= matches.matches());
|
||||
matches.resize(static_cast<std::size_t>(matches_it - matches.matches()));
|
||||
return reinterpret_cast<Resource const *>(p->get());
|
||||
}
|
||||
matches.resize(0);
|
||||
return nullptr;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // __URLROUTER_H__
|
323
HttpProxy/UrlRouterPrivate.cpp
Normal file
323
HttpProxy/UrlRouterPrivate.cpp
Normal file
@ -0,0 +1,323 @@
|
||||
#include "UrlRouterPrivate.h"
|
||||
#include "BoostLog.h"
|
||||
#include "TemplateSegmentRule.h"
|
||||
#include <boost/url/decode_view.hpp>
|
||||
#include <boost/url/rfc/detail/path_rules.hpp>
|
||||
|
||||
UrlRouterPrivate::UrlRouterPrivate() {
|
||||
m_nodes.push_back(SegementNode{});
|
||||
}
|
||||
|
||||
void UrlRouterPrivate::insertImpl(boost::urls::string_view pattern, const std::shared_ptr<AnyResource> &resource) {
|
||||
if (pattern.starts_with("/")) pattern.remove_prefix(1);
|
||||
auto segements = boost::urls::grammar::parse(pattern, templatePathRule);
|
||||
if (!segements) {
|
||||
segements.value();
|
||||
}
|
||||
auto iterator = segements->begin();
|
||||
auto end = segements->end();
|
||||
auto currentNode = &m_nodes.front();
|
||||
int level = 0;
|
||||
while (iterator != end) {
|
||||
boost::urls::string_view segement = (*iterator).string();
|
||||
if (segement == ".") {
|
||||
++iterator;
|
||||
continue;
|
||||
} else if (segement == "..") {
|
||||
if (currentNode == &m_nodes.front()) {
|
||||
--level;
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
std::size_t parentIndex = currentNode->parentIndex;
|
||||
if (currentNode == &m_nodes.back() && !currentNode->resource && currentNode->childIndexes.empty()) {
|
||||
auto p = &m_nodes[parentIndex];
|
||||
std::size_t currentIndex = currentNode - m_nodes.data();
|
||||
p->childIndexes.erase(std::remove(p->childIndexes.begin(), p->childIndexes.end(), currentIndex));
|
||||
m_nodes.pop_back();
|
||||
}
|
||||
currentNode = &m_nodes[parentIndex];
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
if (level < 0) {
|
||||
++level;
|
||||
++iterator;
|
||||
continue;
|
||||
}
|
||||
auto cit = std::find_if(currentNode->childIndexes.begin(), currentNode->childIndexes.end(),
|
||||
[this, &iterator](std::size_t ci) -> bool { return m_nodes[ci].segment == *iterator; });
|
||||
if (cit != currentNode->childIndexes.end()) {
|
||||
currentNode = &m_nodes[*cit];
|
||||
} else {
|
||||
SegementNode child;
|
||||
child.segment = *iterator;
|
||||
std::size_t currentIndex = currentNode - m_nodes.data();
|
||||
child.parentIndex = currentIndex;
|
||||
m_nodes.push_back(std::move(child));
|
||||
m_nodes[currentIndex].childIndexes.push_back(m_nodes.size() - 1);
|
||||
if (m_nodes[currentIndex].childIndexes.size() > 1) {
|
||||
// keep nodes sorted
|
||||
auto &cs = m_nodes[currentIndex].childIndexes;
|
||||
std::size_t n = cs.size() - 1;
|
||||
while (n) {
|
||||
if (m_nodes[cs.begin()[n]].segment < m_nodes[cs.begin()[n - 1]].segment)
|
||||
std::swap(cs.begin()[n], cs.begin()[n - 1]);
|
||||
else
|
||||
break;
|
||||
--n;
|
||||
}
|
||||
}
|
||||
currentNode = &m_nodes.back();
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
if (level != 0) {
|
||||
boost::urls::detail::throw_invalid_argument();
|
||||
}
|
||||
currentNode->resource = resource;
|
||||
currentNode->templatePath = pattern;
|
||||
}
|
||||
|
||||
const AnyResource *UrlRouterPrivate::findImpl(boost::urls::segments_encoded_view path,
|
||||
boost::urls::string_view *&matches,
|
||||
boost::urls::string_view *&ids) const noexcept {
|
||||
if (path.empty()) path = boost::urls::segments_encoded_view("./");
|
||||
const SegementNode *p = tryMatch(path.begin(), path.end(), &m_nodes.front(), 0, matches, ids);
|
||||
if (p) return p->resource.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SegementNode *UrlRouterPrivate::tryMatch(boost::urls::segments_encoded_base::const_iterator it,
|
||||
boost::urls::segments_encoded_base::const_iterator end,
|
||||
const SegementNode *current, int level,
|
||||
boost::urls::string_view *&matches,
|
||||
boost::urls::string_view *&ids) const {
|
||||
while (it != end) {
|
||||
boost::urls::pct_string_view s = *it;
|
||||
if (*s == ".") {
|
||||
// ignore segment
|
||||
++it;
|
||||
continue;
|
||||
} else if (*s == "..") { // move back to the parent node
|
||||
++it;
|
||||
if (level <= 0 && current != &m_nodes.front()) {
|
||||
if (!current->segment.isLiteral()) {
|
||||
--matches;
|
||||
--ids;
|
||||
}
|
||||
current = &m_nodes[current->parentIndex];
|
||||
} else
|
||||
// there's no parent, so we
|
||||
// discount that from the implicit
|
||||
// tree beyond terminals
|
||||
--level;
|
||||
continue;
|
||||
}
|
||||
if (level < 0) {
|
||||
++level;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool branch = false;
|
||||
if (current->childIndexes.size() > 1) {
|
||||
int branches_lb = 0;
|
||||
for (auto i : current->childIndexes) {
|
||||
auto &c = m_nodes[i];
|
||||
if (c.segment.isLiteral() || !c.segment.hasModifier()) {
|
||||
// a literal path counts only
|
||||
// if it matches
|
||||
branches_lb += c.segment.match(s);
|
||||
} else {
|
||||
// everything not matching
|
||||
// a single path counts as
|
||||
// more than one path already
|
||||
branches_lb = 2;
|
||||
}
|
||||
if (branches_lb > 1) {
|
||||
// already know we need to
|
||||
// branch
|
||||
branch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const SegementNode *r = nullptr;
|
||||
bool matchAny = false;
|
||||
for (auto index : current->childIndexes) {
|
||||
auto &child = m_nodes[index];
|
||||
if (!child.segment.match(s)) continue;
|
||||
if (child.segment.isLiteral()) {
|
||||
if (branch) {
|
||||
r = tryMatch(std::next(it), end, &child, level, matches, ids);
|
||||
if (r) break;
|
||||
} else {
|
||||
current = &child;
|
||||
matchAny = true;
|
||||
break;
|
||||
}
|
||||
} else if (!child.segment.hasModifier()) {
|
||||
if (branch) {
|
||||
auto matches0 = matches;
|
||||
auto ids0 = ids;
|
||||
*matches++ = *it;
|
||||
*ids++ = child.segment.id();
|
||||
r = tryMatch(std::next(it), end, &child, level, matches, ids);
|
||||
if (r) {
|
||||
break;
|
||||
} else {
|
||||
// rewind
|
||||
matches = matches0;
|
||||
ids = ids0;
|
||||
}
|
||||
} else {
|
||||
// only path possible
|
||||
*matches++ = *it;
|
||||
*ids++ = child.segment.id();
|
||||
current = &child;
|
||||
matchAny = true;
|
||||
break;
|
||||
}
|
||||
} else if (child.segment.isOptional()) {
|
||||
auto matches0 = matches;
|
||||
auto ids0 = ids;
|
||||
*matches++ = *it;
|
||||
*ids++ = child.segment.id();
|
||||
r = tryMatch(std::next(it), end, &child, level, matches, ids);
|
||||
if (r) break;
|
||||
matches = matches0;
|
||||
ids = ids0;
|
||||
*matches++ = {};
|
||||
*ids++ = child.segment.id();
|
||||
r = tryMatch(it, end, &child, level, matches, ids);
|
||||
if (r) break;
|
||||
matches = matches0;
|
||||
ids = ids0;
|
||||
} else {
|
||||
auto first = it;
|
||||
std::size_t ndotdot = 0;
|
||||
std::size_t nnondot = 0;
|
||||
auto it1 = it;
|
||||
while (it1 != end) {
|
||||
if (*it1 == "..") {
|
||||
++ndotdot;
|
||||
if (ndotdot >= (nnondot + child.segment.isStar())) break;
|
||||
} else if (*it1 != ".") {
|
||||
++nnondot;
|
||||
}
|
||||
++it1;
|
||||
}
|
||||
if (it1 != end) break;
|
||||
auto matches0 = matches;
|
||||
auto ids0 = ids;
|
||||
*matches++ = *it;
|
||||
*ids++ = child.segment.id();
|
||||
if (child.segment.isPlus()) {
|
||||
++first;
|
||||
}
|
||||
auto start = end;
|
||||
while (start != first) {
|
||||
r = tryMatch(start, end, &child, level, matches, ids);
|
||||
if (r) {
|
||||
boost::urls::string_view prev = *std::prev(start);
|
||||
*matches0 = {matches0->data(), prev.data() + prev.size()};
|
||||
break;
|
||||
}
|
||||
matches = matches0 + 1;
|
||||
ids = ids0 + 1;
|
||||
--start;
|
||||
}
|
||||
if (r) {
|
||||
break;
|
||||
}
|
||||
matches = matches0 + 1;
|
||||
ids = ids0 + 1;
|
||||
r = tryMatch(start, end, &child, level, matches, ids);
|
||||
if (r) {
|
||||
if (!child.segment.isPlus()) *matches0 = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (r) return r;
|
||||
if (!matchAny) ++level;
|
||||
++it;
|
||||
}
|
||||
if (level != 0) { // the path ended below or above an existing node
|
||||
return nullptr;
|
||||
}
|
||||
if (!current->resource) {
|
||||
return findOptionalResource(current, m_nodes, matches, ids);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
const SegementNode *UrlRouterPrivate::findOptionalResource(const SegementNode *root,
|
||||
const std::vector<SegementNode> &ns,
|
||||
boost::urls::string_view *&matches,
|
||||
boost::urls::string_view *&ids) {
|
||||
BOOST_ASSERT(root);
|
||||
if (root->resource) return root;
|
||||
BOOST_ASSERT(!root->childIndexes.empty());
|
||||
for (auto index : root->childIndexes) {
|
||||
auto &child = ns[index];
|
||||
if (!child.segment.isOptional() && !child.segment.isStar()) continue;
|
||||
// Child nodes are also potentially optional.
|
||||
auto matches0 = matches;
|
||||
auto ids0 = ids;
|
||||
*matches++ = {};
|
||||
*ids++ = child.segment.id();
|
||||
auto n = findOptionalResource(&child, ns, matches, ids);
|
||||
if (n) return n;
|
||||
matches = matches0;
|
||||
ids = ids0;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ChildIndexVector::empty() const {
|
||||
return m_size == 0;
|
||||
}
|
||||
|
||||
size_t ChildIndexVector::size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
size_t *ChildIndexVector::begin() {
|
||||
if (m_childIndexes) return m_childIndexes;
|
||||
return m_staticChildIndexes;
|
||||
}
|
||||
|
||||
const size_t *ChildIndexVector::begin() const {
|
||||
return const_cast<ChildIndexVector *>(this)->begin();
|
||||
}
|
||||
|
||||
size_t *ChildIndexVector::end() {
|
||||
return begin() + m_size;
|
||||
}
|
||||
|
||||
const size_t *ChildIndexVector::end() const {
|
||||
return const_cast<ChildIndexVector *>(this)->end();
|
||||
}
|
||||
|
||||
void ChildIndexVector::erase(size_t *it) {
|
||||
BOOST_ASSERT(it - begin() >= 0);
|
||||
std::memmove(it - 1, it, end() - it);
|
||||
--m_size;
|
||||
}
|
||||
|
||||
void ChildIndexVector::push_back(size_t v) {
|
||||
if (m_size == N && !m_childIndexes) {
|
||||
m_childIndexes = new std::size_t[N * 2];
|
||||
m_capcity = N * 2;
|
||||
std::memcpy(m_childIndexes, m_staticChildIndexes, N * sizeof(std::size_t));
|
||||
} else if (m_childIndexes && m_size == m_capcity) {
|
||||
auto *tmp = new std::size_t[m_capcity * 2];
|
||||
std::memcpy(tmp, m_childIndexes, m_capcity * sizeof(std::size_t));
|
||||
delete[] m_childIndexes;
|
||||
m_childIndexes = tmp;
|
||||
m_capcity = m_capcity * 2;
|
||||
}
|
||||
begin()[m_size++] = v;
|
||||
}
|
63
HttpProxy/UrlRouterPrivate.h
Normal file
63
HttpProxy/UrlRouterPrivate.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef __URLROUTERPRIVATE_H__
|
||||
#define __URLROUTERPRIVATE_H__
|
||||
|
||||
#include "TemplateSegmentRule.h"
|
||||
#include <boost/url/segments_encoded_view.hpp>
|
||||
#include <boost/url/string_view.hpp>
|
||||
#include <vector>
|
||||
|
||||
class AnyResource {
|
||||
public:
|
||||
virtual ~AnyResource() = default;
|
||||
virtual void const *get() const noexcept = 0;
|
||||
};
|
||||
|
||||
class ChildIndexVector {
|
||||
static constexpr std::size_t N = 5;
|
||||
|
||||
public:
|
||||
bool empty() const;
|
||||
std::size_t size() const;
|
||||
std::size_t *begin();
|
||||
const std::size_t *begin() const;
|
||||
std::size_t *end();
|
||||
const std::size_t *end() const;
|
||||
void erase(std::size_t *it);
|
||||
void push_back(std::size_t v);
|
||||
|
||||
private:
|
||||
std::size_t m_capcity{0};
|
||||
std::size_t m_size{0};
|
||||
std::size_t *m_childIndexes{nullptr};
|
||||
std::size_t m_staticChildIndexes[N]{};
|
||||
};
|
||||
|
||||
class SegementNode {
|
||||
public:
|
||||
static constexpr std::size_t npos{std::size_t(-1)};
|
||||
std::size_t parentIndex{npos};
|
||||
ChildIndexVector childIndexes;
|
||||
TemplateSegment segment;
|
||||
std::string templatePath;
|
||||
std::shared_ptr<AnyResource> resource;
|
||||
};
|
||||
|
||||
class UrlRouterPrivate {
|
||||
public:
|
||||
UrlRouterPrivate();
|
||||
void insertImpl(boost::urls::string_view pattern, const std::shared_ptr<AnyResource> &resource);
|
||||
const AnyResource *findImpl(boost::urls::segments_encoded_view path, boost::urls::string_view *&matches,
|
||||
boost::urls::string_view *&ids) const noexcept;
|
||||
|
||||
protected:
|
||||
SegementNode const *tryMatch(boost::urls::segments_encoded_view::const_iterator it,
|
||||
boost::urls::segments_encoded_view::const_iterator end, const SegementNode *current,
|
||||
int level, boost::urls::string_view *&matches, boost::urls::string_view *&ids) const;
|
||||
static SegementNode const *findOptionalResource(const SegementNode *root, std::vector<SegementNode> const &ns,
|
||||
boost::urls::string_view *&matches, boost::urls::string_view *&ids);
|
||||
|
||||
private:
|
||||
std::vector<SegementNode> m_nodes;
|
||||
};
|
||||
|
||||
#endif // __URLROUTERPRIVATE_H__
|
Loading…
Reference in New Issue
Block a user