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/http_server.cpp | 188 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 server/src/http_server.cpp (limited to 'server/src/http_server.cpp') diff --git a/server/src/http_server.cpp b/server/src/http_server.cpp new file mode 100644 index 0000000..da8e6f0 --- /dev/null +++ b/server/src/http_server.cpp @@ -0,0 +1,188 @@ +module; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +module routemon:http.server$impl; + +import :http.server; + +namespace routemon::http { + +auto presponse::header() -> bhttp::response_header& +{ + return impl_->header(); +} + +auto presponse::header() const -> bhttp::response_header const& +{ + return impl_->header(); +} + +auto presponse::is_done() const -> bool { return impl_->is_done(); } + +auto presponse::prepare(beast::error_code& ec) -> const_buffers_type +{ + return impl_->prepare(ec); +} + +auto presponse::consume(std::size_t n) -> void { return impl_->consume(n); } + +auto presponse::keep_alive() const noexcept -> bool +{ + return impl_->keep_alive(); +} + +keep_alive::keep_alive(bool value) : value{value} {} + +auto problem_rsp( + base_ctx const& ctx, problem::details const& problem, keep_alive ka) + -> presponse +{ + auto rsp = make_rsp(problem.status, ka); + rsp.set(bhttp::field::content_type, "application/problem+json"); + rsp.body() = json::serialize(json::value_from(problem, ctx.locale)); + rsp.prepare_payload(); + return presponse{std::move(rsp)}; +} + +auto make_preflight_rsp(preflight_response res, keep_alive ka) + -> bhttp::response +{ + auto rsp = make_rsp(bhttp::status::no_content, ka); + auto allow_headers_str = res.allow_headers + | std::views::transform( + [](auto const& field) -> std::string_view + { return bhttp::to_string(field); }) + | std::views::join_with(std::string_view{", "}) + | std::ranges::to(); + rsp.set( + bhttp::field::access_control_allow_methods, + res.allow_methods.to_string()); + rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str); + rsp.prepare_payload(); + return rsp; +} + +auto global_options_handler(base_ctx const& ctx, readable_request r) + -> net::awaitable +{ + // TODO: switch to "small (4KB) discarded" body type, similar to what Go + // does? Same goes for default_options_handler? Not sure. + if (auto res = co_await read_request(ctx, std::move(r)); + !res) + co_return std::move(res.error()); + auto req = r.p->release(); + auto rsp = make_rsp( + bhttp::status::no_content, keep_alive{req.keep_alive()}); + rsp.prepare_payload(); + co_return std::move(rsp); +} + +auto router::handle_request(readable_request r) const + -> net::awaitable +{ + return impl_->handle_request(r); +} + +server::server(log::logger const& l, router&& r) + : l_{l.sub("http_server")}, r_{std::move(r)} +{ +} + +auto server::do_session(beast::tcp_stream strm) -> net::awaitable +{ + auto buf = beast::flat_buffer{}; + + while (true) + { + auto p0 = bhttp::request_parser{}; + p0.body_limit(boost::none); + auto [ec, _] = + co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple); + if (ec == bhttp::error::end_of_stream) + break; + else if (ec) + throw boost::system::system_error{ec}; + + auto http_version = p0.get().version(); + auto&& rsp = co_await r_.handle_request( + readable_request{ + .p = util::not_null{&p0}, + .strm = util::not_null{&strm}, + .buf = util::not_null{&buf}, + }); + rsp.header().version(http_version); + bool keep_alive = rsp.keep_alive(); + co_await beast::async_write(strm, std::move(rsp)); + if (!keep_alive) + { + break; + } + } + + strm.socket().shutdown(tcp::socket::shutdown_send); +} + +auto server::do_listen(tcp::endpoint endpoint) -> net::awaitable +{ + auto executor = co_await net::this_coro::executor; + auto acceptor = tcp::acceptor{executor, endpoint}; + + l_.with("endpoint", endpoint.address().to_string()) + .with("port", std::to_string(endpoint.port())) + .info("Serving"); + while (true) + { + net::co_spawn( + executor, + do_session(beast::tcp_stream{co_await acceptor.async_accept()}), + [this](std::exception_ptr e) + { + if (e) + { + try + { + std::rethrow_exception(e); + } + catch (std::exception const& e) + { + l_.error("Error in session: {}", e.what()); + } + } + }); + } +} + +auto server::spawn(net::io_context& ioc) -> void +{ + auto const addr = net::ip::make_address("0.0.0.0"); + auto const endpoint = tcp::endpoint{addr, 8284}; + + // TODO: make exception handling as nice as in srv.cpp + net::co_spawn( + ioc, do_listen(endpoint), + [this](std::exception_ptr e) + { + if (e) + { + try + { + std::rethrow_exception(e); + } + catch (std::exception const& e) + { + l_.error("Error: {}", e.what()); + } + } + }); +} + +} // namespace routemon::http -- cgit v1.3