Files
pip/libs/http_server/pihttpserver.cpp
peri4 ef8b785ac6 version 5.1.0
PIHTTPServer now can handle path with partially *, ** and {} path arguments
PIHTTP::MessageConst add queryArguments() and pathArguments(). arguments() now union of these args
2025-08-28 19:48:19 +03:00

200 lines
5.3 KiB
C++

#include "pihttpserver.h"
#include "piliterals_string.h"
PIHTTPServer::PIHTTPServer() {
setRequestCallback([this](const PIHTTP::MessageConst & r) -> PIHTTP::MessageMutable {
PIHTTP::MessageMutable reply;
reply.setCode(PIHTTP::Code::NotFound);
auto in_path = splitPath(r.path());
auto it = endpoints.makeReverseIterator();
bool found = false;
while (it.next()) {
for (const auto & ep: it.value()) {
if (ep.function && ep.method == r.method()) {
PIMap<PIString, PIString> ext_args;
if (ep.match(in_path, ext_args)) {
auto & r_mut(static_cast<PIHTTP::MessageMutable &>(const_cast<PIHTTP::MessageConst &>(r)));
r_mut.pathArguments() = ext_args;
r_mut.arguments() = r_mut.pathArguments();
r_mut.arguments() << r_mut.queryArguments();
reply = ep.function(r);
found = true;
break;
}
}
}
if (found) break;
}
if (!found && unhandled) reply = unhandled(r);
auto hit = reply_headers.makeIterator();
while (hit.next())
reply.addHeader(hit.key(), hit.value());
return reply;
});
}
PIHTTPServer::~PIHTTPServer() {
stop();
}
bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, RequestFunction functor) {
Endpoint ep;
if (!ep.create(path)) return false;
ep.method = method;
ep.function = functor;
endpoints[ep.priority] << ep;
return true;
}
void PIHTTPServer::registerUnhandled(RequestFunction functor) {
unhandled = functor;
}
void PIHTTPServer::unregisterPath(const PIString & path, PIHTTP::Method method) {
auto pl = splitPath(path);
auto it = endpoints.makeIterator();
while (it.next()) {
it.value().removeWhere([&pl, method](const Endpoint & ep) { return ep.path == pl && ep.method == method; });
}
endpoints.removeWhere([](uint, const PIVector<Endpoint> & epl) { return epl.isEmpty(); });
}
void PIHTTPServer::unregisterPath(const PIString & path) {
auto pl = splitPath(path);
auto it = endpoints.makeIterator();
while (it.next()) {
it.value().removeWhere([&pl](const Endpoint & ep) { return ep.path == pl; });
}
endpoints.removeWhere([](uint, const PIVector<Endpoint> & epl) { return epl.isEmpty(); });
}
PIStringList PIHTTPServer::splitPath(const PIString & path) {
auto ret = path.split("/");
ret.removeAll({});
return ret;
}
PIHTTPServer::PathElement::PathElement(const PIString & reg) {
source = reg;
if (reg == "*"_a) {
type = Type::AnyOne;
} else if (reg == "**"_a) {
type = Type::AnyMany;
} else if (reg.contains('*')) {
type = Type::AnyPart;
parts = reg.split('*');
} else if (reg.contains('{')) {
type = Type::Arguments;
int ind = 0, eind = 0, pind = 0;
for (;;) {
ind = reg.find('{', ind);
if (ind < 0) break;
eind = reg.find('}', ind + 1);
if (eind < 0) break;
arguments.insert(arguments.size_s(), reg.mid(ind + 1, eind - ind - 1));
if (ind == 0)
parts << PIString();
else {
if (ind > pind)
parts << reg.mid(pind, ind - pind);
else if (parts.isNotEmpty()) {
piCout << "[PIHTTPServer] Warning: sequential arguments, ignoring this path!";
type = Type::Invalid;
return;
}
}
ind = pind = eind + 1;
}
if (eind < reg.size_s() - 1) parts << reg.mid(eind + 1);
}
}
bool PIHTTPServer::PathElement::match(const PIString & in, PIMap<PIString, PIString> & ext_args) const {
// piCout << "match" << source << "with" << in;
if (type == Type::AnyOne) return true;
if (type == Type::AnyPart) {
int ind = 0;
for (const auto & m: parts) {
ind = in.find(m, ind);
if (ind < 0) return false;
}
return true;
}
if (type == Type::Arguments) {
int ind = 0, eind = 0;
for (int i = 0; i < parts.size_s(); ++i) {
const auto & m(parts[i]);
if (m.isNotEmpty()) {
ind = in.find(m, eind);
if (ind < 0) return false;
}
if (i > 0) {
ext_args[arguments.value(i - 1)] = in.mid(eind, ind - eind);
}
eind = ind + m.size_s();
}
if (parts.size() == arguments.size()) {
ext_args[arguments.value(arguments.size_s() - 1)] = in.mid(eind);
}
return true;
}
return source == in;
}
uint PIHTTPServer::PathElement::priority() const {
switch (type) {
case Type::Fixed: return 0x10000; break;
case Type::Arguments: return 0x1000; break;
case Type::AnyPart: return 0x100; break;
case Type::AnyOne: return 0x10; break;
case Type::AnyMany: return 0x1; break;
default: break;
}
return 0;
}
bool PIHTTPServer::Endpoint::create(const PIString & p) {
path = splitPath(p);
prepared_path.clear();
priority = 0;
for (const auto & i: path) {
PathElement pe(i);
prepared_path << pe;
path_types |= pe.type;
priority += pe.priority();
}
return !path_types[PathElement::Type::Invalid];
}
bool PIHTTPServer::Endpoint::match(const PIStringList & in_path, PIMap<PIString, PIString> & ext_args) const {
if (path_types[PathElement::Type::AnyMany]) {
int any_ind = path.indexOf("**"_a);
for (int i = 0; i < any_ind; ++i) {
if (!prepared_path[i].match(in_path[i], ext_args)) return false;
}
int si = prepared_path.size_s() - 1, ii = in_path.size_s() - 1;
for (; si > any_ind && ii >= 0; --si, --ii) {
if (!prepared_path[si].match(in_path[ii], ext_args)) return false;
}
} else {
if (in_path.size() != prepared_path.size()) return false;
for (int i = 0; i < prepared_path.size_s(); ++i) {
if (!prepared_path[i].match(in_path[i], ext_args)) return false;
}
}
return true;
}