3#include "endpoint_http.hpp"
4#include "endpoint_http_regex.hpp"
5#include "endpoint_http_files.hpp"
6#include "endpoint_websocket.hpp"
7#include "type_traits.hpp"
8#include "../http/connection.hpp"
9#include "../http/connection_plain.hpp"
10#include "../http/connection_t.hpp"
11#include "../http/preflight_config.hpp"
12#include "../../core/type_traits.hpp"
13#include "../../core/http/generator.hpp"
14#include "../../core/http/http.hpp"
15#include "../../core/http/request.hpp"
16#include "../../core/http/response.hpp"
17#include "../../core/http/utils.hpp"
19 #include "../http/connection_tls.hpp"
22#include <boost/beast/core.hpp>
23#include <boost/beast/http.hpp>
24#include <boost/beast/version.hpp>
25#include <spdlog/logger.h>
41namespace malloy::server
43 class routing_context;
48 template<
typename T,
typename... Args>
51 t.do_write(std::forward<Args>(args)...);
63 using header_type = boost::beast::http::request_header<>;
65 void setup_body(
const header_type&,
typename request_type::body_type::value_type&)
const {}
77 template<
typename Body>
79 send_response(
const boost::beast::http::request_header<>& req,
malloy::http::response<Body>&& resp, http::connection_t connection, std::string_view server_str)
83 resp.version(req.version());
84 if (!malloy::http::has_field(resp, malloy::http::field::server))
85 resp.set(malloy::http::field::server, server_str);
86 resp.prepare_payload();
89 [resp = std::move(resp)](
auto& c)
mutable {
90 c->do_write(std::move(resp));
108 make_endpt_writer_callback()
110 return [
this]<
typename R>(
const auto& req, R&& resp,
const auto& conn) {
112 [&,
this]<
typename Re>(Re&& resp) {
113 detail::send_response(req, std::forward<Re>(resp), conn, m_server_str);
115 std::forward<R>(resp)
120 class abstract_req_validator
124 ~abstract_req_validator() =
default;
128 process(
const boost::beast::http::request_header<>&,
const http::connection_t& conn) = 0;
131 template<concepts::request_val
idator V,
typename Writer>
132 class req_validator_impl :
133 public abstract_req_validator
138 req_validator_impl(V validator, Writer writer_) :
139 writer{std::move(writer_)},
140 m_validator{std::move(validator)}
145 process(
const boost::beast::http::request_header<>& h,
const http::connection_t& conn)
override
147 auto maybe_resp = std::invoke(m_validator, h);
151 writer(h, std::move(*maybe_resp), conn);
163 policy_store(std::string reg, std::unique_ptr<abstract_req_validator> validator) :
164 m_validator{std::move(validator)},
165 m_raw_reg{std::move(reg)}
171 process(
const boost::beast::http::request_header<>& h,
const http::connection_t& conn)
const
173 if (!matches(h.target()))
176 return m_validator->process(h, conn);
181 matches(std::string_view url)
const
184 compile_match_expr();
185 std::string surl{url.begin(), url.end()};
186 return std::regex_match(surl, *m_compiled_reg);
190 compile_match_expr()
const
192 m_compiled_reg = std::regex{m_raw_reg};
195 std::unique_ptr<abstract_req_validator> m_validator;
196 mutable std::optional<std::regex> m_compiled_reg;
197 std::string m_raw_reg;
201 template<
typename Derived>
202 using req_generator = std::shared_ptr<typename http::connection<Derived>::request_generator>;
204 using request_header = boost::beast::http::request_header<>;
232 router(std::shared_ptr<spdlog::logger> logger);
273 set_logger(std::shared_ptr<spdlog::logger> logger);
283 add_subrouter(std::string resource, std::unique_ptr<router> sub_router);
307 add(
const method_type method,
const std::string_view target, Func&& handler, ExtraInfo&& extra)
309 using func_t = std::decay_t<Func>;
311 constexpr bool uses_captures = std::invocable<func_t, const request_type&, const std::vector<std::string>&>;
313 if constexpr (uses_captures) {
314 return add_regex_endpoint<
316 std::invoke_result_t<func_t, const request_type&, const std::vector<std::string>&>
318 method, target, std::forward<Func>(handler), std::forward<ExtraInfo>(extra)
322 return add_regex_endpoint<
324 std::invoke_result_t<func_t, const request_type&>
326 method, target, std::forward<Func>(handler), std::forward<ExtraInfo>(extra)
331 template<concepts::route_handler<
typename detail::default_route_filter::request_type> Func>
333 add(
const method_type method,
const std::string_view target, Func&& handler)
339 add_preflight(std::string_view target, http::preflight_config cfg);
350 template<malloy::concepts::callable_
string CacheControl>
352 add_file_serving(std::string resource, std::filesystem::path storage_base_path,
const CacheControl& cc)
356 m_logger->trace(
"adding file serving location: {} -> {}", resource, storage_base_path.string());
359 auto ep = std::make_unique<endpoint_http_files>();
360 ep->resource_base = resource;
361 ep->base_path = std::move(storage_base_path);
362 ep->cache_control = cc();
363 ep->writer = make_endpt_writer_callback();
366 return add_http_endpoint(std::move(ep));
381 std::move(storage_base_path),
382 []() -> std::string {
return ""; }
405 add_websocket(std::string&& resource,
typename websocket::connection::handler_t&& handler);
415 template<concepts::request_val
idator Policy>
420 m_logger->trace(
"adding policy: {}", resource);
422 using policy_t = std::decay_t<Policy>;
423 auto writer = [
this](
const auto& header,
auto&& resp,
auto&& conn) { detail::send_response(header, std::forward<
decltype(resp)>(resp), std::forward<
decltype(conn)>(conn), m_server_str); };
425 m_policies.emplace_back(resource, std::make_unique<req_validator_impl<policy_t,
decltype(writer)>>(std::forward<Policy>(policy), std::move(writer)));
445 bool isWebsocket =
false,
450 const std::filesystem::path& doc_root,
451 const req_generator<Derived>& req,
452 Connection&& connection
456 if constexpr (!isWebsocket) {
457 if (is_handled_by_policies<Derived>(req, connection))
462 for (
const auto& [resource_base,
router] : m_sub_routers) {
464 const auto res_str = malloy::http::resource_string(req->header());
465 if (!res_str.starts_with(resource_base))
469 malloy::http::chop_resource(req->header(), resource_base);
472 router->template handle_request<isWebsocket, Derived>(doc_root, std::move(req), connection);
483 if constexpr (isWebsocket)
484 handle_ws_request<Derived>(std::move(req), connection);
486 handle_http_request<Derived>(doc_root, std::move(req), connection);
491 server_string()
const
497 std::shared_ptr<spdlog::logger> m_logger{
nullptr};
498 std::unordered_map<std::string, std::unique_ptr<router>> m_sub_routers;
499 std::vector<std::unique_ptr<endpoint_http>> m_endpoints_http;
500 std::vector<std::unique_ptr<endpoint_websocket>> m_endpoints_websocket;
501 std::vector<policy_store> m_policies;
502 std::string_view m_server_str;
504 friend class routing_context;
506 router(std::shared_ptr<spdlog::logger> logger, std::string_view m_server_str);
509 set_server_string(std::string_view str);
511 template<
typename Derived>
514 is_handled_by_policies(
const req_generator<Derived>& req,
const http::connection_t& connection)
517 std::cbegin(m_policies),
518 std::cend(m_policies),
519 [&](
const policy_store& policy) {
520 return policy.process(req->header(), connection);
532 template<
typename Derived>
535 const std::filesystem::path&,
536 const req_generator<Derived>& req,
537 const http::connection_t& connection
542 m_logger->trace(
"handling HTTP request: {} {}",
543 std::string_view{req->header().method_string()},
544 std::string_view{req->header().target()}
548 const auto& header = req->header();
551 for (
const auto& ep : m_endpoints_http) {
553 if (!ep->matches(header))
557 auto resp = ep->handle(req, connection);
560 detail::send_response(req->header(), std::move(*resp), connection, m_server_str);
578 template<
typename Derived>
581 const req_generator<Derived>& gen,
582 const std::shared_ptr<websocket::connection>& connection
585 const auto res_string = malloy::http::resource_string(gen->header());
586 m_logger->trace(
"handling WS request: {} {}",
587 std::string_view{gen->header().method_string()},
592 for (
const auto& ep : m_endpoints_websocket) {
594 if (ep->resource != res_string)
599 m_logger->warn(
"websocket route with resource path \"{}\" has no valid handler assigned.");
604 req.base() = gen->header();
605 ep->handler(std::move(req), connection);
615 concepts::request_filter ExtraInfo,
618 add_regex_endpoint(
method_type method, std::string_view target, Func&& handler, ExtraInfo&& extra)
622 m_logger->trace(
"adding route: {}", target);
627 regex = std::regex{target.cbegin(), target.cend()};
629 catch (
const std::regex_error& e) {
631 m_logger->error(
"invalid route target supplied \"{}\": {}", target, e.what());
636 using bodies_t = std::conditional_t<wrapped, Body, std::variant<Body>>;
639 auto ep = std::make_unique<endpoint_http_regex<bodies_t, std::decay_t<ExtraInfo>, UsesCaptures>>();
640 ep->resource_base = std::move(regex);
642 ep->filter = std::forward<ExtraInfo>(extra);
643 if constexpr (wrapped) {
644 ep->handler = std::move(handler);
648 [w = std::forward<Func>(handler)](
auto&&... args) {
649 return std::variant<Body>{w(std::forward<
decltype(args)>(args)...)};
656 m_logger->warn(
"route has invalid handler. ignoring.");
660 ep->writer = make_endpt_writer_callback();
663 return add_http_endpoint(std::move(ep));
675 add_http_endpoint(std::unique_ptr<endpoint_http>&& ep);
686 add_websocket_endpoint(std::unique_ptr<endpoint_websocket>&& ep);
699 template<
typename FormatString,
typename... Args>
702 const std::exception& exception,
703 const spdlog::level::level_enum level,
704 const FormatString& fmt,
709 m_logger->log(level, fmt::runtime(fmt), std::forward<Args>(args)...);
static response bad_request(std::string_view reason)
Definition: generator.cpp:27
Definition: request.hpp:19
Definition: response.hpp:22
Definition: router.hpp:103
bool add_websocket(std::string &&resource, typename websocket::connection::handler_t &&handler)
Definition: router.cpp:189
router & operator=(router &&rhs) noexcept=default
bool add_redirect(malloy::http::status status, std::string &&resource_old, std::string &&resource_new)
Definition: router.cpp:157
router & operator=(const router &rhs)=delete
bool add_subrouter(std::string resource, std::unique_ptr< router > sub_router)
Definition: router.cpp:59
bool add_file_serving(std::string resource, std::filesystem::path storage_base_path, const CacheControl &cc)
Definition: router.hpp:352
void set_logger(std::shared_ptr< spdlog::logger > logger)
Definition: router.cpp:21
bool add(const method_type method, const std::string_view target, Func &&handler, ExtraInfo &&extra)
Definition: router.hpp:307
malloy::http::method method_type
Definition: router.hpp:209
void add_policy(const std::string &resource, Policy &&policy)
Definition: router.hpp:417
router(const router &other)=delete
router(router &&other) noexcept=default
bool add_file_serving(std::string resource, std::filesystem::path storage_base_path)
Definition: router.hpp:377
void handle_request(const std::filesystem::path &doc_root, const req_generator< Derived > &req, Connection &&connection)
Definition: router.hpp:449
Definition: type_traits.hpp:104
Definition: type_traits.hpp:34
Definition: type_traits.hpp:26
Definition: router.hpp:49
boost::beast::http::verb method
Definition: types.hpp:18
boost::beast::http::status status
Definition: types.hpp:23
Definition: router.hpp:61