use original code of router.
This commit is contained in:
parent
c1b636acee
commit
0edab61c09
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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__
|
@ -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;
|
||||
}
|
@ -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__
|
@ -1 +0,0 @@
|
||||
#include "UrlRouter.h"
|
@ -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__
|
@ -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;
|
||||
}
|
@ -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__
|
949
HttpProxy/detail/impl/router.cpp
Normal file
949
HttpProxy/detail/impl/router.cpp
Normal 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
|
58
HttpProxy/detail/router.hpp
Normal file
58
HttpProxy/detail/router.hpp
Normal 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
|
98
HttpProxy/impl/matches.cpp
Normal file
98
HttpProxy/impl/matches.cpp
Normal 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
75
HttpProxy/impl/router.hpp
Normal 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
159
HttpProxy/matches.hpp
Normal 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
90
HttpProxy/router.hpp
Normal 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
|
||||
|
Loading…
Reference in New Issue
Block a user