Merge branch 'feature/virtual-hosts'

This commit is contained in:
R.Y.A.N 2025-03-09 15:56:18 +01:00
commit 53d43dfea6
Signed by: R.Y.A.N
GPG Key ID: 3BD93EABD1407B82
23 changed files with 591 additions and 290 deletions

View File

@ -0,0 +1,43 @@
#include <seafire/routing/builder.hxx>
#include <seafire/routing/flatten.hxx>
#include <vector>
namespace 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 seafire::routing

View File

@ -0,0 +1,42 @@
#ifndef seafire__routing__builder_hxx_
#define seafire__routing__builder_hxx_
#include <seafire/routing/routing-table.hxx>
#include <seafire/routing/virtual-host.hxx>
#include <seafire/server/request-handler.hxx>
#include <list>
#include <string>
namespace 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 seafire::routing
#endif

View File

@ -4,15 +4,26 @@ namespace seafire::routing
{
endpoint_t::
endpoint_t(std::string pattern, server::request_handler_t handler)
: pattern_{std::move(pattern)}, handler_{std::move(handler)}
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::
pattern() const
host() const
{
return pattern_;
return host_;
}
std::string const&
endpoint_t::
path() const
{
return path_;
}
server::request_handler_t const&
@ -25,7 +36,7 @@ namespace seafire::routing
std::ostream&
to_stream(std::ostream& o, endpoint_t const& ep)
{
return o << ep.pattern();
return o << ep.host() << ": " << ep.path();
}
std::ostream&

View File

@ -12,16 +12,20 @@ namespace seafire::routing
class endpoint_t
{
public:
endpoint_t(std::string, server::request_handler_t);
endpoint_t(std::string, std::string, server::request_handler_t);
std::string const&
pattern() const;
host() const;
std::string const&
path() const;
server::request_handler_t const&
handler() const;
private:
std::string pattern_;
std::string host_;
std::string path_;
server::request_handler_t handler_;
};

View File

@ -1,50 +0,0 @@
#include <seafire/routing/flatten-route.hxx>
namespace seafire::routing
{
void
flatten_route(std::vector<endpoint_t>& endpoints,
std::vector<server::middleware_t> middlewares,
route_t const& route,
std::string const& root)
{
auto const path = root + 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(path, server::make_middleware(middlewares, *h));
}
// flatten any child routes.
//
for (auto const& child_route : route.children()) {
flatten_route(endpoints,
middlewares,
child_route,
route.path().empty() ? path : path + '/');
}
}
void
flatten_route(std::vector<endpoint_t>& endpoints, route_t const& r)
{
flatten_route(endpoints, {}, r, "/");
}
std::vector<endpoint_t>
flatten_route(route_t const& r)
{
std::vector<endpoint_t> endpoints;
flatten_route(endpoints, r);
return endpoints;
}
} // namespace seafire::routing

View File

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

View File

@ -0,0 +1,55 @@
#include <seafire/routing/flatten.hxx>
namespace seafire::routing
{
void
flatten(std::vector<endpoint_t>& endpoints,
std::string const& vhost,
std::vector<server::middleware_t> middlewares,
route_t const& route,
std::string const& root)
{
auto const path = root + 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, path, server::make_middleware(middlewares, *h));
}
// flatten any child routes.
//
for (auto const& child_route : route.children()) {
flatten(endpoints,
vhost,
middlewares,
child_route,
route.path().empty() ? path : path + '/');
}
}
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 seafire::routing

View File

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

View File

@ -0,0 +1,63 @@
#ifndef seafire__routing__host_parameter_hxx_
#define seafire__routing__host_parameter_hxx_
#include <seafire/server/parameters.hxx>
#include <seafire/server/request.hxx>
#include <seafire/routing/parameters.hxx>
#include <optional>
namespace 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 seafire::routing
#endif

View File

@ -1,24 +0,0 @@
#ifndef seafire__routing__match_path_hxx_
#define seafire__routing__match_path_hxx_
#include <seafire/routing/route-parameters.hxx>
#include <code/uri/uri.hxx>
#include <string>
namespace seafire::routing
{
bool
match_path(std::string const&,
std::string const&,
route_parameters_t&);
std::string
render_path(std::string const&,
route_parameters_t&);
} // namespace seafire::routing
#endif

View File

