use original code of router.

This commit is contained in:
amass 2023-12-30 22:09:18 +08:00
parent c1b636acee
commit 0edab61c09
15 changed files with 1436 additions and 666 deletions

View File

@ -1,3 +1,7 @@
project(HttpProxy
DESCRIPTION "router api is copy of boost_1_84_0/libs/url/example/router"
)
find_package(Boost COMPONENTS url REQUIRED)
add_library(HttpProxy
@ -5,10 +9,9 @@ add_library(HttpProxy
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
detail/impl/router.cpp
impl/matches.cpp
)
target_include_directories(HttpProxy

View File

@ -1,12 +0,0 @@
#include "TemplateMatchs.h"
TemplateMatchStorageBase::const_reference TemplateMatchStorageBase::at(boost::core::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::core::string_view id) const {
return at(id);
}

View File

@ -1,57 +0,0 @@
#ifndef __TEMPLATEMATCHS_H__
#define __TEMPLATEMATCHS_H__
#include <boost/url/string_view.hpp>
class TemplateMatchStorageBase {
public:
using const_reference = boost::core::string_view const &;
virtual boost::core::string_view *matches() = 0;
virtual const boost::core::string_view *matches() const = 0;
virtual boost::core::string_view *ids() = 0;
virtual const boost::core::string_view *ids() const = 0;
virtual std::size_t size() const = 0;
virtual void resize(std::size_t) = 0;
const_reference at(boost::core::string_view id) const;
const_reference operator[](boost::core::string_view id) const;
};
template <std::size_t N = 20>
class TemplateMatchStorage : public TemplateMatchStorageBase {
public:
boost::core::string_view *matches() final {
return m_matches;
}
virtual const boost::core::string_view *matches() const final {
return m_matches;
}
boost::core::string_view *ids() final {
return m_ids;
}
const boost::core::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::core::string_view m_matches[N];
boost::core::string_view m_ids[N];
std::size_t m_size;
};
using TemplateMatches = TemplateMatchStorage<20>;
#endif // __TEMPLATEMATCHS_H__

View File

@ -1,96 +0,0 @@
#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::system::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::core::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::core::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::core::string_view TemplateSegment::id() const {
BOOST_ASSERT(!isLiteral());
boost::core::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::core::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

@ -1,63 +0,0 @@
#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>
#include <boost/url/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::core::string_view id() const;
boost::core::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::system::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__

View File

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

View File

@ -1,47 +0,0 @@
#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::core::string_view *matches_it = matches.matches();
boost::core::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

@ -1,323 +0,0 @@
#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::core::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::core::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::core::string_view *&matches,
boost::core::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::core::string_view *&matches,
boost::core::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::core::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::core::string_view *&matches,
boost::core::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

@ -1,63 +0,0 @@
#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::core::string_view pattern, const std::shared_ptr<AnyResource> &resource);
const AnyResource *findImpl(boost::urls::segments_encoded_view path, boost::core::string_view *&matches,
boost::core::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::core::string_view *&matches, boost::core::string_view *&ids) const;
static SegementNode const *findOptionalResource(const SegementNode *root, std::vector<SegementNode> const &ns,
boost::core::string_view *&matches, boost::core::string_view *&ids);
private:
std::vector<SegementNode> m_nodes;
};
#endif // __URLROUTERPRIVATE_H__

View File

@ -0,0 +1,949 @@
//
// Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#ifndef BOOST_URL_DETAIL_ROUTER_IPP
#define BOOST_URL_DETAIL_ROUTER_IPP
#include "../router.hpp"
#include <boost/url/decode_view.hpp>
#include <boost/url/grammar/alnum_chars.hpp>
#include <boost/url/grammar/alpha_chars.hpp>
#include <boost/url/grammar/lut_chars.hpp>
#include <boost/url/grammar/token_rule.hpp>
#include <boost/url/grammar/variant_rule.hpp>
#include <boost/url/rfc/detail/path_rules.hpp>
#include <boost/url/detail/replacement_field_rule.hpp>
#include <vector>
namespace boost {
namespace urls {
namespace detail {
// A path segment template
class segment_template
{
enum class modifier : unsigned char
{
none,
// {id?}
optional,
// {id*}
star,
// {id+}
plus
};
std::string str_;
bool is_literal_ = true;
modifier modifier_ = modifier::none;
friend struct segment_template_rule_t;
public:
segment_template() = default;
bool
match(pct_string_view seg) const;
core::string_view
string() const
{
return str_;
}
core::string_view
id() const;
bool
empty() const
{
return str_.empty();
}
bool
is_literal() const
{
return is_literal_;
}
bool
has_modifier() const
{
return !is_literal() &&
modifier_ != modifier::none;
}
bool
is_optional() const
{
return modifier_ == modifier::optional;
}
bool
is_star() const
{
return modifier_ == modifier::star;
}
bool
is_plus() const
{
return modifier_ == modifier::plus;
}
friend
bool operator==(
segment_template const& a,
segment_template const& b)
{
if (a.is_literal_ != b.is_literal_)
return false;
if (a.is_literal_)
return a.str_ == b.str_;
return a.modifier_ == b.modifier_;
}
// segments have precedence:
// - literal
// - unique
// - optional
// - plus
// - star
friend
bool operator<(
segment_template const& a,
segment_template const& b)
{
if (b.is_literal())
return false;
if (a.is_literal())
return !b.is_literal();
return a.modifier_ < b.modifier_;
}
};
// A segment template is either a literal string
// or a replacement field (as in a format_string).
// Fields cannot contain format specs and might
// have one of the following modifiers:
// - ?: optional segment
// - *: zero or more segments
// - +: one or more segments
struct segment_template_rule_t
{
using value_type = segment_template;
system::result<value_type>
parse(
char const*& it,
char const* end
) const noexcept;
};
constexpr auto segment_template_rule = segment_template_rule_t{};
constexpr auto path_template_rule =
grammar::tuple_rule(
grammar::squelch(
grammar::optional_rule(
grammar::delim_rule('/'))),
grammar::range_rule(
segment_template_rule,
grammar::tuple_rule(
grammar::squelch(grammar::delim_rule('/')),
segment_template_rule)));
bool
segment_template::
match(pct_string_view seg) const
{
if (is_literal_)
return *seg == str_;
// other nodes match any string
return true;
}
core::string_view
segment_template::
id() const
{
// if (is_literal_) return {};
BOOST_ASSERT(!is_literal());
core::string_view r = {str_};
r.remove_prefix(1);
r.remove_suffix(1);
if (r.ends_with('?') ||
r.ends_with('+') ||
r.ends_with('*'))
r.remove_suffix(1);
return r;
}
auto
segment_template_rule_t::
parse(
char const*& it,
char const* end) const noexcept
-> system::result<value_type>
{
segment_template t;
if (it != end &&
*it == '{')
{
// replacement field
auto it0 = it;
++it;
auto send =
grammar::find_if(
it, end, grammar::lut_chars('}'));
if (send != end)
{
core::string_view s(it, send);
static constexpr auto modifiers_cs =
grammar::lut_chars("?*+");
static constexpr auto id_rule =
grammar::tuple_rule(
grammar::optional_rule(
arg_id_rule),
grammar::optional_rule(
grammar::delim_rule(modifiers_cs)));
if (s.empty() ||
grammar::parse(s, id_rule))
{
it = send + 1;
t.str_ = core::string_view(it0, send + 1);
t.is_literal_ = false;
if (s.ends_with('?'))
t.modifier_ =
segment_template::modifier::optional;
else if (s.ends_with('*'))
t.modifier_ =
segment_template::modifier::star;
else if (s.ends_with('+'))
t.modifier_ =
segment_template::modifier::plus;
return t;
}
}
it = it0;
}
// literal segment
auto rv = grammar::parse(
it, end, urls::detail::segment_rule);
BOOST_ASSERT(rv);
rv->decode({}, urls::string_token::assign_to(t.str_));
t.is_literal_ = true;
return t;
}
// a small vector for child nodes...
// we shouldn't expect many children per node, and
// we don't want to allocate for that. But we also
// cannot cap the max number of child nodes because
// especially the root nodes can potentially an
// exponentially higher number of child nodes.
class child_idx_vector
{
static constexpr std::size_t N = 5;
std::size_t static_child_idx_[N]{};
std::size_t* child_idx{nullptr};
std::size_t size_{0};
std::size_t cap_{0};
public:
~child_idx_vector()
{
delete[] child_idx;
}
child_idx_vector() = default;
child_idx_vector(child_idx_vector const& other)
: size_{other.size_}
, cap_{other.cap_}
{
if (other.child_idx)
{
child_idx = new std::size_t[cap_];
std::memcpy(child_idx, other.child_idx, size_ * sizeof(std::size_t));
return;
}
std::memcpy(static_child_idx_, other.static_child_idx_, size_ * sizeof(std::size_t));
}
child_idx_vector(child_idx_vector&& other)
: child_idx{other.child_idx}
, size_{other.size_}
, cap_{other.cap_}
{
std::memcpy(static_child_idx_, other.static_child_idx_, N);
other.child_idx = nullptr;
}
bool
empty() const
{
return size_ == 0;
}
std::size_t
size() const
{
return size_;
}
std::size_t*
begin()
{
if (child_idx)
return child_idx;
return static_child_idx_;
}
std::size_t*
end()
{
return begin() + size_;
}
std::size_t const*
begin() const
{
if (child_idx)
return child_idx;
return static_child_idx_;
}
std::size_t const*
end() const
{
return begin() + size_;
}
void
erase(std::size_t* it)
{
BOOST_ASSERT(it - begin() >= 0);
std::memmove(it - 1, it, end() - it);
--size_;
}
void
push_back(std::size_t v)
{
if (size_ == N && !child_idx)
{
child_idx = new std::size_t[N*2];
cap_ = N*2;
std::memcpy(child_idx, static_child_idx_, N * sizeof(std::size_t));
}
else if (child_idx && size_ == cap_)
{
auto* tmp = new std::size_t[cap_*2];
std::memcpy(tmp, child_idx, cap_ * sizeof(std::size_t));
delete[] child_idx;
child_idx = tmp;
cap_ = cap_*2;
}
begin()[size_++] = v;
}
};
// A node in the resource tree
// Each segment in the resource tree might be
// associated with
struct node
{
static constexpr std::size_t npos{std::size_t(-1)};
// literal segment or replacement field
detail::segment_template seg{};
// A pointer to the resource
router_base::any_resource const* resource{nullptr};
// The complete match for the resource
std::string path_template;
// Index of the parent node in the
// implementation pool of nodes
std::size_t parent_idx{npos};
// Index of child nodes in the pool
detail::child_idx_vector child_idx;
};
class impl
{
// Pool of nodes in the resource tree
std::vector<node> nodes_;
public:
impl()
{
// root node with no resource
nodes_.push_back(node{});
}
~impl()
{
for (auto &r: nodes_)
delete r.resource;
}
// include a node for a resource
void
insert_impl(
core::string_view path,
router_base::any_resource const* v);
// match a node and return the element
router_base::any_resource const*
find_impl(
segments_encoded_view path,
core::string_view*& matches,
core::string_view*& ids) const;
private:
// try to match from this root node
node const*
try_match(
segments_encoded_view::const_iterator it,
segments_encoded_view::const_iterator end,
node const* root,
int level,
core::string_view*& matches,
core::string_view*& ids) const;
// check if a node has a resource when we
// also consider optional paths through
// the child nodes.
static
node const*
find_optional_resource(
const node* root,
std::vector<node> const& ns,
core::string_view*& matches,
core::string_view*& ids);
};
node const*
impl::
find_optional_resource(
const node* root,
std::vector<node> const& ns,
core::string_view*& matches,
core::string_view*& ids)
{
BOOST_ASSERT(root);
if (root->resource)
return root;
BOOST_ASSERT(!root->child_idx.empty());
for (auto i: root->child_idx)
{
auto& c = ns[i];
if (!c.seg.is_optional() &&
!c.seg.is_star())
continue;
// Child nodes are also
// potentially optional.
auto matches0 = matches;
auto ids0 = ids;
*matches++ = {};
*ids++ = c.seg.id();
auto n = find_optional_resource(
&c, ns, matches, ids);
if (n)
return n;
matches = matches0;
ids = ids0;
}
return nullptr;
}
void
impl::
insert_impl(
core::string_view path,
router_base::any_resource const* v)
{
// Parse dynamic route segments
if (path.starts_with("/"))
path.remove_prefix(1);
auto segsr =
grammar::parse(path, detail::path_template_rule);
if (!segsr)
{
delete v;
segsr.value();
}
auto segs = *segsr;
auto it = segs.begin();
auto end = segs.end();
// Iterate existing nodes
node* cur = &nodes_.front();
int level = 0;
while (it != end)
{
core::string_view seg = (*it).string();
if (seg == ".")
{
++it;
continue;
}
if (seg == "..")
{
// discount unmatched leaf or
// keep track of levels behind root
if (cur == &nodes_.front())
{
--level;
++it;
continue;
}
// move to parent deleting current
// if it carries no resource
std::size_t p_idx = cur->parent_idx;
if (cur == &nodes_.back() &&
!cur->resource &&
cur->child_idx.empty())
{
node* p = &nodes_[p_idx];
std::size_t cur_idx = cur - nodes_.data();
p->child_idx.erase(
std::remove(
p->child_idx.begin(),
p->child_idx.end(),
cur_idx));
nodes_.pop_back();
}
cur = &nodes_[p_idx];
++it;
continue;
}
// discount unmatched root parent
if (level < 0)
{
++level;
++it;
continue;
}
// look for child
auto cit = std::find_if(
cur->child_idx.begin(),
cur->child_idx.end(),
[this, &it](std::size_t ci) -> bool
{
return nodes_[ci].seg == *it;
});
if (cit != cur->child_idx.end())
{
// move to existing child
cur = &nodes_[*cit];
}
else
{
// create child if it doesn't exist
node child;
child.seg = *it;
std::size_t cur_id = cur - nodes_.data();
child.parent_idx = cur_id;
nodes_.push_back(std::move(child));
nodes_[cur_id].child_idx.push_back(nodes_.size() - 1);
if (nodes_[cur_id].child_idx.size() > 1)
{
// keep nodes sorted
auto& cs = nodes_[cur_id].child_idx;
std::size_t n = cs.size() - 1;
while (n)
{
if (nodes_[cs.begin()[n]].seg < nodes_[cs.begin()[n - 1]].seg)
std::swap(cs.begin()[n], cs.begin()[n - 1]);
else
break;
--n;
}
}
cur = &nodes_.back();
}
++it;
}
if (level != 0)
{
delete v;
urls::detail::throw_invalid_argument();
}
cur->resource = v;
cur->path_template = path;
}
node const*
impl::
try_match(
segments_encoded_view::const_iterator it,
segments_encoded_view::const_iterator end,
node const* cur,
int level,
core::string_view*& matches,
core::string_view*& ids) const
{
while (it != end)
{
pct_string_view s = *it;
if (*s == ".")
{
// ignore segment
++it;
continue;
}
if (*s == "..")
{
// move back to the parent node
++it;
if (level <= 0 &&
cur != &nodes_.front())
{
if (!cur->seg.is_literal())
{
--matches;
--ids;
}
cur = &nodes_[cur->parent_idx];
}
else
// there's no parent, so we
// discount that from the implicit
// tree beyond terminals
--level;
continue;
}
// we are in the implicit tree above the
// root, so discount that as a level
if (level < 0)
{
++level;
++it;
continue;
}
// calculate the lower bound on the
// possible number of branches to
// determine if we need to branch.
// We branch when we might have more than
// one child matching node at this level.
// If so, we need to potentially branch
// to find which path leads to a valid
// resource. Otherwise, we can just
// consume the node and input without
// any recursive function calls.
bool branch = false;
if (cur->child_idx.size() > 1)
{
int branches_lb = 0;
for (auto i: cur->child_idx)
{
auto& c = nodes_[i];
if (c.seg.is_literal() ||
!c.seg.has_modifier())
{
// a literal path counts only
// if it matches
branches_lb += c.seg.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;
}
}
}
// attempt to match each child node
node const* r = nullptr;
bool match_any = false;
for (auto i: cur->child_idx)
{
auto& c = nodes_[i];
if (c.seg.match(s))
{
if (c.seg.is_literal())
{
// just continue from the
// next segment
if (branch)
{
r = try_match(
std::next(it), end,
&c, level,
matches, ids);
if (r)
break;
}
else
{
cur = &c;
match_any = true;
break;
}
}
else if (!c.seg.has_modifier())
{
// just continue from the
// next segment
if (branch)
{
auto matches0 = matches;
auto ids0 = ids;
*matches++ = *it;
*ids++ = c.seg.id();
r = try_match(
std::next(it), end, &c,
level, matches, ids);
if (r)
{
break;
}
else
{
// rewind
matches = matches0;
ids = ids0;
}
}
else
{
// only path possible
*matches++ = *it;
*ids++ = c.seg.id();
cur = &c;
match_any = true;
break;
}
}
else if (c.seg.is_optional())
{
// attempt to match by ignoring
// and not ignoring the segment.
// we first try the complete
// continuation consuming the
// input, which is the
// longest and most likely
// match
auto matches0 = matches;
auto ids0 = ids;
*matches++ = *it;
*ids++ = c.seg.id();
r = try_match(
std::next(it), end,
&c, level, matches, ids);
if (r)
break;
// rewind
matches = matches0;
ids = ids0;
// try complete continuation
// consuming no segment
*matches++ = {};
*ids++ = c.seg.id();
r = try_match(
it, end, &c,
level, matches, ids);
if (r)
break;
// rewind
matches = matches0;
ids = ids0;
}
else
{
// check if the next segments
// won't send us to a parent
// directory
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 + c.seg.is_star()))
break;
}
else if (*it1 != ".")
{
++nnondot;
}
++it1;
}
if (it1 != end)
break;
// attempt to match many
// segments
auto matches0 = matches;
auto ids0 = ids;
*matches++ = *it;
*ids++ = c.seg.id();
// if this is a plus seg, we
// already consumed the first
// segment
if (c.seg.is_plus())
{
++first;
}
// {*} is usually the last
// match in a path.
// try complete continuation
// match for every subrange
// from {last, last} to
// {first, last}.
// We also try {last, last}
// first because it is the
// longest match.
auto start = end;
while (start != first)
{
r = try_match(
start, end, &c,
level, matches, ids);
if (r)
{
core::string_view prev = *std::prev(start);
*matches0 = {
matches0->data(),
prev.data() + prev.size()};
break;
}
matches = matches0 + 1;
ids = ids0 + 1;
--start;
}
if (r)
{
break;
}
// start == first
matches = matches0 + 1;
ids = ids0 + 1;
r = try_match(
start, end, &c,
level, matches, ids);
if (r)
{
if (!c.seg.is_plus())
*matches0 = {};
break;
}
}
}
}
// r represent we already found a terminal
// node which is a match
if (r)
return r;
// if we couldn't match anything, we go
// one level up in the implicit tree
// because the path might still have a
// "..".
if (!match_any)
++level;
++it;
}
if (level != 0)
{
// the path ended below or above an
// existing node
return nullptr;
}
if (!cur->resource)
{
// we consumed all the input and reached
// a node with no resource, but it might
// still have child optional segments
// with resources we can reach without
// consuming any input
return find_optional_resource(
cur, nodes_, matches, ids);
}
return cur;
}
router_base::any_resource const*
impl::
find_impl(
segments_encoded_view path,
core::string_view*& matches,
core::string_view*& ids) const
{
// parse_path is inconsistent for empty paths
if (path.empty())
path = segments_encoded_view("./");
// Iterate nodes from the root
node const*p = try_match(
path.begin(), path.end(),
&nodes_.front(), 0,
matches, ids);
if (p)
return p->resource;
return nullptr;
}
router_base::
router_base()
: impl_(new impl{}) {}
router_base::
~router_base()
{
delete reinterpret_cast<impl*>(impl_);
}
void
router_base::
insert_impl(
core::string_view s,
any_resource const* v)
{
reinterpret_cast<impl*>(impl_)
->insert_impl(s, v);
}
auto
router_base::
find_impl(
segments_encoded_view path,
core::string_view*& matches,
core::string_view*& ids) const noexcept
-> any_resource const*
{
return reinterpret_cast<impl*>(impl_)
->find_impl(path, matches, ids);
}
} // detail
} // urls
} // boost
#endif

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#ifndef BOOST_URL_DETAIL_ROUTER_HPP
#define BOOST_URL_DETAIL_ROUTER_HPP
#include <boost/url/pct_string_view.hpp>
#include <boost/url/segments_encoded_view.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 <string>
namespace boost {
namespace urls {
namespace detail {
class router_base
{
void* impl_{nullptr};
public:
// A type-erased router resource
struct any_resource
{
virtual ~any_resource() = default;
virtual void const* get() const noexcept = 0;
};
protected:
router_base();
virtual ~router_base();
void
insert_impl(
core::string_view s,
any_resource const* v);
any_resource const*
find_impl(
segments_encoded_view path,
core::string_view*& matches,
core::string_view*& names) const noexcept;
};
} // detail
} // urls
} // boost
#endif

