0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-12-25 23:14:13 +01:00

ircd::resource: Implement more complex path routing.

This commit is contained in:
Jason Volk 2022-07-17 21:39:10 -07:00
parent b4cc681112
commit 3a5f7e0aac
4 changed files with 217 additions and 72 deletions

View file

@ -25,7 +25,19 @@ struct ircd::m::resource
static log::log log;
using ircd::resource::resource;
static string_view path_version(const string_view &);
static string_view path_canonize(const mutable_buffer &, const string_view &);
private:
char path_buf[512];
ircd::resource &route(const string_view &path) const override;
string_view params(const string_view &path) const override;
public:
resource(const string_view &path, struct opts);
resource(const string_view &path);
~resource() noexcept override;
};
struct ircd::m::resource::method
@ -46,6 +58,7 @@ struct ircd::m::resource::request
{
template<class> struct object;
string_view version; // api version
pair<string_view> authorization; // proffering any
string_view access_token; // proffering user
m::request::x_matrix x_matrix; // proferring server
@ -66,6 +79,11 @@ struct ircd::m::resource::request::object
{
const m::resource::request &request;
const decltype(request.version) &version
{
request.version
};
const decltype(request.access_token) &access_token
{
request.access_token

View file

@ -39,21 +39,23 @@ struct ircd::resource
std::unique_ptr<method> default_method_head;
std::unique_ptr<method> default_method_options;
private:
using method_closure = std::function<bool (const method &)>;
string_view method_list(const mutable_buffer &buf, const method_closure &) const;
string_view method_list(const mutable_buffer &buf) const;
response handle_options(client &, const request &) const;
response handle_head(client &, const request &) const;
public:
method &operator[](const string_view &name) const;
virtual resource &route(const string_view &path) const;
virtual string_view params(const string_view &path) const;
resource(const string_view &path, struct opts);
resource(const string_view &path);
resource(resource &&) = delete;
resource(const resource &) = delete;
~resource() noexcept;
virtual ~resource() noexcept;
static resource &find(const string_view &path);
};

View file

@ -22,78 +22,31 @@ ircd::util::instance_map<ircd::string_view, ircd::resource, ircd::iless>::map
ircd::resource &
ircd::resource::find(const string_view &path_)
{
const auto &resources{instance_map::map};
auto it
{
resources.begin()
};
if(unlikely(it == resources.end()))
throw http::error
{
http::code::NOT_FOUND
};
auto &resource
{
it->second
};
const string_view path
{
rstrip(path_, '/')
};
auto &resources(instance_map::map);
auto it
{
resources.lower_bound(path)
};
if(it == end(resources)) try
{
--it;
if(it == begin(resources) || !startswith(path, it->first))
return *resources.at("/");
}
catch(const std::out_of_range &e)
{
throw http::error
{
http::code::NOT_FOUND
};
}
auto rpath{it->first};
//assert(!endswith(rpath, '/'));
// Exact file or directory match
if(path == rpath)
return *it->second;
// Directories handle all paths under them.
while(!startswith(path, rpath))
{
// Walk the iterator back to find if there is a directory prefixing this path.
if(it == begin(resources))
throw http::error
{
http::code::NOT_FOUND
};
--it;
rpath = it->first;
if(~it->second->opts->flags & it->second->DIRECTORY)
continue;
// If the closest directory still doesn't match hand this off to the
// webroot which can then service or 404 this itself.
if(!startswith(path, rpath))
return *resources.at("/");
}
// Check if the resource is a directory; if not, it can only
// handle exact path matches.
if(~it->second->opts->flags & it->second->DIRECTORY && path != rpath)
throw http::error
{
http::code::NOT_FOUND
};
if(it->second->opts->flags & it->second->DIRECTORY)
{
const auto rem(lstrip(path, rpath));
if(!empty(rem) && !startswith(rem, '/'))
throw http::error
{
http::code::NOT_FOUND
};
}
return *it->second;
return path && path != "/"?
resource->route(path):
*resource;
}
//
@ -168,6 +121,54 @@ noexcept
};
}
ircd::string_view
ircd::resource::params(const string_view &path)
const
{
const auto prefix_tokens
{
token_count(this->path, '/')
};
const auto params_after
{
prefix_tokens? prefix_tokens - 1: 0
};
const auto ret
{
tokens_after(path, '/', params_after)
};
return ret;
}
ircd::resource &
ircd::resource::route(const string_view &path)
const
{
if(this->path != "/" && startswith(path, this->path))
if(path == this->path || opts->flags & flag::DIRECTORY)
return mutable_cast(*this);
const auto &resources{instance_map::map};
assert(!resources.empty());
auto it
{
resources.lower_bound(path)
};
if(it == end(resources) || it->first > path)
--it;
if(it->second != this)
return it->second->route(path);
it = begin(resources);
assert(it != end(resources));
return mutable_cast(*it->second);
}
ircd::resource::method &
ircd::resource::operator[](const string_view &name)
const try
@ -580,7 +581,9 @@ try
}
};
client.request.params = lstrip(head.path, resource->path);
// The path components after the resource->path become the parameter
// vector (or parv[]) passed to the resource as its arguments.
client.request.params = resource->params(head.path);
client.request.params = strip(client.request.params, '/');
client.request.parv =
{

View file

@ -38,6 +38,123 @@ ircd::m::x_matrix_verify_destination
{ "default", true },
};
//
// m::resource
//
ircd::m::resource::resource(const string_view &path)
:m::resource
{
path, {},
}
{
}
ircd::m::resource::resource(const string_view &path,
struct opts opts)
:ircd::resource
{
path_canonize(path_buf, path), opts
}
{
}
ircd::m::resource::~resource()
noexcept
{
}
ircd::string_view
ircd::m::resource::params(const string_view &path)
const
{
const auto prefix_tokens
{
token_count(this->path, '/')
};
const auto version
{
path_version(path)
};
const auto params_after
{
prefix_tokens? prefix_tokens - 1 + bool(version): 0
};
const auto ret
{
tokens_after(path, '/', params_after)
};
return ret;
}
ircd::resource &
ircd::m::resource::route(const string_view &path)
const
{
thread_local char canon_buf[1024];
const string_view canon_path
{
path_canonize(canon_buf, path)
};
return mutable_cast(ircd::resource::route(canon_path));
}
ircd::string_view
ircd::m::resource::path_canonize(const mutable_buffer &buf,
const string_view &path)
{
const auto version
{
path_version(path)
};
if(!version)
return path;
const auto &[before, after]
{
tokens_split(path, '/', 2, 1)
};
mutable_buffer out{buf};
consume(out, copy(out, '/'));
consume(out, copy(out, before));
if(likely(after))
{
consume(out, copy(out, '/'));
consume(out, copy(out, after));
}
return string_view
{
data(buf), data(out)
};
}
ircd::string_view
ircd::m::resource::path_version(const string_view &path)
{
const auto version
{
token(path, '/', 2, {})
};
const bool pass
{
true
&& version.size() >= 2
&& (version[0] == 'v' || version[0] == 'r')
&& (version[1] >= '0' && version[1] <= '9')
};
return pass? version: string_view{};
}
//
// m::resource::method
//
@ -86,10 +203,11 @@ try
if(ident)
log::debug
{
log, "%s %s %s `%s'",
log, "%s %s %s %s `%s'",
client.loghead(),
ident,
request.head.method,
request.version?: "??"_sv,
request.head.path,
};
@ -160,6 +278,10 @@ ircd::m::resource::request::request(const method &method,
{
r
}
,version
{
path_version(head.path)
}
,authorization
{
split(head.authorization, ' ')