#include "App.hpp" #include #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 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 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 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& 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