View File

@ -0,0 +1,98 @@
//
// Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#include "../matches.hpp"
namespace boost {
namespace urls {
auto
matches_base::
at( size_type pos ) const
-> const_reference
{
if (pos < size())
{
return matches()[pos];
}
boost::throw_exception(
std::out_of_range(""));
}
auto
matches_base::
operator[]( size_type pos ) const
-> const_reference
{
BOOST_ASSERT(pos < size());
return matches()[pos];
}
auto
matches_base::
at( core::string_view id ) const
-> const_reference
{
for (std::size_t i = 0; i < size(); ++i)
{
if (ids()[i] == id)
return matches()[i];
}
boost::throw_exception(
std::out_of_range(""));
}
auto
matches_base::
operator[]( core::string_view id ) const
-> const_reference
{
return at(id);
}
auto
matches_base::
find( core::string_view id ) const
-> const_iterator
{
for (std::size_t i = 0; i < size(); ++i)
{
if (ids()[i] == id)
return matches() + i;
}
return matches() + size();
}
auto
matches_base::
begin() const
-> const_iterator
{
return &matches()[0];
}
auto
matches_base::
end() const
-> const_iterator
{
return &matches()[size()];
}
auto
matches_base::
empty() const noexcept
-> bool
{
return size() == 0;
}
} // urls
} // boost

