add class 'UrlRouter'.

This commit is contained in:
luocai 2023-07-21 15:28:59 +08:00
parent 4c1db38c8e
commit a6b3939777
15 changed files with 935 additions and 0 deletions

View File

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

View 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();
}

View 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

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

View 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();
});
}

View 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

View 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);
}

View 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__

View 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;
}

View 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
View File

@ -0,0 +1 @@
#include "UrlRouter.h"

47
HttpProxy/UrlRouter.h Normal file
View 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__

View 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;
}

View 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__