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