@ -1,18 +1,18 @@
#include <seafire/routing/match-path.hxx>
#include <seafire/routing/match.hxx>
#include <sstream>
#include <vector>
#include <iostream>
namespace seafire::routing
{
bool
match_path(std::string const& pattern,
std::string const& subject,
route_parameters_t& params)
match(std::string const& subject,
std::string const& pattern,
char delim,
parameters_t& params)
{
route_parameters_t tmp_params;
parameters_t tmp_params;
// p/pend = pattern iterators.
//
@ -46,7 +46,7 @@ namespace seafire::routing
// v/vend = local subject iterators.
//
auto const v = s;
while (s != send && (greedy || '/' != *s)) {
while (s != send && (greedy || delim != *s)) {
++s;
}
auto const vend = s;
@ -83,8 +83,7 @@ namespace seafire::routing
}
std::string
render_path(std::string const& pattern,
route_parameters_t const& params)
render(std::string const& pattern, parameters_t& params)
{
std::stringstream str;

22
seafire/routing/match.hxx Normal file
View File

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

View File

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

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 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 seafire::routing
#endif

View File

@ -4,7 +4,7 @@
#include <seafire/server/parameters.hxx>
#include <seafire/server/request.hxx>
#include <seafire/routing/route-parameters.hxx>
#include <seafire/routing/parameters.hxx>
#include <optional>
@ -16,23 +16,18 @@ namespace seafire::routing
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;
static
std::string const&
name()
{
static std::string const name{Name};
return name;
}
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
{

View File

@ -1,30 +0,0 @@
#ifndef seafire__routing__route_parameters_hxx_
#define seafire__routing__route_parameters_hxx_
#include <map>
#include <optional>
#include <string>
namespace seafire::routing
{
class route_parameters_t
{
public:
std::map<std::string, std::string>&
map();
std::map<std::string, std::string> const&
map() const;
std::optional<std::string>
get(std::string const& key) const;
private:
std::map<std::string, std::string> kv_;
};
} // namespace seafire::routing
#endif

View File

@ -6,18 +6,21 @@ namespace seafire::routing
{
static
void
ensure_valid_path(std::string const& path)
std::string
validate_path(std::string path)
{
if (!path.empty()) {
if (path.front() == '/') {
throw std::invalid_argument{"route path must not start with '/'"};
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::
@ -25,17 +28,13 @@ namespace seafire::routing
route_t::
route_t(std::string path)
: path_{std::move(path)}
{
ensure_valid_path(path_);
}
: path_{validate_path(std::move(path))}
{}
route_t::
route_t(std::string path, server::request_handler_t handler)
: path_{std::move(path)}, handler_{std::move(handler)}
{
ensure_valid_path(path_);
}
: path_{validate_path(std::move(path))}, handler_{std::move(handler)}
{}
std::string const&
route_t::
@ -80,26 +79,20 @@ namespace seafire::routing
return children_.back();
}
route_t&
route_t::
add_route(route_t r)
{
children_.emplace_back(std::move(r));
return children_.back();
}
route_t&
route_t::
add_route(std::string path)
{
return add_route(route_t{std::move(path)});
children_.emplace_back(std::move(path));
return children_.back();
}
route_t&
route_t::
add_route(std::string path, server::request_handler_t handler)
{
return add_route(route_t{std::move(path), std::move(handler)});
children_.emplace_back(std::move(path), std::move(handler));
return children_.back();
}
std::ostream&

View File

@ -24,6 +24,9 @@ namespace seafire::routing
route_t(std::string, server::request_handler_t);
route_t(route_t const&) = delete;
route_t(route_t&&) = delete;
std::string const&
path() const;
@ -42,15 +45,15 @@ namespace seafire::routing
route_t&
add_route();
route_t&
add_route(route_t);
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_;

View File

@ -1,6 +1,8 @@
#include <seafire/routing/router.hxx>
#include <seafire/routing/diagnostics.hxx>
#include <seafire/protocol/rfc7230/host.hxx>
namespace seafire::routing
{
@ -58,11 +60,23 @@ namespace seafire::routing
{
trace() << "on_request(...)";
auto host = get<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 for [" << path << "]";
trace() << "locating endpoint:\n"
<< " -> hostname: " << hostname << '\n'
<< " -> path : " << path
;
auto result = routing_table().find_route(path);
auto result = routing_table().find_route(hostname, path);
if (!result) {
trace() << "endpoint for [" << path << "] not found";
@ -72,15 +86,19 @@ namespace seafire::routing
auto trace_endpoint = [&result](common::diagnostics_t::proxy_t proxy)
{
proxy << "endpoint found";
proxy << "endpoint found!";
for (auto const& j : result->params.map())
proxy << "\n -> " << j.first << " = " << j.second << '\n';
for (auto const& j : result->host_params.map())
proxy << "\n -> host param : " << j.first << " = " << j.second << '\n';
for (auto const& j : result->route_params.map())
proxy << "\n -> route param: " << j.first << " = " << j.second << '\n';
};
trace_endpoint(trace());
req.extensions().extend(&res.memory().alloc(result->params));
req.extensions().extend(&res.memory().alloc(result->host_params));
req.extensions().extend(&res.memory().alloc(result->route_params));
result->handler.invoke(req, res);
}

View File

@ -1,7 +1,6 @@
#include <seafire/routing/routing-table.hxx>
#include <seafire/routing/match-path.hxx>
#include <seafire/routing/flatten-route.hxx>
#include <seafire/routing/match.hxx>
namespace seafire::routing
{
@ -20,66 +19,27 @@ namespace seafire::routing
std::optional<routing_table_t::find_result_t>
routing_table_t::
find_route(std::string const& path) const
find_route(std::string const& host, std::string const& path) const
{
for (auto const& e : endpoints()) {
route_parameters_t params;
host_parameters_t host_params;
if (match_path(e.pattern(), path, params))
return find_result_t{e.handler(), std::move(params)};
if (!match(host, e.host(), '.', host_params)) {
continue;
}
return {};
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()
};
}
}
routing_table_t::builder_t::
builder_t() = default;
routing_table_t::builder_t::
~builder_t() noexcept = default;
route_t&
routing_table_t::builder_t::
add_route()
{
roots_.emplace_back();
return roots_.back();
}
route_t&
routing_table_t::builder_t::
add_route(route_t r)
{
roots_.emplace_back(std::move(r));
return roots_.back();
}
route_t&
routing_table_t::builder_t::
add_route(std::string path)
{
return add_route(route_t{std::move(path)});
}
route_t&
routing_table_t::builder_t::
add_route(std::string path,
server::request_handler_t handler)
{
return add_route({std::move(path), std::move(handler)});
}
routing_table_t
routing_table_t::builder_t::
build() const
{
std::vector<endpoint_t> endpoints;
for (auto const& r : roots_) {
flatten_route(endpoints, r);
}
return routing_table_t{std::move(endpoints)};
return std::nullopt;
}
} // namespace seafire::routing

View File

@ -2,7 +2,7 @@
#define seafire__routing__routing_table_hxx_
#include <seafire/routing/endpoint.hxx>
#include <seafire/routing/route-parameters.hxx>
#include <seafire/routing/parameters.hxx>
#include <seafire/routing/route.hxx>
#include <seafire/server/request-handler.hxx>
@ -18,12 +18,12 @@ namespace seafire::routing
class routing_table_t
{
public:
class builder_t;
struct find_result_t
{
host_parameters_t host_params;
route_parameters_t route_params;
server::request_handler_t const& handler;
route_parameters_t params;
};
@ -34,45 +34,17 @@ namespace seafire::routing
endpoints() const;
std::optional<find_result_t>
find_route(std::string const&) const;
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_;
};
class routing_table_t::builder_t {
public:
builder_t();
builder_t(builder_t const&) = delete;
builder_t(builder_t&&) = delete;
~builder_t() noexcept;
route_t&
add_route();
route_t&
add_route(route_t);
route_t&
add_route(std::string);
route_t&
add_route(std::string, server::request_handler_t);
routing_table_t
build() const;
builder_t& operator=(builder_t const&) = delete;
builder_t& operator=(builder_t&&) = delete;
private:
std::list<route_t> roots_;
};
} // namespace seafire::routing
#endif

View File

@ -0,0 +1,100 @@
#include <seafire/routing/virtual-host.hxx>
namespace 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 << " => " << 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 seafire::routing

View File

@ -0,0 +1,65 @@
#ifndef seafire_routing__virtual_host_hxx_
#define seafire_routing__virtual_host_hxx_
#include <seafire/routing/route.hxx>
#include <seafire/server/middleware.hxx>
#include <seafire/server/request-handler.hxx>
#include <iostream>
#include <list>
#include <optional>
#include <vector>
namespace 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 seafire::routing
#endif