75
HttpProxy/impl/router.hpp Normal file
View File

@ -0,0 +1,75 @@
//
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#include <boost/url/detail/except.hpp>
#include <boost/url/decode_view.hpp>
#include <boost/url/grammar/unsigned_rule.hpp>
#include <boost/mp11/algorithm.hpp>
namespace boost {
namespace urls {
template <class T>
template <class U>
void
router<T>::
insert(core::string_view pattern, U&& v)
{
BOOST_STATIC_ASSERT(
std::is_same<T, U>::value ||
std::is_convertible<U, T>::value ||
std::is_base_of<T, U>::value);
using U_ = typename std::decay<
typename std::conditional<
std::is_base_of<T, U>::value, U, T
>::type>::type;
struct impl : any_resource
{
U_ u;
explicit
impl(U&& u_)
: u(std::forward<U>(u_))
{
}
void const*
get() const noexcept override
{
return static_cast<T const*>(&u);
}
};
any_resource const* p = new impl(
std::forward<U>(v));
insert_impl( pattern, p );
}
template <class T>
T const*
router<T>::
find(segments_encoded_view path, matches_base& m) const noexcept
{
core::string_view* matches_it = m.matches();
core::string_view* ids_it = m.ids();
any_resource const* p = find_impl(
path, matches_it, ids_it );
if (p)
{
BOOST_ASSERT(matches_it >= m.matches());
m.resize(static_cast<std::size_t>(
matches_it - m.matches()));
return reinterpret_cast<
T const*>(p->get());
}
m.resize(0);
return nullptr;
}
} // urls
} // boost

