summaryrefslogtreecommitdiffstats
path: root/server/src/http_server.cppm
diff options
context:
space:
mode:
authorRutger Broekhoff2026-08-29 12:01:42 +0200
committerRutger Broekhoff2026-08-29 12:01:42 +0200
commit52b3cd46c76fd4be18aeb8422a08a073484b9fad (patch)
treeb4f23957c096e6bce5369bb94a4e8e23de71761a /server/src/http_server.cppm
parent35878b76049b9c3e752480e9f298b7e2da6fdf1f (diff)
downloadroutemon-52b3cd46c76fd4be18aeb8422a08a073484b9fad.tar.gz
routemon-52b3cd46c76fd4be18aeb8422a08a073484b9fad.zip
More module implementation partition units
Diffstat (limited to 'server/src/http_server.cppm')
-rw-r--r--server/src/http_server.cppm522
1 files changed, 214 insertions, 308 deletions
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm
index f5b4c3e..dc8c183 100644
--- a/server/src/http_server.cppm
+++ b/server/src/http_server.cppm
@@ -137,25 +137,12 @@ public:
137 { 137 {
138 } 138 }
139 139
140 auto header() -> bhttp::response_header<bhttp::fields>& 140 auto header() -> bhttp::response_header<bhttp::fields>&;
141 { 141 auto header() const -> bhttp::response_header<bhttp::fields> const&;
142 return impl_->header(); 142 auto is_done() const -> bool;
143 } 143 auto prepare(beast::error_code& ec) -> const_buffers_type;
144 auto header() const -> bhttp::response_header<bhttp::fields> const& 144 auto consume(std::size_t n) -> void;
145 { 145 auto keep_alive() const noexcept -> bool;
146 return impl_->header();
147 }
148
149 auto is_done() const -> bool { return impl_->is_done(); }
150
151 auto prepare(beast::error_code& ec) -> const_buffers_type
152 {
153 return impl_->prepare(ec);
154 }
155
156 auto consume(std::size_t n) -> void { return impl_->consume(n); }
157
158 auto keep_alive() const noexcept -> bool { return impl_->keep_alive(); }
159}; 146};
160static_assert(beast::concepts::buffers_generator<presponse>); 147static_assert(beast::concepts::buffers_generator<presponse>);
161 148
@@ -207,15 +194,17 @@ struct base_ctx
207}; 194};
208 195
209template <class Ctx> 196template <class Ctx>
210using basic_route_handler_fn_t = std::function< 197using basic_route_handler_fn_t =
211 auto(Ctx, readable_request, std::vector<std::string> const& matches) 198 std::function<auto(
212 ->net::awaitable<presponse>>; 199 Ctx, readable_request,
200 std::vector<std::string> const& matches)
201 ->net::awaitable<presponse>>;
213 202
214struct keep_alive 203struct keep_alive
215{ 204{
216 bool value; 205 bool value;
217 206
218 explicit keep_alive(bool value) : value{value} {} 207 explicit keep_alive(bool value);
219}; 208};
220 209
221template <bhttp::concepts::body Body> 210template <bhttp::concepts::body Body>
@@ -229,37 +218,16 @@ auto make_rsp(bhttp::status status, keep_alive ka) -> bhttp::response<Body>
229 218
230auto problem_rsp( 219auto problem_rsp(
231 base_ctx const& ctx, problem::details const& problem, keep_alive ka) 220 base_ctx const& ctx, problem::details const& problem, keep_alive ka)
232 -> presponse 221 -> presponse;
233{
234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka);
235 rsp.set(bhttp::field::content_type, "application/problem+json");
236 rsp.body() = json::serialize(json::value_from(problem, ctx.locale));
237 rsp.prepare_payload();
238 return presponse{std::move(rsp)};
239}
240 222
241struct preflight_response 223struct preflight_response
242{ 224{
243 verb_set allow_methods; 225 verb_set allow_methods;
244 std::vector<bhttp::field> allow_headers; 226 std::vector<bhttp::field> allow_headers;
245}; 227};
228
246auto make_preflight_rsp(preflight_response res, keep_alive ka) 229auto make_preflight_rsp(preflight_response res, keep_alive ka)
247 -> bhttp::response<bhttp::empty_body> 230 -> bhttp::response<bhttp::empty_body>;
248{
249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka);
250 auto allow_headers_str = res.allow_headers
251 | std::views::transform(
252 [](auto const& field) -> std::string_view
253 { return bhttp::to_string(field); })
254 | std::views::join_with(std::string_view{", "})
255 | std::ranges::to<std::string>();
256 rsp.set(
257 bhttp::field::access_control_allow_methods,
258 res.allow_methods.to_string());
259 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
260 rsp.prepare_payload();
261 return rsp;
262}
263 231
264// Using base_ctx instead of a template here since that saves you 232// Using base_ctx instead of a template here since that saves you
265// typing on invocation (and we do not care about the context type 233// typing on invocation (and we do not care about the context type
@@ -276,8 +244,8 @@ auto read_request(base_ctx const& ctx, readable_request&& r)
276 244
277template <> 245template <>
278auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) 246auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
279 -> net::awaitable< 247 -> net::
280 std::expected<bhttp::request<bhttp::empty_body>, presponse>> 248 awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>>
281{ 249{
282 auto [ec, _] = 250 auto [ec, _] =
283 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); 251 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple);
@@ -340,19 +308,7 @@ auto default_options_handler(
340} 308}
341 309
342auto global_options_handler(base_ctx const& ctx, readable_request r) 310auto global_options_handler(base_ctx const& ctx, readable_request r)
343 -> net::awaitable<presponse> 311 -> net::awaitable<presponse>;
344{
345 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
346 // does? Same goes for default_options_handler? Not sure.
347 if (auto res = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
348 !res)
349 co_return std::move(res.error());
350 auto req = r.p->release();
351 auto rsp = make_rsp<bhttp::empty_body>(
352 bhttp::status::no_content, keep_alive{req.keep_alive()});
353 rsp.prepare_payload();
354 co_return std::move(rsp);
355}
356 312
357template <class Ctx> 313template <class Ctx>
358auto id_middleware( 314auto id_middleware(
@@ -427,28 +383,22 @@ struct handler_map
427 383
428 auto verbs() const -> verb_set 384 auto verbs() const -> verb_set
429 { 385 {
430 auto set = verb_set{}; 386 return verb_set{
431 if (static_cast<bool>(options)) 387 .options = static_cast<bool>(options),
432 set.enable(supported_verb::options); 388 .delete_ = static_cast<bool>(delete_),
433 if (static_cast<bool>(delete_)) 389 .get = static_cast<bool>(get),
434 set.enable(supported_verb::delete_); 390 .head = static_cast<bool>(head),
435 if (static_cast<bool>(get)) 391 .post = static_cast<bool>(post),
436 set.enable(supported_verb::get); 392 .put = static_cast<bool>(put),
437 if (static_cast<bool>(head)) 393 };
438 set.enable(supported_verb::head);
439 if (static_cast<bool>(post))
440 set.enable(supported_verb::post);
441 if (static_cast<bool>(put))
442 set.enable(supported_verb::put);
443 return set;
444 } 394 }
445 395
446 auto empty() const -> bool { return verbs().empty(); } 396 auto empty() const -> bool { return verbs().empty(); }
447 397
448 template <std::default_initializable U> 398 template <std::default_initializable U>
449 auto map(std::invocable<V const&> auto f) const -> handler_map<U> 399 auto map(std::invocable<V const&> auto f) const -> handler_map<U>
450 requires std::assignable_from< 400 requires std::
451 U&, std::invoke_result_t<decltype(f), V const&>> 401 assignable_from<U&, std::invoke_result_t<decltype(f), V const&>>
452 { 402 {
453 return { 403 return {
454 .options = static_cast<bool>(options) ? f(options) : U{}, 404 .options = static_cast<bool>(options) ? f(options) : U{},
@@ -508,8 +458,9 @@ template <class T>
508concept match_arg = std::constructible_from<T, std::string const&>; 458concept match_arg = std::constructible_from<T, std::string const&>;
509 459
510template <class Ctx, match_arg... MatchArgs> 460template <class Ctx, match_arg... MatchArgs>
511using route_handler_fn_t = std::function< 461using route_handler_fn_t =
512 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>; 462 std::function<auto(Ctx, readable_request, MatchArgs...)
463 ->net::awaitable<presponse>>;
513 464
514template <class Ctx, match_arg... MatchArgs> 465template <class Ctx, match_arg... MatchArgs>
515auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn) 466auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn)
@@ -588,271 +539,226 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
588 } 539 }
589}; 540};
590 541
591template <std::derived_from<base_ctx> PreRouteCtx> 542class router
592class server
593{ 543{
594 log::logger l_; 544 struct impl_base
595 locale::selector lsel_;
596 middleware_t<base_ctx, PreRouteCtx> global_middleware_;
597 route_tree<routed_ctx<PreRouteCtx>> routes_;
598
599public:
600 explicit server(
601 log::logger const& l, locale::selector&& lsel,
602 middleware_t<base_ctx, PreRouteCtx> global_middleware,
603 route_tree<routed_ctx<PreRouteCtx>> routes)
604 : l_{l.sub("http_server")}, lsel_{std::move(lsel)},
605 global_middleware_{std::move(global_middleware)},
606 routes_{std::move(routes)}
607 {
608 }
609
610 struct match_result
611 { 545 {
612 util::not_null< 546 virtual ~impl_base() = default;
613 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*> 547 virtual auto handle_request(readable_request r) const
614 route_handlers; 548 -> net::awaitable<presponse> = 0;
615 std::vector<std::string> wildcard_matches;
616
617 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); }
618 }; 549 };
619 550
620 auto match(boost::urls::segments_view segments) const 551 std::unique_ptr<impl_base> impl_;
621 -> std::optional<match_result> 552
553 template <std::derived_from<base_ctx> PreRouteCtx>
554 class impl : public impl_base
622 { 555 {
623 auto const* tree = &routes_; 556 locale::selector lsel_;
624 auto wildcard_matches = std::vector<std::string>{}; 557 middleware_t<base_ctx, PreRouteCtx> global_middleware_;
625 for (auto const& seg : segments) 558 route_tree<routed_ctx<PreRouteCtx>> routes_;
559
560 public:
561 explicit impl(
562 locale::selector&& lsel,
563 middleware_t<base_ctx, PreRouteCtx> global_middleware,
564 route_tree<routed_ctx<PreRouteCtx>> routes)
565 : lsel_{std::move(lsel)},
566 global_middleware_{std::move(global_middleware)},
567 routes_{std::move(routes)}
626 { 568 {
627 tree->sub.visit(
628 util::overloaded{
629 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const&
630 subtrees)
631 {
632 auto it = subtrees.find(seg);
633 tree = it == subtrees.end() ? nullptr : &it->second;
634 },
635 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
636 wildcard_subtree)
637 {
638 wildcard_matches.push_back(seg);
639 tree = &*wildcard_subtree;
640 },
641 });
642 if (!tree)
643 return std::nullopt;
644 } 569 }
645 if (tree->here.empty())
646 return std::nullopt;
647 return match_result{
648 .route_handlers = util::not_null{&tree->here},
649 .wildcard_matches = wildcard_matches,
650 };
651 }
652 570
653 auto route_request(PreRouteCtx ctx, readable_request r) const 571 struct match_result
654 -> net::awaitable<presponse> 572 {
655 { 573 util::not_null<
656 auto req_base = r.p->get().base(); 574 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*
575 >
576 route_handlers;
577 std::vector<std::string> wildcard_matches;
657 578
658 auto const bad_request_tpl = problem::tpl{ 579 auto allowed_methods() const -> verb_set
659 .status = bhttp::status::bad_request, 580 {
660 .title = translate("Bad request"), 581 return route_handlers->verbs();
661 .type_uri = "https://routemon.fautchen.eu/problems/bad-request", 582 }
662 }; 583 };
663 584
664 if (req_base.target() == "*") 585 auto match(boost::urls::segments_view segments) const
586 -> std::optional<match_result>
665 { 587 {
666 // request-target is in asterisk-form (RFC 9112, § 3.2.4), 588 auto const* tree = &routes_;
667 // so the request must be a server-wide OPTIONS request. 589 auto wildcard_matches = std::vector<std::string>{};
668 590 for (auto const& seg : segments)
669 if (req_base.method() != bhttp::verb::options)
670 { 591 {
671 auto tpl = problem::tpl{ 592 tree->sub.visit(
672 .status = bhttp::status::method_not_allowed, 593 util::overloaded{
673 .title = translate("Method not allowed"), 594 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const&
674 .type_uri = "https://routemon.fautchen.eu/problems/" 595 subtrees)
675 "method-not-allowed", 596 {
676 }; 597 auto it = subtrees.find(seg);
677 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 598 tree = it == subtrees.end() ? nullptr : &it->second;
599 },
600 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
601 wildcard_subtree)
602 {
603 wildcard_matches.push_back(seg);
604 tree = &*wildcard_subtree;
605 },
606 });
607 if (!tree)
608 return std::nullopt;
678 } 609 }
679 610 if (tree->here.empty())
680 co_return co_await global_options_handler(ctx, r); 611 return std::nullopt;
612 return match_result{
613 .route_handlers = util::not_null{&tree->here},
614 .wildcard_matches = wildcard_matches,
615 };
681 } 616 }
682 else if (auto mreq_url0 = boost::urls::parse_origin_form(req_base.target())) 617
618 auto route_request(PreRouteCtx ctx, readable_request r) const
619 -> net::awaitable<presponse>
683 { 620 {
684 // request-target is in origin-form (RFC 9112, § 3.2.1), 621 auto req_base = r.p->get().base();
685 // so it must be a normal request (not a CONNECT or
686 // server-wide OPTIONS request).
687 622
688 auto req_url = boost::urls::url{*mreq_url0}; 623 auto const bad_request_tpl = problem::tpl{
689 req_url.normalize(); 624 .status = bhttp::status::bad_request,
690 if (!req_url.is_path_absolute()) 625 .title = translate("Bad request"),
691 { 626 .type_uri = "https://routemon.fautchen.eu/problems/bad-request",
692 auto problem = bad_request_tpl.instantiate().set_detail(translate( 627 };
693 "Path of normalized (RFC 3986, § 6) "
694 "origin-form request-target (RFC "
695 "9112, § 3.2.1) should be "
696 "absolute"));
697 co_return problem_rsp(ctx, problem, keep_alive{false});
698 }
699 628
700 auto mres = match(req_url.segments()); 629 if (req_base.target() == "*")
701 if (!mres)
702 { 630 {
703 auto tpl = problem::tpl{ 631 // request-target is in asterisk-form (RFC 9112, § 3.2.4),
704 .status = bhttp::status::not_found, 632 // so the request must be a server-wide OPTIONS request.
705 .title = translate("Not found"),
706 .type_uri = "https://routemon.fautchen.eu/problems/not-found",
707 };
708 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
709 }
710 633
711 auto mverb = supported_verb::from(req_base.method()); 634 if (req_base.method() != bhttp::verb::options)
712 if (!mverb) 635 {
713 { 636 auto tpl = problem::tpl{
714 // Method not implemented. 637 .status = bhttp::status::method_not_allowed,
715 auto tpl = problem::tpl{ 638 .title = translate("Method not allowed"),
716 .status = bhttp::status::not_implemented, 639 .type_uri = "https://routemon.fautchen.eu/problems/"
717 .title = translate("Method not implemented"), 640 "method-not-allowed",
718 .type_uri = "https://routemon.fautchen.eu/problems/" 641 };
719 "method-not-implemented", 642 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
720 }; 643 }
721 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
722 }
723 644
724 if (auto mhdl = mres->route_handlers->lookup(*mverb)) 645 co_return co_await global_options_handler(ctx, r);
646 }
647 else if (auto mreq_url0 =
648 boost::urls::parse_origin_form(req_base.target()))
725 { 649 {
726 auto new_ctx = 650 // request-target is in origin-form (RFC 9112, § 3.2.1),
727 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()}; 651 // so it must be a normal request (not a CONNECT or
728 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches); 652 // server-wide OPTIONS request).
653
654 auto req_url = boost::urls::url{*mreq_url0};
655 req_url.normalize();
656 if (!req_url.is_path_absolute())
657 {
658 auto problem = bad_request_tpl.instantiate().set_detail(translate(
659 "Path of normalized (RFC 3986, § 6) "
660 "origin-form request-target (RFC "
661 "9112, § 3.2.1) should be "
662 "absolute"));
663 co_return problem_rsp(ctx, problem, keep_alive{false});
664 }
665
666 auto mres = match(req_url.segments());
667 if (!mres)
668 {
669 auto tpl = problem::tpl{
670 .status = bhttp::status::not_found,
671 .title = translate("Not found"),
672 .type_uri = "https://routemon.fautchen.eu/problems/not-found",
673 };
674 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
675 }
676
677 auto mverb = supported_verb::from(req_base.method());
678 if (!mverb)
679 {
680 // Method not implemented.
681 auto tpl = problem::tpl{
682 .status = bhttp::status::not_implemented,
683 .title = translate("Method not implemented"),
684 .type_uri = "https://routemon.fautchen.eu/problems/"
685 "method-not-implemented",
686 };
687 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
688 }
689
690 if (auto mhdl = mres->route_handlers->lookup(*mverb))
691 {
692 auto new_ctx =
693 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()};
694 co_return co_await mhdl(
695 std::move(new_ctx), r, mres->wildcard_matches);
696 }
697 else
698 {
699 // Path recognized, but method not allowed.
700 auto tpl = problem::tpl{
701 .status = bhttp::status::method_not_allowed,
702 .title = translate("Method not allowed"),
703 .type_uri = "https://routemon.fautchen.eu/problems/"
704 "method-not-allowed",
705 };
706 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
707 rsp.header().set(
708 bhttp::field::allow, mres->allowed_methods().to_string());
709 co_return std::move(rsp);
710 }
729 } 711 }
730 else 712 else
731 { 713 {
732 // Path recognized, but method not allowed. 714 // We do not accept any other request-target forms.
733 auto tpl = problem::tpl{ 715
734 .status = bhttp::status::method_not_allowed, 716 auto problem = bad_request_tpl.instantiate().set_detail(translate(
735 .title = translate("Method not allowed"), 717 "Invalid request-target, expected "
736 .type_uri = "https://routemon.fautchen.eu/problems/" 718 "asterisk-form or origin-form "
737 "method-not-allowed", 719 "(see RFC 9112, § 3.2)"));
738 }; 720 co_return problem_rsp(ctx, problem, keep_alive{false});
739 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
740 rsp.header().set(
741 bhttp::field::allow, mres->allowed_methods().to_string());
742 co_return std::move(rsp);
743 } 721 }
744 } 722 }
745 else
746 {
747 // We do not accept any other request-target forms.
748 723
749 auto problem = bad_request_tpl.instantiate().set_detail(translate( 724 auto handle_request(readable_request r) const
750 "Invalid request-target, expected " 725 -> net::awaitable<presponse> override
751 "asterisk-form or origin-form " 726 {
752 "(see RFC 9112, § 3.2)")); 727 auto header = r.p->get().base();
753 co_return problem_rsp(ctx, problem, keep_alive{false}); 728 auto locale = lsel_.select(header[bhttp::field::accept_language]);
729 co_return co_await global_middleware_(
730 base_ctx{.locale = locale}, header,
731 [&](PreRouteCtx ctx) -> net::awaitable<presponse>
732 { co_return co_await route_request(std::move(ctx), std::move(r)); });
754 } 733 }
755 } 734 };
756 735
757 auto handle_request(readable_request r) const -> net::awaitable<presponse> 736public:
737 template <std::derived_from<base_ctx> PreRouteCtx>
738 explicit router(
739 locale::selector&& lsel,
740 middleware_t<base_ctx, PreRouteCtx> global_middleware,
741 route_tree<routed_ctx<PreRouteCtx>> routes)
742 : impl_{std::make_unique<impl<PreRouteCtx>>(
743 std::move(lsel), std::move(global_middleware), std::move(routes))}
758 { 744 {
759 auto header = r.p->get().base();
760 auto locale = lsel_.select(header[bhttp::field::accept_language]);
761 auto ctx0 = base_ctx{.locale = locale};
762
763 co_return co_await global_middleware_(
764 std::move(ctx0), header,
765 [&](PreRouteCtx ctx) -> net::awaitable<presponse>
766 { co_return co_await route_request(std::move(ctx), std::move(r)); });
767 } 745 }
768 746
769 auto do_session(beast::tcp_stream strm) -> net::awaitable<void> 747 auto handle_request(readable_request r) const -> net::awaitable<presponse>;
770 { 748};
771 auto buf = beast::flat_buffer{};
772
773 while (true)
774 {
775 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
776 p0.body_limit(boost::none);
777 auto [ec, _] =
778 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
779 if (ec == bhttp::error::end_of_stream)
780 break;
781 else if (ec)
782 throw boost::system::system_error{ec};
783
784 auto http_version = p0.get().version();
785 auto&& rsp = co_await handle_request(
786 readable_request{
787 .p = util::not_null{&p0},
788 .strm = util::not_null{&strm},
789 .buf = util::not_null{&buf},
790 });
791 rsp.header().version(http_version);
792 bool keep_alive = rsp.keep_alive();
793 co_await beast::async_write(strm, std::move(rsp));
794 if (!keep_alive)
795 {
796 break;
797 }
798 }
799
800 strm.socket().shutdown(tcp::socket::shutdown_send);
801 }
802 749
803 auto do_listen(tcp::endpoint endpoint) -> net::awaitable<void> 750class server
804 { 751{
805 auto executor = co_await net::this_coro::executor; 752 log::logger l_;
806 auto acceptor = tcp::acceptor{executor, endpoint}; 753 router r_;
807 754
808 l_.with("endpoint", endpoint.address().to_string()) 755 auto do_session(beast::tcp_stream strm) -> net::awaitable<void>;
809 .with("port", std::to_string(endpoint.port())) 756 auto do_listen(tcp::endpoint endpoint) -> net::awaitable<void>;
810 .info("Serving");
811 while (true)
812 {
813 net::co_spawn(
814 executor,
815 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
816 [this](std::exception_ptr e)
817 {
818 if (e)
819 {
820 try
821 {
822 std::rethrow_exception(e);
823 }
824 catch (std::exception const& e)
825 {
826 l_.error("Error in session: {}", e.what());
827 }
828 }
829 });
830 }
831 }
832 757
833 auto spawn(net::io_context& ioc) -> void 758public:
834 { 759 explicit server(log::logger const& l, router&& r);
835 auto const addr = net::ip::make_address("0.0.0.0");
836 auto const endpoint = tcp::endpoint{addr, 8284};
837 760
838 // TODO: make exception handling as nice as in srv.cpp 761 auto spawn(net::io_context& ioc) -> void;
839 net::co_spawn(
840 ioc, do_listen(endpoint),
841 [this](std::exception_ptr e)
842 {
843 if (e)
844 {
845 try
846 {
847 std::rethrow_exception(e);
848 }
849 catch (std::exception const& e)
850 {
851 l_.error("Error: {}", e.what());
852 }
853 }
854 });
855 }
856}; 762};
857 763
858} // namespace routemon::http 764} // namespace routemon::http