Full C++23 framework tree: runtime, observability, security, data, app, realtime, supervisor, clients, CLI, example plus enterprise tenancy, audit, operability, governance, supplychain. Postgres via docker compose for tests.
124 lines
4.0 KiB
C++
124 lines
4.0 KiB
C++
#include "App.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include "Caps.hpp"
|
|
|
|
namespace molo::example::fw {
|
|
|
|
HttpError::HttpError(int status, std::string code, std::string message)
|
|
: status_(status), code_(std::move(code)), message_(std::move(message)) {}
|
|
|
|
int HttpError::status() const { return status_; }
|
|
const std::string& HttpError::code() const { return code_; }
|
|
const std::string& HttpError::message() const { return message_; }
|
|
|
|
void Application::use(MiddlewareSlot slot, Middleware middleware) {
|
|
if (has_middleware_ && slot < last_slot_)
|
|
throw std::runtime_error("middleware category order violated");
|
|
last_slot_ = slot;
|
|
has_middleware_ = true;
|
|
middlewares_.emplace_back(slot, std::move(middleware));
|
|
}
|
|
|
|
void Application::include_router(const std::string& prefix,
|
|
const Router& router,
|
|
const std::string& name_prefix) {
|
|
router_.include_router(prefix, router, name_prefix);
|
|
}
|
|
|
|
void Application::mount(const std::string& path,
|
|
std::shared_ptr<Application> sub) {
|
|
mounts_.emplace_back(path, std::move(sub));
|
|
}
|
|
|
|
Response Application::dispatch(Request req) {
|
|
if (req.body.size() > caps::kMaxRequestBody)
|
|
return TextResponse("body too large", 413);
|
|
try {
|
|
return run_chain(std::move(req), 0);
|
|
} catch (const HttpError& e) {
|
|
std::string body = "{\"error\":\"" + json_escape(e.code()) +
|
|
"\",\"message\":\"" + json_escape(e.message()) + "\"}";
|
|
return JsonResponse(body, e.status());
|
|
} catch (const std::exception& e) {
|
|
return JsonResponse("{\"error\":\"internal\",\"message\":\"" +
|
|
json_escape(e.what()) + "\"}",
|
|
500);
|
|
}
|
|
}
|
|
|
|
Response Application::run_chain(Request req, int depth) {
|
|
Next next = [this, depth](Request r) { return dispatch_inner(r, depth); };
|
|
for (std::size_t i = middlewares_.size(); i > 0; --i) {
|
|
Middleware mw = middlewares_[i - 1].second;
|
|
Next inner = next;
|
|
next = [mw, inner](Request r) { return mw(std::move(r), inner); };
|
|
}
|
|
return next(std::move(req));
|
|
}
|
|
|
|
Response Application::dispatch_inner(Request req, int depth) {
|
|
if (depth > caps::kMaxMountDepth)
|
|
throw HttpError(500, "mount_depth", "sub-application nesting too deep");
|
|
std::string best;
|
|
std::shared_ptr<Application> target;
|
|
for (auto& [prefix, sub] : mounts_) {
|
|
if (req.path.size() >= prefix.size() &&
|
|
req.path.compare(0, prefix.size(), prefix) == 0 &&
|
|
prefix.size() > best.size()) {
|
|
best = prefix;
|
|
target = sub;
|
|
}
|
|
}
|
|
if (target) {
|
|
req.path = req.path.substr(best.size());
|
|
if (req.path.empty() || req.path.front() != '/') req.path = "/" + req.path;
|
|
return target->run_chain(std::move(req), depth + 1);
|
|
}
|
|
Handler handler;
|
|
if (router_.match(req, handler)) return handler(std::move(req));
|
|
bool path_exists = false;
|
|
for (const auto& r : router_.routes()) {
|
|
std::map<std::string, std::string> params;
|
|
if (match_pattern(r.pattern, req.path, params)) {
|
|
path_exists = true;
|
|
break;
|
|
}
|
|
}
|
|
if (path_exists)
|
|
throw HttpError(405, "method_not_allowed", "method not allowed");
|
|
throw HttpError(404, "not_found", "no route for " + req.path);
|
|
}
|
|
|
|
const std::vector<Route>& Application::routes() const {
|
|
return router_.routes();
|
|
}
|
|
|
|
Middleware errors_middleware() {
|
|
return [](Request req, Next next) -> Response {
|
|
try {
|
|
return next(std::move(req));
|
|
} catch (const HttpError& e) {
|
|
std::string body = "{\"error\":\"" + json_escape(e.code()) +
|
|
"\",\"message\":\"" + json_escape(e.message()) +
|
|
"\"}";
|
|
return JsonResponse(body, e.status());
|
|
}
|
|
};
|
|
}
|
|
|
|
Middleware request_id_middleware() {
|
|
return [](Request req, Next next) -> Response {
|
|
if (req.correlation_id.empty()) {
|
|
static unsigned long counter = 1;
|
|
req.correlation_id = "req-" + std::to_string(counter++);
|
|
}
|
|
Response res = next(req);
|
|
res.headers.set("X-Request-ID", req.correlation_id);
|
|
return res;
|
|
};
|
|
}
|
|
|
|
} // namespace molo::example::fw
|