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:
parent
b4cc681112
commit
3a5f7e0aac
4 changed files with 217 additions and 72 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
139
ircd/resource.cc
139
ircd/resource.cc
|
@ -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 =
|
||||
{
|
||||
|
|
|
@ -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, ' ')
|
||||
|
|
Loading…
Reference in a new issue