From 52b3cd46c76fd4be18aeb8422a08a073484b9fad Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Sat, 29 Aug 2026 12:01:42 +0200 Subject: More module implementation partition units --- server/src/srv.cpp | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 server/src/srv.cpp (limited to 'server/src/srv.cpp') diff --git a/server/src/srv.cpp b/server/src/srv.cpp new file mode 100644 index 0000000..00a2bb8 --- /dev/null +++ b/server/src/srv.cpp @@ -0,0 +1,258 @@ +module; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +module routemon:srv$impl; + +import std; +import :gpx; +import :srv; + +namespace beast = boost::beast; +namespace json = boost::json; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; + +namespace routemon::srv { + +class gpx_parse_error_category_impl : public std::error_category +{ +public: + char const* name() const noexcept override { return "gpx_parse"; } + + auto message(int condition) const noexcept -> std::string override + { + std::ignore = condition; + return "failed to parse GPX file"; + } +}; + +auto gpx_parse_error_category() noexcept -> gpx_parse_error_category_impl const& +{ + static auto const inst = gpx_parse_error_category_impl{}; + return inst; +} + +auto gpx_parse_error() noexcept -> std::error_code +{ + return std::error_code{1, gpx_parse_error_category()}; +} + +class gpx_parse_result +{ + std::variant res_; + +public: + auto set_exception(std::exception_ptr ex) noexcept { res_ = ex; } + auto set_gpx_file(gpx::file&& f) noexcept { res_ = std::move(f); } + + auto unwrap() -> gpx::file&& + { + return std::visit( + util::overloaded{ + [](std::exception_ptr ex) -> gpx::file&& + { + if (ex) + std::rethrow_exception(ex); + else + throw std::runtime_error{"no GPX file parse result available"}; + }, + [](gpx::file&& f) -> gpx::file&& { return std::move(f); }, + }, + std::move(res_)); + } +}; + +struct readable_gpx_body +{ + using value_type = gpx_parse_result; + + class reader + { + gpx::reader r_; + util::not_null res_; + + public: + template + explicit reader(bhttp::header&, value_type& v) : res_{&v} + { + } + + // The following methods (which are called by Beast) are marked + // noexcept, since Beast does not ensure that exceptions thrown + // here are appropriately directed to the caller of + // (async_)read(_some), so throwing here might cause the program + // to crash. + + auto + init(boost::optional /* n */, beast::error_code& ec) noexcept + -> void + { + try + { + r_.init(); + ec = {}; + } + catch (std::exception& ex) + { + res_->set_exception(std::current_exception()); + ec = gpx_parse_error(); + } + } + + auto + put(beast::concepts::const_buffer_sequence auto b, + beast::error_code& ec) noexcept -> std::size_t + { + auto total = 0uz; + try + { + for (auto it = net::buffer_sequence_begin(b); + it != net::buffer_sequence_end(b); it++) + { + r_.put( + std::string_view{ + static_cast(it->data()), it->size() + }); + total += it->size(); + } + ec = {}; + } + catch (std::exception& ex) + { + res_->set_exception(std::current_exception()); + ec = gpx_parse_error(); + } + return total; + } + + auto finish(beast::error_code& ec) noexcept + { + try + { + res_->set_gpx_file(r_.finish()); + ec = {}; + } + catch (std::exception& ex) + { + res_->set_exception(std::current_exception()); + ec = gpx_parse_error(); + } + } + }; +}; +static_assert(bhttp::concepts::body); +static_assert(bhttp::concepts::body_reader); + +auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) + -> net::awaitable +{ + auto gpx_file = gpx::file{}; + try + { + auto req = + co_await http::read_request(ctx, std::move(r)); + gpx_file = std::move(req->body().unwrap()); + } + catch (std::exception& ex) + { + // TODO: more detailed problem reporting + auto tpl = problem::tpl{ + .status = bhttp::status::bad_request, + .title = translate("Failed to parse GPX file"), + .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed", + }; + co_return http::problem_rsp( + ctx, tpl.instantiate(), http::keep_alive{false}); + } + + // TODO: catch handler exceptions and return 500 when raised? + // (keep-alive depends on whether whole request was read) + auto mres = inner_.process_gpx(std::move(gpx_file)); + if (!mres) + { + auto tpl = problem::tpl{ + .status = bhttp::status::internal_server_error, + .title = translate("Internal server error"), + .type_uri = "https://routemon.fautchen.eu/problems/" + "internal-server-error", + }; + co_return http::problem_rsp(ctx, tpl.instantiate(), http::keep_alive{true}); + } + + auto rsp = http::make_rsp( + bhttp::status::ok, http::keep_alive{true}); + rsp.set(bhttp::field::content_type, "application/json"); + rsp.body() = json::serialize(json::value_from(*mres)); + rsp.prepare_payload(); + co_return rsp; +} + +auto handler::handle_sysinfo(l0_ctx ctx, http::readable_request r) + -> net::awaitable +{ + auto req = co_await http::read_request(ctx, std::move(r)); + auto info = inner_.sysinfo(); + + auto rsp = http::make_rsp( + bhttp::status::ok, http::keep_alive{true}); + rsp.set(bhttp::field::content_type, "application/json"); + rsp.body() = json::serialize(json::value_from(info)); + rsp.prepare_payload(); + co_return rsp; +} + +handler::handler(api::handler&& inner) : inner_{std::move(inner)} {} + +auto handler::make_routes() -> http::route_tree> +{ + auto handler = [this](MemFn member) + { return std::bind_front(member, this); }; + + return http::dtree>{}.named_subtrees({ + {"gpx", http::dtree{{ + .post = handler(&handler::handle_process_gpx), + }} + .no_subtrees()}, + {"sysinfo", http::dtree{ + { + .get = handler(&handler::handle_sysinfo), + } + }.no_subtrees()}, + }); +} + +auto server::make_global_middleware() + -> http::middleware_t +{ + return http::middleware_compose< + http::base_ctx, http::trace_id_ctx, + http::trace_id_ctx + >(http::trace_id_middleware, + http::lax_cors_middleware>); +} + +server::server( + log::logger const& l, locale::selector&& lsel, api::handler&& inner) + : handler_{std::move(inner)}, + srv_{ + l, http::router{ + std::move(lsel), make_global_middleware(), handler_.make_routes() + } + } +{ +} + +auto server::spawn(net::io_context& ioc) -> void { srv_.spawn(ioc); } + +} // namespace routemon::srv -- cgit v1.3