159
HttpProxy/matches.hpp Normal file
View File

@ -0,0 +1,159 @@
//
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#ifndef BOOST_URL_MATCHES_HPP
#define BOOST_URL_MATCHES_HPP
#include <boost/url/detail/config.hpp>
#include <boost/url/string_view.hpp>
namespace boost {
namespace urls {
// Base route match results
class matches_base
{
public:
using iterator = core::string_view*;
using const_iterator = core::string_view const*;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = core::string_view&;
using const_reference = core::string_view const&;
using pointer = core::string_view*;
using const_pointer = core::string_view const*;
matches_base() = default;
virtual ~matches_base() = default;
virtual
core::string_view const*
matches() const = 0;
virtual
core::string_view const*
ids() const = 0;
virtual
core::string_view*
matches() = 0;
virtual
core::string_view*
ids() = 0;
virtual
std::size_t
size() const = 0;
virtual
void
resize(std::size_t) = 0;
const_reference
at( size_type pos ) const;
const_reference
at( core::string_view id ) const;
const_reference
operator[]( size_type pos ) const;
const_reference
operator[]( core::string_view id ) const;
const_iterator
find( core::string_view id ) const;
const_iterator
begin() const;
const_iterator
end() const;
bool
empty() const noexcept;
};
/// A range type with the match results
template <std::size_t N = 20>
class matches_storage
: public matches_base
{
core::string_view matches_storage_[N];
core::string_view ids_storage_[N];
std::size_t size_;
matches_storage(
core::string_view matches[N],
core::string_view ids[N],
std::size_t n)
{
for (std::size_t i = 0; i < n; ++i)
{
matches_storage_[i] = matches[i];
ids_storage_[i] = ids[i];
}
}
virtual
core::string_view*
matches() override
{
return matches_storage_;
}
virtual
core::string_view*
ids() override
{
return ids_storage_;
}
public:
matches_storage() = default;
virtual
core::string_view const*
matches() const override
{
return matches_storage_;
}
virtual
core::string_view const*
ids() const override
{
return ids_storage_;
}
virtual
std::size_t
size() const override
{
return size_;
}
virtual
void
resize(std::size_t n) override
{
size_ = n;
}
};
/// Default type for storing route match results
using matches = matches_storage<20>;
} // urls
} // boost
#endif

