summaryrefslogtreecommitdiffstats
path: root/server/src/http_server.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/http_server.cppm')
-rw-r--r--server/src/http_server.cppm390
1 files changed, 192 insertions, 198 deletions
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm
index 0f7fda7..366037f 100644
--- a/server/src/http_server.cppm
+++ b/server/src/http_server.cppm
@@ -41,7 +41,7 @@ private:
41 virtual ~impl_base() = default; 41 virtual ~impl_base() = default;
42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0; 42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0;
43 virtual auto header() const 43 virtual auto header() const
44 -> bhttp::response_header<bhttp::fields> const& = 0; 44 -> bhttp::response_header<bhttp::fields> const& = 0;
45 virtual auto is_done() const -> bool = 0; 45 virtual auto is_done() const -> bool = 0;
46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0; 46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0;
47 virtual auto consume(std::size_t n) -> void = 0; 47 virtual auto consume(std::size_t n) -> void = 0;
@@ -121,12 +121,12 @@ private:
121 auto keep_alive() const noexcept -> bool override 121 auto keep_alive() const noexcept -> bool override
122 { 122 {
123 return state_.visit( 123 return state_.visit(
124 util::overloaded{ 124 util::overloaded{
125 [](bhttp::response<Body> const& rsp) -> bool 125 [](bhttp::response<Body> const& rsp) -> bool
126 { return rsp.keep_alive(); }, 126 { return rsp.keep_alive(); },
127 [](bhttp::message_generator const& gen) -> bool 127 [](bhttp::message_generator const& gen) -> bool
128 { return gen.keep_alive(); }, 128 { return gen.keep_alive(); },
129 }); 129 });
130 } 130 }
131 }; 131 };
132 132
@@ -164,15 +164,15 @@ using next_handler_t = std::function<auto(Ctx)->net::awaitable<presponse>>;
164 164
165template <class OuterCtx, class InnerCtx> 165template <class OuterCtx, class InnerCtx>
166using middleware_t = 166using middleware_t =
167 std::function<auto( 167 std::function<auto(
168 OuterCtx, bhttp::request_header<bhttp::fields>&, 168 OuterCtx, bhttp::request_header<bhttp::fields>&,
169 next_handler_t<InnerCtx>) 169 next_handler_t<InnerCtx>)
170 ->net::awaitable<presponse>>; 170 ->net::awaitable<presponse>>;
171 171
172template <class Ctx> 172template <class Ctx>
173auto lax_cors_middleware( 173auto lax_cors_middleware(
174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr, 174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr,
175 next_handler_t<Ctx> next) -> net::awaitable<presponse> 175 next_handler_t<Ctx> next) -> net::awaitable<presponse>
176{ 176{
177 std::ignore = req_hdr; 177 std::ignore = req_hdr;
178 auto prersp = co_await next(ctx); 178 auto prersp = co_await next(ctx);
@@ -188,16 +188,16 @@ struct trace_id_ctx : InnerCtx
188 188
189template <class OuterCtx> 189template <class OuterCtx>
190auto trace_id_middleware( 190auto trace_id_middleware(
191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr, 191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr,
192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse> 192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse>
193{ 193{
194 std::ignore = req_hdr; 194 std::ignore = req_hdr;
195 auto ctx = trace_id_ctx{std::move(ctx0)}; 195 auto ctx = trace_id_ctx{std::move(ctx0)};
196 auto prersp = co_await next(std::move(ctx)); 196 auto prersp = co_await next(std::move(ctx));
197 prersp.header().set( 197 prersp.header().set(
198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()}); 198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()});
199 prersp.header().insert( 199 prersp.header().insert(
200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id"); 200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id");
201 co_return std::move(prersp); 201 co_return std::move(prersp);
202} 202}
203 203
@@ -208,8 +208,8 @@ struct base_ctx
208 208
209template <class Ctx> 209template <class Ctx>
210using basic_route_handler_fn_t = std::function< 210using basic_route_handler_fn_t = std::function<
211 auto(Ctx, readable_request, std::vector<std::string> const& matches) 211 auto(Ctx, readable_request, std::vector<std::string> const& matches)
212 ->net::awaitable<presponse>>; 212 ->net::awaitable<presponse>>;
213 213
214struct keep_alive 214struct keep_alive
215{ 215{
@@ -228,8 +228,8 @@ auto make_rsp(bhttp::status status, keep_alive ka) -> bhttp::response<Body>
228} 228}
229 229
230auto problem_rsp( 230auto problem_rsp(
231 base_ctx const& ctx, problem::details const& problem, keep_alive ka) 231 base_ctx const& ctx, problem::details const& problem, keep_alive ka)
232 -> presponse 232 -> presponse
233{ 233{
234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka); 234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka);
235 rsp.set(bhttp::field::content_type, "application/problem+json"); 235 rsp.set(bhttp::field::content_type, "application/problem+json");
@@ -244,18 +244,17 @@ struct preflight_response
244 std::vector<bhttp::field> allow_headers; 244 std::vector<bhttp::field> allow_headers;
245}; 245};
246auto make_preflight_rsp(preflight_response res, keep_alive ka) 246auto make_preflight_rsp(preflight_response res, keep_alive ka)
247 -> bhttp::response<bhttp::empty_body> 247 -> bhttp::response<bhttp::empty_body>
248{ 248{
249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka); 249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka);
250 auto allow_headers_str = res.allow_headers 250 auto allow_headers_str = res.allow_headers
251 | std::views::transform( 251 | std::views::transform(
252 [](auto const& field) -> std::string_view 252 [](auto const& field) -> std::string_view
253 { return bhttp::to_string(field); }) 253 { return bhttp::to_string(field); })
254 | std::views::join_with(std::string_view{", "}) 254 | std::views::join_with(std::string_view{", "})
255 | std::ranges::to<std::string>(); 255 | std::ranges::to<std::string>();
256 rsp.set( 256 rsp.set(
257 bhttp::field::access_control_allow_methods, 257 bhttp::field::access_control_allow_methods, res.allow_methods.to_string());
258 res.allow_methods.to_string());
259 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str); 258 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
260 rsp.prepare_payload(); 259 rsp.prepare_payload();
261 return rsp; 260 return rsp;
@@ -266,7 +265,7 @@ auto make_preflight_rsp(preflight_response res, keep_alive ka)
266// anyway, but all context types should derive from base_ctx). 265// anyway, but all context types should derive from base_ctx).
267template <bhttp::concepts::body_reader Body> 266template <bhttp::concepts::body_reader Body>
268auto read_request(base_ctx const& ctx, readable_request&& r) 267auto read_request(base_ctx const& ctx, readable_request&& r)
269 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>> 268 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>>
270{ 269{
271 std::ignore = ctx; 270 std::ignore = ctx;
272 auto p = bhttp::request_parser<Body>{std::move(*r.p)}; 271 auto p = bhttp::request_parser<Body>{std::move(*r.p)};
@@ -276,20 +275,19 @@ auto read_request(base_ctx const& ctx, readable_request&& r)
276 275
277template <> 276template <>
278auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) 277auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
279 -> net::awaitable< 278 -> net::awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>>
280 std::expected<bhttp::request<bhttp::empty_body>, presponse>>
281{ 279{
282 auto [ec, _] = 280 auto [ec, _] =
283 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); 281 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple);
284 if (ec == bhttp::error::unexpected_body) 282 if (ec == bhttp::error::unexpected_body)
285 { 283 {
286 auto tpl = problem::tpl{ 284 auto tpl = problem::tpl{
287 .status = bhttp::status::bad_request, 285 .status = bhttp::status::bad_request,
288 .title = translate("No body expected for this request"), 286 .title = translate("No body expected for this request"),
289 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body", 287 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body",
290 }; 288 };
291 co_return std::unexpected{problem_rsp( 289 co_return std::unexpected{problem_rsp(
292 ctx, tpl.instantiate(), keep_alive{false})}; 290 ctx, tpl.instantiate(), keep_alive{false})};
293 } 291 }
294 else if (ec) 292 else if (ec)
295 { 293 {
@@ -310,8 +308,8 @@ template <class Ctx>
310 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx); 308 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx);
311 } 309 }
312auto default_options_handler( 310auto default_options_handler(
313 Ctx const& ctx, readable_request r, std::vector<std::string> const&) 311 Ctx const& ctx, readable_request r, std::vector<std::string> const&)
314 -> net::awaitable<presponse> 312 -> net::awaitable<presponse>
315{ 313{
316 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); 314 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
317 if (!mreq) 315 if (!mreq)
@@ -321,18 +319,18 @@ auto default_options_handler(
321 { 319 {
322 // CORS preflight request 320 // CORS preflight request
323 co_return make_preflight_rsp( 321 co_return make_preflight_rsp(
324 preflight_response{ 322 preflight_response{
325 // TODO: should access-control-allow-methods contain OPTIONS? 323 // TODO: should access-control-allow-methods contain OPTIONS?
326 .allow_methods = ctx.route_methods, 324 .allow_methods = ctx.route_methods,
327 .allow_headers = {bhttp::field::content_type}, 325 .allow_headers = {bhttp::field::content_type},
328 }, 326 },
329 keep_alive{mreq->keep_alive()}); 327 keep_alive{mreq->keep_alive()});
330 } 328 }
331 else 329 else
332 { 330 {
333 // Normal OPTIONS request 331 // Normal OPTIONS request
334 auto rsp = make_rsp<bhttp::empty_body>( 332 auto rsp = make_rsp<bhttp::empty_body>(
335 bhttp::status::no_content, keep_alive{mreq->keep_alive()}); 333 bhttp::status::no_content, keep_alive{mreq->keep_alive()});
336 rsp.set(bhttp::field::allow, ctx.route_methods.to_string()); 334 rsp.set(bhttp::field::allow, ctx.route_methods.to_string());
337 rsp.prepare_payload(); 335 rsp.prepare_payload();
338 co_return std::move(rsp); 336 co_return std::move(rsp);
@@ -340,7 +338,7 @@ auto default_options_handler(
340} 338}
341 339
342auto global_options_handler(base_ctx const& ctx, readable_request r) 340auto global_options_handler(base_ctx const& ctx, readable_request r)
343 -> net::awaitable<presponse> 341 -> net::awaitable<presponse>
344{ 342{
345 // TODO: switch to "small (4KB) discarded" body type, similar to what Go 343 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
346 // does? Same goes for default_options_handler? Not sure. 344 // does? Same goes for default_options_handler? Not sure.
@@ -349,46 +347,45 @@ auto global_options_handler(base_ctx const& ctx, readable_request r)
349 co_return std::move(res.error()); 347 co_return std::move(res.error());
350 auto req = r.p->release(); 348 auto req = r.p->release();
351 auto rsp = make_rsp<bhttp::empty_body>( 349 auto rsp = make_rsp<bhttp::empty_body>(
352 bhttp::status::no_content, keep_alive{req.keep_alive()}); 350 bhttp::status::no_content, keep_alive{req.keep_alive()});
353 rsp.prepare_payload(); 351 rsp.prepare_payload();
354 co_return std::move(rsp); 352 co_return std::move(rsp);
355} 353}
356 354
357template <class Ctx> 355template <class Ctx>
358auto id_middleware( 356auto id_middleware(
359 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next) 357 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next)
360 -> net::awaitable<presponse> 358 -> net::awaitable<presponse>
361{ 359{
362 co_return co_await next(std::move(ctx)); 360 co_return co_await next(std::move(ctx));
363} 361}
364 362
365template <class A, class B, class C> 363template <class A, class B, class C>
366auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc) 364auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc)
367 -> middleware_t<A, C> 365 -> middleware_t<A, C>
368{ 366{
369 return [ab = std::move(ab), bc = std::move(bc)]( 367 return [ab = std::move(ab), bc = std::move(bc)](
370 A a, bhttp::request_header<bhttp::fields>& header, 368 A a, bhttp::request_header<bhttp::fields>& header,
371 next_handler_t<C> next) -> net::awaitable<presponse> 369 next_handler_t<C> next) -> net::awaitable<presponse>
372 { 370 {
373 co_return co_await ab( 371 co_return co_await ab(
374 std::move(a), header, [&](B b) -> net::awaitable<presponse> 372 std::move(a), header, [&](B b) -> net::awaitable<presponse>
375 { co_return co_await bc(std::move(b), header, next); }); 373 { co_return co_await bc(std::move(b), header, next); });
376 }; 374 };
377} 375}
378 376
379template <class A, class B> 377template <class A, class B>
380auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn) 378auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn)
381 -> basic_route_handler_fn_t<A> 379 -> basic_route_handler_fn_t<A>
382{ 380{
383 return 381 return [ab = std::move(ab), fn = std::move(fn)](
384 [ab = std::move(ab), fn = std::move(fn)]( 382 A a_ctx, readable_request r,
385 A a_ctx, readable_request r, 383 std::vector<std::string> const& matches) -> net::awaitable<presponse>
386 std::vector<std::string> const& matches) -> net::awaitable<presponse>
387 { 384 {
388 co_return co_await ab( 385 co_return co_await ab(
389 std::move(a_ctx), r.p->get().base(), 386 std::move(a_ctx), r.p->get().base(),
390 [&](B b_ctx) -> net::awaitable<presponse> 387 [&](B b_ctx) -> net::awaitable<presponse>
391 { co_return co_await fn(std::move(b_ctx), r, matches); }); 388 { co_return co_await fn(std::move(b_ctx), r, matches); });
392 }; 389 };
393} 390}
394 391
@@ -448,15 +445,15 @@ struct handler_map
448 template <std::default_initializable U> 445 template <std::default_initializable U>
449 auto map(std::invocable<V const&> auto f) const -> handler_map<U> 446 auto map(std::invocable<V const&> auto f) const -> handler_map<U>
450 requires std::assignable_from< 447 requires std::assignable_from<
451 U&, std::invoke_result_t<decltype(f), V const&>> 448 U&, std::invoke_result_t<decltype(f), V const&>>
452 { 449 {
453 return { 450 return {
454 .options = static_cast<bool>(options) ? f(options) : U{}, 451 .options = static_cast<bool>(options) ? f(options) : U{},
455 .delete_ = static_cast<bool>(delete_) ? f(delete_) : U{}, 452 .delete_ = static_cast<bool>(delete_) ? f(delete_) : U{},
456 .get = static_cast<bool>(get) ? f(get) : U{}, 453 .get = static_cast<bool>(get) ? f(get) : U{},
457 .head = static_cast<bool>(head) ? f(head) : U{}, 454 .head = static_cast<bool>(head) ? f(head) : U{},
458 .post = static_cast<bool>(post) ? f(post) : U{}, 455 .post = static_cast<bool>(post) ? f(post) : U{},
459 .put = static_cast<bool>(put) ? f(put) : U{}, 456 .put = static_cast<bool>(put) ? f(put) : U{},
460 }; 457 };
461 } 458 }
462}; 459};
@@ -478,29 +475,29 @@ struct route_tree
478 475
479template <class OuterCtx, class InnerCtx> 476template <class OuterCtx, class InnerCtx>
480auto middleware_wrap_tree( 477auto middleware_wrap_tree(
481 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree) 478 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree)
482 -> route_tree<OuterCtx> 479 -> route_tree<OuterCtx>
483{ 480{
484 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>( 481 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>(
485 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw)); 482 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw));
486 auto new_sub = tree.sub.visit( 483 auto new_sub = tree.sub.visit(
487 util::overloaded{ 484 util::overloaded{
488 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees) 485 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees)
489 -> decltype(route_tree<OuterCtx>::sub) 486 -> decltype(route_tree<OuterCtx>::sub)
490 { 487 {
491 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{}; 488 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{};
492 for (auto [seg, subtree] : subtrees) 489 for (auto [seg, subtree] : subtrees)
493 new_subtrees[seg] = middleware_wrap_tree(mw, subtree); 490 new_subtrees[seg] = middleware_wrap_tree(mw, subtree);
494 return new_subtrees; 491 return new_subtrees;
495 }, 492 },
496 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree) 493 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree)
497 -> decltype(route_tree<OuterCtx>::sub) 494 -> decltype(route_tree<OuterCtx>::sub)
498 { 495 {
499 return typename route_tree<OuterCtx>::wildcard_subtree{ 496 return typename route_tree<OuterCtx>::wildcard_subtree{
500 middleware_wrap_tree(mw, *subtree) 497 middleware_wrap_tree(mw, *subtree)
501 }; 498 };
502 }, 499 },
503 }); 500 });
504 return {.here = new_leaves, .sub = new_sub}; 501 return {.here = new_leaves, .sub = new_sub};
505} 502}
506 503
@@ -509,23 +506,21 @@ concept match_arg = std::constructible_from<T, std::string const&>;
509 506
510template <class Ctx, match_arg... MatchArgs> 507template <class Ctx, match_arg... MatchArgs>
511using route_handler_fn_t = std::function< 508using route_handler_fn_t = std::function<
512 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>; 509 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>;
513 510
514template <class Ctx, match_arg... MatchArgs> 511template <class Ctx, match_arg... MatchArgs>
515auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn) 512auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn)
516 -> basic_route_handler_fn_t<Ctx> 513 -> basic_route_handler_fn_t<Ctx>
517{ 514{
518 return 515 return [fn = std::move(fn)](
519 [fn = std::move(fn)]( 516 Ctx ctx, readable_request r,
520 Ctx ctx, readable_request r, 517 std::vector<std::string> const& matches) -> net::awaitable<presponse>
521 std::vector<std::string> const& matches) -> net::awaitable<presponse>
522 { 518 {
523 if (sizeof...(MatchArgs) != matches.size()) 519 if (sizeof...(MatchArgs) != matches.size())
524 throw std::runtime_error{"got unexpected amount of matches"}; 520 throw std::runtime_error{"got unexpected amount of matches"};
525 auto it = matches.begin(); 521 auto it = matches.begin();
526 co_return co_await fn( 522 co_return co_await fn(
527 std::move(ctx), r, 523 std::move(ctx), r, MatchArgs{static_cast<std::string const&>(*it++)}...);
528 MatchArgs{static_cast<std::string const&>(*it++)}...);
529 }; 524 };
530} 525}
531 526
@@ -534,7 +529,7 @@ struct ctree : route_tree<Ctx>
534{ 529{
535 template <class OuterCtx> 530 template <class OuterCtx>
536 auto wrap(middleware_t<OuterCtx, Ctx> mw) const 531 auto wrap(middleware_t<OuterCtx, Ctx> mw) const
537 -> ctree<OuterCtx, MatchArgs...> 532 -> ctree<OuterCtx, MatchArgs...>
538 { 533 {
539 return {middleware_wrap_tree(std::move(mw), *this)}; 534 return {middleware_wrap_tree(std::move(mw), *this)};
540 } 535 }
@@ -546,25 +541,24 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
546 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves 541 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves
547 { 542 {
548 auto here = this->template map<basic_route_handler_fn_t<Ctx>>( 543 auto here = this->template map<basic_route_handler_fn_t<Ctx>>(
549 degen_route_handler<Ctx, MatchArgs...>); 544 degen_route_handler<Ctx, MatchArgs...>);
550 if (!here.verbs().empty() && !static_cast<bool>(this->options)) 545 if (!here.verbs().empty() && !static_cast<bool>(this->options))
551 here.options = default_options_handler<Ctx>; 546 here.options = default_options_handler<Ctx>;
552 return here; 547 return here;
553 } 548 }
554 549
555 [[nodiscard]] auto named_subtrees( 550 [[nodiscard]] auto named_subtrees(
556 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>> 551 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>>
557 subtrees) const -> ctree<Ctx, MatchArgs...> 552 subtrees) const -> ctree<Ctx, MatchArgs...>
558 { 553 {
559 auto sub = typename route_tree<Ctx>::named_subtrees{ 554 auto sub = typename route_tree<Ctx>::named_subtrees{
560 std::from_range, subtrees 555 std::from_range, subtrees
561 | std::views::transform( 556 | std::views::transform(
562 [](auto const& p) 557 [](auto const& p)
563 { 558 {
564 return std::make_pair( 559 return std::make_pair(
565 p.first, 560 p.first, static_cast<route_tree<Ctx>>(p.second));
566 static_cast<route_tree<Ctx>>(p.second)); 561 })
567 })
568 }; 562 };
569 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}}; 563 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}};
570 } 564 }
@@ -572,13 +566,13 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
572 template <match_arg MatchArg> 566 template <match_arg MatchArg>
573 [[nodiscard]] auto 567 [[nodiscard]] auto
574 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree) 568 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree)
575 -> ctree<Ctx, MatchArgs...> 569 -> ctree<Ctx, MatchArgs...>
576 { 570 {
577 return {route_tree<Ctx>{ 571 return {route_tree<Ctx>{
578 .here = to_leaves(), 572 .here = to_leaves(),
579 .sub = typename route_tree<Ctx>::wildcard_subtree{ 573 .sub = typename route_tree<Ctx>::wildcard_subtree{
580 static_cast<route_tree<Ctx>>(subtree) 574 static_cast<route_tree<Ctx>>(subtree)
581 } 575 }
582 }}; 576 }};
583 } 577 }
584 578
@@ -598,9 +592,9 @@ class server
598 592
599public: 593public:
600 explicit server( 594 explicit server(
601 log::logger const& l, locale::selector&& lsel, 595 log::logger const& l, locale::selector&& lsel,
602 middleware_t<base_ctx, PreRouteCtx> global_middleware, 596 middleware_t<base_ctx, PreRouteCtx> global_middleware,
603 route_tree<routed_ctx<PreRouteCtx>> routes) 597 route_tree<routed_ctx<PreRouteCtx>> routes)
604 : l_{l.sub("http_server")}, lsel_{std::move(lsel)}, 598 : l_{l.sub("http_server")}, lsel_{std::move(lsel)},
605 global_middleware_{std::move(global_middleware)}, 599 global_middleware_{std::move(global_middleware)},
606 routes_{std::move(routes)} 600 routes_{std::move(routes)}
@@ -610,55 +604,56 @@ public:
610 struct match_result 604 struct match_result
611 { 605 {
612 util::not_null< 606 util::not_null<
613 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*> 607 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*>
614 route_handlers; 608 route_handlers;
615 std::vector<std::string> wildcard_matches; 609 std::vector<std::string> wildcard_matches;
616 610
617 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); } 611 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); }
618 }; 612 };
619 613
620 auto match(boost::urls::segments_view segments) const 614 auto match(boost::urls::segments_view segments) const
621 -> std::optional<match_result> 615 -> std::optional<match_result>
622 { 616 {
623 auto const* tree = &routes_; 617 auto const* tree = &routes_;
624 auto wildcard_matches = std::vector<std::string>{}; 618 auto wildcard_matches = std::vector<std::string>{};
625 for (auto const& seg : segments) 619 for (auto const& seg : segments)
626 { 620 {
627 tree->sub.visit( 621 tree->sub.visit(
628 util::overloaded{ 622 util::overloaded{
629 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& 623 [&](
630 subtrees) 624 route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& subtrees)
631 { 625 {
632 auto it = subtrees.find(seg); 626 auto it = subtrees.find(seg);
633 tree = it == subtrees.end() ? nullptr : &it->second; 627 tree = it == subtrees.end() ? nullptr : &it->second;
634 }, 628 },
635 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const& 629 [&](
636 wildcard_subtree) 630 route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
637 { 631 wildcard_subtree)
638 wildcard_matches.push_back(seg); 632 {
639 tree = &*wildcard_subtree; 633 wildcard_matches.push_back(seg);
640 }, 634 tree = &*wildcard_subtree;
641 }); 635 },
636 });
642 if (!tree) 637 if (!tree)
643 return std::nullopt; 638 return std::nullopt;
644 } 639 }
645 if (tree->here.empty()) 640 if (tree->here.empty())
646 return std::nullopt; 641 return std::nullopt;
647 return match_result{ 642 return match_result{
648 .route_handlers = util::not_null{&tree->here}, 643 .route_handlers = util::not_null{&tree->here},
649 .wildcard_matches = wildcard_matches, 644 .wildcard_matches = wildcard_matches,
650 }; 645 };
651 } 646 }
652 647
653 auto route_request(PreRouteCtx ctx, readable_request r) const 648 auto route_request(PreRouteCtx ctx, readable_request r) const
654 -> net::awaitable<presponse> 649 -> net::awaitable<presponse>
655 { 650 {
656 auto req_base = r.p->get().base(); 651 auto req_base = r.p->get().base();
657 652
658 auto const bad_request_tpl = problem::tpl{ 653 auto const bad_request_tpl = problem::tpl{
659 .status = bhttp::status::bad_request, 654 .status = bhttp::status::bad_request,
660 .title = translate("Bad request"), 655 .title = translate("Bad request"),
661 .type_uri = "https://routemon.fautchen.eu/problems/bad-request", 656 .type_uri = "https://routemon.fautchen.eu/problems/bad-request",
662 }; 657 };
663 658
664 if (req_base.target() == "*") 659 if (req_base.target() == "*")
@@ -669,10 +664,10 @@ public:
669 if (req_base.method() != bhttp::verb::options) 664 if (req_base.method() != bhttp::verb::options)
670 { 665 {
671 auto tpl = problem::tpl{ 666 auto tpl = problem::tpl{
672 .status = bhttp::status::method_not_allowed, 667 .status = bhttp::status::method_not_allowed,
673 .title = translate("Method not allowed"), 668 .title = translate("Method not allowed"),
674 .type_uri = "https://routemon.fautchen.eu/problems/" 669 .type_uri = "https://routemon.fautchen.eu/problems/"
675 "method-not-allowed", 670 "method-not-allowed",
676 }; 671 };
677 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 672 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
678 } 673 }
@@ -690,10 +685,10 @@ public:
690 if (!req_url.is_path_absolute()) 685 if (!req_url.is_path_absolute())
691 { 686 {
692 auto problem = bad_request_tpl.instantiate().set_detail(translate( 687 auto problem = bad_request_tpl.instantiate().set_detail(translate(
693 "Path of normalized (RFC 3986, § 6) " 688 "Path of normalized (RFC 3986, § 6) "
694 "origin-form request-target (RFC " 689 "origin-form request-target (RFC "
695 "9112, § 3.2.1) should be " 690 "9112, § 3.2.1) should be "
696 "absolute")); 691 "absolute"));
697 co_return problem_rsp(ctx, problem, keep_alive{false}); 692 co_return problem_rsp(ctx, problem, keep_alive{false});
698 } 693 }
699 694
@@ -701,9 +696,9 @@ public:
701 if (!mres) 696 if (!mres)
702 { 697 {
703 auto tpl = problem::tpl{ 698 auto tpl = problem::tpl{
704 .status = bhttp::status::not_found, 699 .status = bhttp::status::not_found,
705 .title = translate("Not found"), 700 .title = translate("Not found"),
706 .type_uri = "https://routemon.fautchen.eu/problems/not-found", 701 .type_uri = "https://routemon.fautchen.eu/problems/not-found",
707 }; 702 };
708 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 703 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
709 } 704 }
@@ -713,10 +708,10 @@ public:
713 { 708 {
714 // Method not implemented. 709 // Method not implemented.
715 auto tpl = problem::tpl{ 710 auto tpl = problem::tpl{
716 .status = bhttp::status::not_implemented, 711 .status = bhttp::status::not_implemented,
717 .title = translate("Method not implemented"), 712 .title = translate("Method not implemented"),
718 .type_uri = "https://routemon.fautchen.eu/problems/" 713 .type_uri = "https://routemon.fautchen.eu/problems/"
719 "method-not-implemented", 714 "method-not-implemented",
720 }; 715 };
721 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 716 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
722 } 717 }
@@ -724,21 +719,21 @@ public:
724 if (auto mhdl = mres->route_handlers->lookup(*mverb)) 719 if (auto mhdl = mres->route_handlers->lookup(*mverb))
725 { 720 {
726 auto new_ctx = 721 auto new_ctx =
727 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()}; 722 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()};
728 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches); 723 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches);
729 } 724 }
730 else 725 else
731 { 726 {
732 // Path recognized, but method not allowed. 727 // Path recognized, but method not allowed.
733 auto tpl = problem::tpl{ 728 auto tpl = problem::tpl{
734 .status = bhttp::status::method_not_allowed, 729 .status = bhttp::status::method_not_allowed,
735 .title = translate("Method not allowed"), 730 .title = translate("Method not allowed"),
736 .type_uri = "https://routemon.fautchen.eu/problems/" 731 .type_uri = "https://routemon.fautchen.eu/problems/"
737 "method-not-allowed", 732 "method-not-allowed",
738 }; 733 };
739 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 734 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
740 rsp.header().set( 735 rsp.header().set(
741 bhttp::field::allow, mres->allowed_methods().to_string()); 736 bhttp::field::allow, mres->allowed_methods().to_string());
742 co_return std::move(rsp); 737 co_return std::move(rsp);
743 } 738 }
744 } 739 }
@@ -747,9 +742,9 @@ public:
747 // We do not accept any other request-target forms. 742 // We do not accept any other request-target forms.
748 743
749 auto problem = bad_request_tpl.instantiate().set_detail(translate( 744 auto problem = bad_request_tpl.instantiate().set_detail(translate(
750 "Invalid request-target, expected " 745 "Invalid request-target, expected "
751 "asterisk-form or origin-form " 746 "asterisk-form or origin-form "
752 "(see RFC 9112, § 3.2)")); 747 "(see RFC 9112, § 3.2)"));
753 co_return problem_rsp(ctx, problem, keep_alive{false}); 748 co_return problem_rsp(ctx, problem, keep_alive{false});
754 } 749 }
755 } 750 }
@@ -761,9 +756,8 @@ public:
761 auto ctx0 = base_ctx{.locale = locale}; 756 auto ctx0 = base_ctx{.locale = locale};
762 757
763 co_return co_await global_middleware_( 758 co_return co_await global_middleware_(
764 std::move(ctx0), header, 759 std::move(ctx0), header, [&](PreRouteCtx ctx) -> net::awaitable<presponse>
765 [&](PreRouteCtx ctx) -> net::awaitable<presponse> 760 { co_return co_await route_request(std::move(ctx), std::move(r)); });
766 { co_return co_await route_request(std::move(ctx), std::move(r)); });
767 } 761 }
768 762
769 auto do_session(beast::tcp_stream strm) -> net::awaitable<void> 763 auto do_session(beast::tcp_stream strm) -> net::awaitable<void>
@@ -775,7 +769,7 @@ public:
775 auto p0 = bhttp::request_parser<bhttp::empty_body>{}; 769 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
776 p0.body_limit(boost::none); 770 p0.body_limit(boost::none);
777 auto [ec, _] = 771 auto [ec, _] =
778 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple); 772 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
779 if (ec == bhttp::error::end_of_stream) 773 if (ec == bhttp::error::end_of_stream)
780 break; 774 break;
781 else if (ec) 775 else if (ec)
@@ -783,11 +777,11 @@ public:
783 777
784 auto http_version = p0.get().version(); 778 auto http_version = p0.get().version();
785 auto&& rsp = co_await handle_request( 779 auto&& rsp = co_await handle_request(
786 readable_request{ 780 readable_request{
787 .p = util::not_null{&p0}, 781 .p = util::not_null{&p0},
788 .strm = util::not_null{&strm}, 782 .strm = util::not_null{&strm},
789 .buf = util::not_null{&buf}, 783 .buf = util::not_null{&buf},
790 }); 784 });
791 rsp.header().version(http_version); 785 rsp.header().version(http_version);
792 bool keep_alive = rsp.keep_alive(); 786 bool keep_alive = rsp.keep_alive();
793 co_await beast::async_write(strm, std::move(rsp)); 787 co_await beast::async_write(strm, std::move(rsp));
@@ -806,27 +800,27 @@ public:
806 auto acceptor = tcp::acceptor{executor, endpoint}; 800 auto acceptor = tcp::acceptor{executor, endpoint};
807 801
808 l_.with("endpoint", endpoint.address().to_string()) 802 l_.with("endpoint", endpoint.address().to_string())
809 .with("port", std::to_string(endpoint.port())) 803 .with("port", std::to_string(endpoint.port()))
810 .info("Serving"); 804 .info("Serving");
811 while (true) 805 while (true)
812 { 806 {
813 net::co_spawn( 807 net::co_spawn(
814 executor, 808 executor,
815 do_session(beast::tcp_stream{co_await acceptor.async_accept()}), 809 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
816 [this](std::exception_ptr e) 810 [this](std::exception_ptr e)
811 {
812 if (e)
817 { 813 {
818 if (e) 814 try
815 {
816 std::rethrow_exception(e);
817 }
818 catch (std::exception const& e)
819 { 819 {
820 try 820 l_.error("Error in session: {}", e.what());
821 {
822 std::rethrow_exception(e);
823 }
824 catch (std::exception const& e)
825 {
826 l_.error("Error in session: {}", e.what());
827 }
828 } 821 }
829 }); 822 }
823 });
830 } 824 }
831 } 825 }
832 826
@@ -837,21 +831,21 @@ public:
837 831
838 // TODO: make exception handling as nice as in srv.cpp 832 // TODO: make exception handling as nice as in srv.cpp
839 net::co_spawn( 833 net::co_spawn(
840 ioc, do_listen(endpoint), 834 ioc, do_listen(endpoint),
841 [this](std::exception_ptr e) 835 [this](std::exception_ptr e)
836 {
837 if (e)
842 { 838 {
843 if (e) 839 try
844 { 840 {
845 try 841 std::rethrow_exception(e);
846 {
847 std::rethrow_exception(e);
848 }
849 catch (std::exception const& e)
850 {
851 l_.error("Error: {}", e.what());
852 }
853 } 842 }
854 }); 843 catch (std::exception const& e)
844 {
845 l_.error("Error: {}", e.what());
846 }
847 }
848 });
855 } 849 }
856}; 850};
857 851