Hello libart-seafire-routing
All checks were successful
on-push / build-and-test (push) Successful in 1m1s

This commit is contained in:
2025-10-18 00:44:28 +02:00
commit 5ce247e014
44 changed files with 1645 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,31 @@
name: on-push
on:
push:
tags-ignore:
- '*'
branches:
- '**'
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/art/infra/buildenv/x86_64-fedora_42-unified:latest
volumes:
- /build
steps:
- name: Configure repository access
run: |
git config --global http.$GITHUB_SERVER_URL/.extraheader "Authorization: token ${{ secrets.ACT_RUNNER_TOKEN }}"
- name: Configure build directory
run: |
bpkg create -d /build cc config.cxx=clang++ config.cc.coptions="-Wall -Werror -Wno-unknown-pragmas"
- name: Build package
run: |
cd /build
bpkg build --yes --trust-yes $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git##$GITHUB_SHA
- name: Test package
run: |
cd /build
b test

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: helloryan

32
.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
.bdep/
Doxyfile
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
README.md Normal file
View File

@@ -0,0 +1,14 @@
# libart-seafire-routing
![Build badge](https://code.helloryan.se/art/libart-seafire-routing/actions/workflows/on-push.yaml/badge.svg)
libart-seafire-routing is part of the Seafire HTTP/1.1 library for C++.
## Dedication
This project is dedicated to the memory of Sefyr.
## Sponsorship
You can sponsor the development of this project via Patreon. Read more
over at https://patreon.com/helloryan.

9
art/seafire/routing/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

View File

@@ -0,0 +1,43 @@
#include <art/seafire/routing/builder.hxx>
#include <art/seafire/routing/flatten.hxx>
#include <vector>
namespace art::seafire::routing
{
builder_t::
builder_t()
{}
std::list<virtual_host_t> const&
builder_t::
virtual_hosts() const
{
return _vhosts;
}
virtual_host_t&
builder_t::
add_virtual_host(std::string vhost)
{
_vhosts.emplace_back(std::move(vhost));
return _vhosts.back();
}
routing_table_t
builder_t::
build() const
{
std::vector<endpoint_t> endpoints;
for (auto const& vhost : _vhosts) {
for (auto const& r : vhost.routes()) {
flatten(endpoints, vhost.host(), vhost.middleware(), r);
}
}
return routing_table_t{std::move(endpoints)};
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,42 @@
#ifndef art__seafire__routing__builder_hxx_
#define art__seafire__routing__builder_hxx_
#include <art/seafire/routing/routing-table.hxx>
#include <art/seafire/routing/virtual-host.hxx>
#include <art/seafire/server/request-handler.hxx>
#include <list>
#include <string>
namespace art::seafire::routing
{
class builder_t
{
public:
builder_t();
builder_t(builder_t const&) = delete;
builder_t(builder_t&&) = delete;
std::list<virtual_host_t> const&
virtual_hosts() const;
virtual_host_t&
add_virtual_host(std::string);
routing_table_t
build() const;
builder_t& operator=(builder_t const&) = delete;
builder_t& operator=(builder_t&&) = delete;
private:
std::list<virtual_host_t> _vhosts;
};
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,67 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import impl_libs =+ libart-uri%lib{art-uri}
import intf_libs =+ libart-seafire-common%lib{art-seafire-common}
import intf_libs =+ libart-seafire-protocol%lib{art-seafire-protocol}
import intf_libs =+ libart-seafire-server%lib{art-seafire-server}
./: lib{art-seafire-routing}: libul{art-seafire-routing}
libul{art-seafire-routing}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{art-seafire-routing}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
$d/exe{$n}: libul{art-seafire-routing}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
# Export options.
#
lib{art-seafire-routing}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{art-seafire-routing}: bin.lib.version = "-$version.project_id"
else
lib{art-seafire-routing}: bin.lib.version = "-$version.major.$version.minor"
# Install into the art/seafire/routing/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/art/seafire/routing/
install.subdirs = true
}

View File

@@ -0,0 +1,15 @@
#include <art/seafire/routing/diagnostics.hxx>
namespace art::seafire::routing
{
/// Returns a reference to the routing diagnostic category.
///
common::diagnostics_t::category_t const&
routing_category()
{
static common::diagnostics_t::category_t category{"router"};
return category;
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,14 @@
#ifndef art__seafire__routing__diagnostics_hxx_
#define art__seafire__routing__diagnostics_hxx_
#include <art/seafire/common/diagnostics.hxx>
namespace art::seafire::routing
{
common::diagnostics_t::category_t const&
routing_category();
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,48 @@
#include <art/seafire/routing/endpoint.hxx>
namespace art::seafire::routing
{
endpoint_t::
endpoint_t(std::string host,
std::string path,
server::request_handler_t handler)
: host_{std::move(host)},
path_{std::move(path)},
handler_{std::move(handler)}
{}
std::string const&
endpoint_t::
host() const
{
return host_;
}
std::string const&
endpoint_t::
path() const
{
return path_;
}
server::request_handler_t const&
endpoint_t::
handler() const
{
return handler_;
}
std::ostream&
to_stream(std::ostream& o, endpoint_t const& ep)
{
return o << ep.host() << ": " << ep.path();
}
std::ostream&
operator<<(std::ostream& o, endpoint_t const& ep)
{
return to_stream(o, ep);
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,41 @@
#ifndef art__seafire__routing__endpoint_hxx_
#define art__seafire__routing__endpoint_hxx_
#include <art/seafire/server/request-handler.hxx>
#include <ostream>
#include <string>
namespace art::seafire::routing
{
class endpoint_t
{
public:
endpoint_t(std::string, std::string, server::request_handler_t);
std::string const&
host() const;
std::string const&
path() const;
server::request_handler_t const&
handler() const;
private:
std::string host_;
std::string path_;
server::request_handler_t handler_;
};
std::ostream&
to_stream(std::ostream&, endpoint_t const&);
std::ostream&
operator<<(std::ostream&, endpoint_t const&);
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,81 @@
#include <art/seafire/routing/flatten.hxx>
namespace art::seafire::routing
{
static
std::string
join_path(std::vector<std::string> const& segments)
{
std::string path{"/"};
bool first{true};
for (auto const& j : segments) {
if (j.empty()) {
continue;
}
if (first) {
first = false;
}
else {
path += "/";
}
path += j;
}
return path;
}
void
flatten(std::vector<endpoint_t>& endpoints,
std::string const& vhost,
std::vector<server::middleware_t> middlewares,
route_t const& route,
std::vector<std::string> segments)
{
segments.emplace_back(route.path());
// append any middlewares if we have any.
//
for (auto const& m : route.middleware()) {
middlewares.emplace_back(m);
}
// generate an endpoint for this route if we have a handler.
//
if (auto const& h = route.handler()) {
endpoints.emplace_back(vhost, join_path(segments), server::make_middleware(middlewares, *h));
}
// flatten any child routes.
//
for (auto const& child_route : route.children()) {
flatten(endpoints,
vhost,
middlewares,
child_route,
segments);
}
}
void
flatten(std::vector<endpoint_t>& endpoints,
std::string const& vhost,
std::vector<server::middleware_t> middlewares,
route_t const& r)
{
flatten(endpoints, vhost, middlewares, r, {});
}
std::vector<endpoint_t>
flatten(std::string const& vhost, route_t const& r)
{
std::vector<endpoint_t> endpoints;
flatten(endpoints, vhost, {}, r);
return endpoints;
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,32 @@
#ifndef art__seafire__routing__flatten_route_hxx_
#define art__seafire__routing__flatten_route_hxx_
#include <art/seafire/routing/endpoint.hxx>
#include <art/seafire/routing/route.hxx>
#include <art/seafire/server/middleware.hxx>
#include <vector>
namespace art::seafire::routing
{
void
flatten(std::vector<endpoint_t>& endpoints,
std::string const&,
std::vector<server::middleware_t>,
route_t const& r,
std::vector<std::string>);
void
flatten(std::vector<endpoint_t>&,
std::string const&,
std::vector<server::middleware_t>,
route_t const&);
std::vector<endpoint_t>
flatten(std::string const&, route_t const&);
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,63 @@
#ifndef art__seafire__routing__host_parameter_hxx_
#define art__seafire__routing__host_parameter_hxx_
#include <art/seafire/server/parameters.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/routing/parameters.hxx>
#include <optional>
namespace art::seafire::routing
{
template<
server::parameter_name_t Name,
typename ParameterType = server::string_parameter_t
>
class host_parameter_t
: public server::named_parameter_t<Name>
{
public:
using parameter_type = ParameterType;
using value_type = typename parameter_type::value_type;
host_parameter_t(std::optional<value_type> value)
: _value{std::move(value)}
{}
using server::named_parameter_t<Name>::name;
std::optional<value_type> const&
value() const
{
return _value;
}
operator std::optional<value_type> const&() const
{
return value();
}
std::optional<value_type> const*
operator->() const
{
return &value();
}
static
host_parameter_t<Name, ParameterType>
fetch(server::request_t& req)
{
auto v = req.extensions().use<host_parameters_t>().get(name());
return parameter_type::try_parse(v);
}
private:
std::optional<value_type> _value;
};
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,115 @@
#include <art/seafire/routing/match.hxx>
#include <sstream>
#include <vector>
namespace art::seafire::routing
{
bool
match(std::string const& subject,
std::string const& pattern,
char delim,
parameters_t& params)
{
parameters_t tmp_params;
// p/pend = pattern iterators.
//
auto p = pattern.begin();
auto const pend = pattern.end();
// s/send = subject iterators.
//
auto s = subject.begin();
auto const send = subject.end();
auto match_param = [&]()
{
// k/kend = local pattern iterators.
//
auto const k = p;
while (p != pend && '}' != *p) {
++p;
}
auto const kend = p;
++p;
if (k == kend) {
throw std::invalid_argument{"empty parameter name"};
}
// a greedy parameter will eat the rest of the subject.
//
bool const greedy{'*' == *k && 1 == std::distance(k, kend)};
// v/vend = local subject iterators.
//
auto const v = s;
while (s != send && (greedy || delim != *s)) {
++s;
}
auto const vend = s;
tmp_params.map().emplace(std::string{k, kend}, std::string{v, vend});
};
while (p != pend) {
if (*p == '{') {
++p;
match_param();
continue;
}
if (s == send) {
break;
}
if (*p != *s) {
return false;
}
++p;
++s;
}
if (p != pend || s != send) {
return false;
}
params = std::move(tmp_params);
return true;
}
std::string
render(std::string const& pattern, parameters_t& params)
{
std::stringstream str;
auto p = pattern.begin();
auto pend = pattern.end();
while (p != pend) {
if (*p == '{') {
++p;
auto const k = p;
while (p != pend && '}' != *p) {
++p;
}
auto const kend = p;
str << params.get(std::string{k, kend}).value_or("<unknown>");
}
else {
str << *p;
}
++p;
}
return str.str();
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,22 @@
#ifndef art__seafire__routing__match_hxx_
#define art__seafire__routing__match_hxx_
#include <art/seafire/routing/parameters.hxx>
#include <string>
namespace art::seafire::routing
{
bool
match(std::string const&,
std::string const&,
char,
parameters_t&);
std::string
render(std::string const&, parameters_t&);
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,30 @@
#include <art/seafire/routing/parameters.hxx>
namespace art::seafire::routing
{
parameters_t::map_type&
parameters_t::
map()
{
return _values;
}
parameters_t::map_type const&
parameters_t::
map() const
{
return _values;
}
std::optional<std::string>
parameters_t::
get(std::string const& key) const
{
if (auto it = map().find(key); it != map().end())
return it->second;
return std::nullopt;
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,56 @@
#ifndef seafire_routing__parameters_hxx_
#define seafire_routing__parameters_hxx_
#include <map>
#include <optional>
#include <string>
#include <utility>
namespace art::seafire::routing
{
class parameters_t
{
public:
using map_type = std::map<std::string, std::string>;
parameters_t() = default;
parameters_t(map_type values)
: _values{std::move(values)}
{}
map_type&
map();
map_type const&
map() const;
std::optional<std::string>
get(std::string const&) const;
private:
map_type _values;
};
class host_parameters_t
: public parameters_t
{
public:
using parameters_t::parameters_t;
};
class route_parameters_t
: public parameters_t
{
public:
using parameters_t::parameters_t;
};
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,63 @@
#ifndef art__seafire__routing__route_parameter_hxx_
#define art__seafire__routing__route_parameter_hxx_
#include <art/seafire/server/parameters.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/routing/parameters.hxx>
#include <optional>
namespace art::seafire::routing
{
template<
server::parameter_name_t Name,
typename ParameterType = server::string_parameter_t
>
class route_parameter_t
: public server::named_parameter_t<Name>
{
public:
using parameter_type = ParameterType;
using value_type = typename parameter_type::value_type;
route_parameter_t(std::optional<value_type> value)
: _value{std::move(value)}
{}
using server::named_parameter_t<Name>::name;
std::optional<value_type> const&
value() const
{
return _value;
}
operator std::optional<value_type> const&() const
{
return value();
}
std::optional<value_type> const*
operator->() const
{
return &value();
}
static
route_parameter_t<Name, ParameterType>
fetch(server::request_t& req)
{
auto v = req.extensions().use<route_parameters_t>().get(name());
return parameter_type::try_parse(v);
}
private:
std::optional<value_type> _value;
};
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,131 @@
#include <art/seafire/routing/route.hxx>
#include <stdexcept>
namespace art::seafire::routing
{
static
std::string
validate_path(std::string path)
{
if (!path.empty()) {
if (path.front() == '/') {
throw std::invalid_argument{"route path must not begin with '/'"};
}
if (path.back() == '/') {
throw std::invalid_argument{"route path must not end with '/'"};
}
}
return path;
}
route_t::
route_t() = default;
route_t::
route_t(std::string path)
: path_{validate_path(std::move(path))}
{}
route_t::
route_t(std::string path, server::request_handler_t handler)
: path_{validate_path(std::move(path))}, handler_{std::move(handler)}
{}
std::string const&
route_t::
path() const
{
return path_;
}
std::vector<server::middleware_t> const&
route_t::
middleware() const
{
return middleware_;
}
std::optional<server::request_handler_t> const&
route_t::
handler() const
{
return handler_;
}
std::list<route_t> const&
route_t::
children() const
{
return children_;
}
void
route_t::
use(server::middleware_t m)
{
middleware_.emplace_back(std::move(m));
}
route_t&
route_t::
add_route()
{
children_.emplace_back();
return children_.back();
}
route_t&
route_t::
add_route(std::string path)
{
children_.emplace_back(std::move(path));
return children_.back();
}
route_t&
route_t::
add_route(std::string path, server::request_handler_t handler)
{
children_.emplace_back(std::move(path), std::move(handler));
return children_.back();
}
std::ostream&
to_stream(std::ostream& o, route_t const& r, std::size_t indent)
{
if (indent > 0) {
o << std::string(indent, ' ');
o << "-> ";
}
o << "route: '" << r.path() << '\'';
if (!r.middleware().empty()) {
o << " (with middleware)";
}
if (!r.handler()) {
o << " (null handler)";
}
o << '\n';
for (auto const& child : r.children()) {
to_stream(o, child, indent + 2);
}
return o;
}
std::ostream&
operator<<(std::ostream& o, route_t const& route)
{
return to_stream(o, route, 0);
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,77 @@
#ifndef art__seafire__routing__route_hxx_
#define art__seafire__routing__route_hxx_
#include <art/seafire/routing/endpoint.hxx>
#include <art/seafire/server/middleware.hxx>
#include <art/seafire/server/request-handler.hxx>
#include <iostream>
#include <list>
#include <optional>
#include <vector>
namespace art::seafire::routing
{
class route_t
{
public:
route_t();
explicit
route_t(std::string);
route_t(std::string, server::request_handler_t);
route_t(route_t const&) = delete;
route_t(route_t&&) = delete;
std::string const&
path() const;
std::vector<server::middleware_t> const&
middleware() const;
std::optional<server::request_handler_t> const&
handler() const;
std::list<route_t> const&
children() const;
void
use(server::middleware_t);
route_t&
add_route();
route_t&
add_route(std::string);
route_t&
add_route(std::string, server::request_handler_t);
route_t& operator=(route_t const&) = delete;
route_t& operator=(route_t&&) = delete;
private:
std::string path_;
std::vector<server::middleware_t> middleware_;
std::optional<server::request_handler_t> handler_;
// must be std::list to prevent invalidation of references to
// individual routes.
//
std::list<route_t> children_;
};
std::ostream&
to_stream(std::ostream&, route_t const&, std::size_t);
std::ostream&
operator<<(std::ostream&, route_t const&);
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,122 @@
#include <art/seafire/routing/router.hxx>
#include <art/seafire/routing/diagnostics.hxx>
#include <art/seafire/protocol/rfc7230/host.hxx>
namespace art::seafire::routing
{
static
std::string
normalize_path(std::string const& path)
{
std::stringstream ipath{path};
std::vector<std::string> segments;
for (std::string segment; std::getline(ipath, segment, '/');) {
if (segment.empty()) {
continue;
}
if (segment == ".") {
continue;
}
if (segment == "..") {
if (!segments.empty()) {
segments.pop_back();
}
continue;
}
segments.push_back(segment);
}
std::string normalized;
for (auto const& j : segments) {
normalized += '/';
normalized += j;
}
return normalized.empty() ? "/" : normalized;
}
router_t::
router_t(common::diagnostics_t& diagnostics, routing_table_t table)
: diagnostics_{diagnostics}, rt_{std::move(table)}
{}
routing_table_t const&
router_t::
routing_table() const
{
return rt_;
}
void
router_t::
on_request(server::request_t& req, server::response_t& res) const
{
trace() << "on_request(...)";
auto host = get<art::seafire::protocol::rfc7230::host_t>(req);
if (!host) {
trace() << "host not present on request!";
res.send(server::common_error_t::not_found);
return;
}
auto hostname = host->hostname();
auto path = normalize_path(req.get_message().target_uri().path_str());
trace() << "locating endpoint:\n"
<< " -> hostname: " << hostname << '\n'
<< " -> path : " << path
;
auto result = routing_table().find_route(hostname, path);
if (!result) {
trace() << "endpoint for [" << path << "] not found";
res.send(server::common_error_t::not_found);
return;
}
auto trace_endpoint = [&result](common::diagnostics_t::proxy_t proxy)
{
proxy << "endpoint found!";
for (auto const& j : result->host_params.map())
proxy << "\n -> host param : " << j.first << " = " << j.second;
for (auto const& j : result->route_params.map())
proxy << "\n -> route param: " << j.first << " = " << j.second;
proxy << '\n';
};
trace_endpoint(trace());
req.extensions().extend(&res.allocator().alloc(result->host_params));
req.extensions().extend(&res.allocator().alloc(result->route_params));
result->handler.invoke(req, res);
}
void
router_t::
operator()(server::request_t& req, server::response_t& res) const
{
on_request(req, res);
}
common::diagnostics_t::proxy_t
router_t::
trace() const
{
return diagnostics_ << routing_category();
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,40 @@
#ifndef art__seafire__routing__router_hxx_
#define art__seafire__routing__router_hxx_
#include <art/seafire/routing/diagnostics.hxx>
#include <art/seafire/routing/routing-table.hxx>
#include <art/seafire/common/diagnostics.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
namespace art::seafire::routing
{
class router_t
{
public:
router_t(common::diagnostics_t&, routing_table_t);
routing_table_t const&
routing_table() const;
void
on_request(server::request_t&, server::response_t&) const;
void
operator()(server::request_t&, server::response_t&) const;
private:
common::diagnostics_t::proxy_t
trace() const;
common::diagnostics_t& diagnostics_;
routing_table_t rt_;
};
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,45 @@
#include <art/seafire/routing/routing-table.hxx>
#include <art/seafire/routing/match.hxx>
namespace art::seafire::routing
{
routing_table_t::
routing_table_t(std::vector<endpoint_t> endpoints)
: endpoints_{std::move(endpoints)}
{}
std::vector<endpoint_t> const&
routing_table_t::
endpoints() const
{
return endpoints_;
}
std::optional<routing_table_t::find_result_t>
routing_table_t::
find_route(std::string const& host, std::string const& path) const
{
for (auto const& e : endpoints()) {
host_parameters_t host_params;
if (!match(host, e.host(), '.', host_params)) {
continue;
}
route_parameters_t route_params;
if (match(path, e.path(), '/', route_params)) {
return find_result_t{
std::move(host_params),
std::move(route_params),
e.handler()
};
}
}
return std::nullopt;
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,50 @@
#ifndef art__seafire__routing__routing_table_hxx_
#define art__seafire__routing__routing_table_hxx_
#include <art/seafire/routing/endpoint.hxx>
#include <art/seafire/routing/parameters.hxx>
#include <art/seafire/routing/route.hxx>
#include <art/seafire/server/request-handler.hxx>
#include <list>
#include <optional>
#include <string>
#include <vector>
namespace art::seafire::routing
{
class routing_table_t
{
public:
struct find_result_t
{
host_parameters_t host_params;
route_parameters_t route_params;
server::request_handler_t const& handler;
};
explicit
routing_table_t(std::vector<endpoint_t>);
std::vector<endpoint_t> const&
endpoints() const;
std::optional<find_result_t>
find_route(std::string const&, std::string const&) const;
private:
static
bool
match_host(std::string const&, std::string const&);
std::vector<endpoint_t> endpoints_;
};
} // namespace art::seafire::routing
#endif

View File

@@ -0,0 +1,37 @@
#ifndef art__seafire__routing__version_hxx_
#define art__seafire__routing__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBART_SEAFIRE_ROUTING_VERSION $libart_seafire_routing.version.project_number$ULL
#define LIBART_SEAFIRE_ROUTING_VERSION_STR "$libart_seafire_routing.version.project$"
#define LIBART_SEAFIRE_ROUTING_VERSION_ID "$libart_seafire_routing.version.project_id$"
#define LIBART_SEAFIRE_ROUTING_VERSION_FULL "$libart_seafire_routing.version$"
#define LIBART_SEAFIRE_ROUTING_VERSION_MAJOR $libart_seafire_routing.version.major$
#define LIBART_SEAFIRE_ROUTING_VERSION_MINOR $libart_seafire_routing.version.minor$
#define LIBART_SEAFIRE_ROUTING_VERSION_PATCH $libart_seafire_routing.version.patch$
#define LIBART_SEAFIRE_ROUTING_PRE_RELEASE $libart_seafire_routing.version.pre_release$
#define LIBART_SEAFIRE_ROUTING_SNAPSHOT_SN $libart_seafire_routing.version.snapshot_sn$ULL
#define LIBART_SEAFIRE_ROUTING_SNAPSHOT_ID "$libart_seafire_routing.version.snapshot_id$"
#endif

View File

@@ -0,0 +1,100 @@
#include <art/seafire/routing/virtual-host.hxx>
namespace art::seafire::routing
{
static
std::string
validate_host(std::string host)
{
if (host.empty()) {
throw std::invalid_argument{"host must not be empty"};
}
if (host.front() == '.') {
throw std::invalid_argument{"host must not begin with '.'"};
}
if (host.back() == '.') {
throw std::invalid_argument{"host must not end with '.'"};
}
return host;
}
virtual_host_t::
virtual_host_t(std::string host)
: _host{validate_host(std::move(host))}
{}
std::string const&
virtual_host_t::
host() const
{
return _host;
}
std::vector<server::middleware_t> const&
virtual_host_t::
middleware() const
{
return _middleware;
}
std::list<route_t> const&
virtual_host_t::
routes() const
{
return _routes;
}
void
virtual_host_t::
use(server::middleware_t m)
{
_middleware.emplace_back(std::move(m));
}
route_t&
virtual_host_t::
add_route()
{
_routes.emplace_back();
return _routes.back();
}
route_t&
virtual_host_t::
add_route(std::string path)
{
_routes.emplace_back(std::move(path));
return _routes.back();
}
route_t&
virtual_host_t::
add_route(std::string path, server::request_handler_t handler)
{
_routes.emplace_back(std::move(path), std::move(handler));
return _routes.back();
}
std::ostream&
to_stream(std::ostream& o, virtual_host_t const& vhost)
{
o << "virtual host: '" << vhost.host() << "'\n";
for (auto const& r : vhost.routes()) {
to_stream(o, r, 2);
}
return o;
}
std::ostream&
operator<<(std::ostream& o, virtual_host_t const& vhost)
{
return to_stream(o, vhost);
}
} // namespace art::seafire::routing

View File

@@ -0,0 +1,65 @@
#ifndef seafire_routing__virtual_host_hxx_
#define seafire_routing__virtual_host_hxx_
#include <art/seafire/routing/route.hxx>
#include <art/seafire/server/middleware.hxx>
#include <art/seafire/server/request-handler.hxx>
#include <iostream>
#include <list>
#include <optional>
#include <vector>
namespace art::seafire::routing
{
class virtual_host_t
{
public:
explicit
virtual_host_t(std::string);
virtual_host_t(virtual_host_t const&) = delete;
virtual_host_t(virtual_host_t&&) = delete;
std::string const&
host() const;
std::vector<server::middleware_t> const&
middleware() const;
std::list<route_t> const&
routes() const;
void
use(server::middleware_t);
route_t&
add_route();
route_t&
add_route(std::string);
route_t&
add_route(std::string, server::request_handler_t);
virtual_host_t& operator=(virtual_host_t const&) = delete;
virtual_host_t& operator=(virtual_host_t&&) = delete;
private:
std::string _host;
std::vector<server::middleware_t> _middleware;
std::list<route_t> _routes;
};
std::ostream&
to_stream(std::ostream&, virtual_host_t const&);
std::ostream&
operator<<(std::ostream&, virtual_host_t const&);
} // namespace art::seafire::routing
#endif

4
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@@ -0,0 +1,7 @@
project = libart-seafire-routing
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@@ -0,0 +1,6 @@
$out_root/
{
include art/seafire/routing/
}
export $out_root/art/seafire/routing/$import.target

16
build/root.build Normal file
View File

@@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@@ -0,0 +1,5 @@
./: {art/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

15
manifest Normal file
View File

@@ -0,0 +1,15 @@
: 1
name: libart-seafire-routing
version: 0.1.0-a.0.z
language: c++
summary: Seafire Routing C++ library
license: BSD-4-Clause
description-file: README.md
url: https://art.helloryan.se/
email: art@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libart-uri ^0.1.0-
depends: libart-seafire-common ^0.1.0-
depends: libart-seafire-protocol ^0.1.0-
depends: libart-seafire-server ^0.1.0-

23
repositories.manifest Normal file
View File

@@ -0,0 +1,23 @@
: 1
summary: Seafire-Routing project repository
:
role: prerequisite
location: https://pkg.cppget.org/1/beta
trust: 70:64:FE:E4:E0:F3:60:F1:B4:51:E1:FA:12:5C:E0:B3:DB:DF:96:33:39:B9:2E:E5:C2:68:63:4C:A6:47:39:43
:
role: prerequisite
location: https://code.helloryan.se/art/libart-uri.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-common.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-protocol.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-server.git##HEAD

8
tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@@ -0,0 +1 @@
./: {*/ -build/}