90
HttpProxy/router.hpp Normal file
View File

@ -0,0 +1,90 @@
//
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#ifndef BOOST_URL_ROUTER_HPP
#define BOOST_URL_ROUTER_HPP
#include <boost/url/detail/config.hpp>
#include <boost/url/parse_path.hpp>
#include "detail/router.hpp"
#include "matches.hpp"
namespace boost {
namespace urls {
/** A URL router.
This container matches static and dynamic
URL requests to an object which represents
how the it should be handled. These
values are usually callback functions.
@tparam T type of resource associated with
each path template
@tparam N maximum number of replacement fields
in a path template
@par Exception Safety
@li Functions marked `noexcept` provide the
no-throw guarantee, otherwise:
@li Functions which throw offer the strong
exception safety guarantee.
@see
@ref parse_absolute_uri,
@ref parse_relative_ref,
@ref parse_uri,
@ref parse_uri_reference,
@ref resolve.
*/
template <class T>
class router
: private detail::router_base
{
public:
/// Constructor
router() = default;
/** Route the specified URL path to a resource
@param path A url path with dynamic segments
@param resource A resource the path corresponds to
@see
https://fmt.dev/latest/syntax.html
*/
template <class U>
void
insert(core::string_view pattern, U&& v);
/** Match URL path to corresponding resource
@param request Request path
@return The match results
*/
T const*
find(segments_encoded_view path, matches_base& m) const noexcept;
#ifdef BOOST_URL_DOCS
/// @copydoc find
T const*
find(segments_encoded_view path, matches& m) const noexcept;
#endif
};
} // urls
} // boost
#include "impl/router.hpp"
#endif