db-lab1/router.hpp

354 lines
11 KiB
C++

#ifndef _ROUTER_HPP
#define _ROUTER_HPP
#include <boost/beast.hpp>
#include <boost/json/src.hpp>
#include <string>
#include <regex>
#include <vector>
#include <map>
#include <memory>
#include <initializer_list>
#include <optional>
#include <pqxx/pqxx>
#include "database.hpp"
#include "session.hpp"
#include "utils.hpp"
#include "config.hpp"
namespace bserv {
namespace beast = boost::beast;
namespace http = beast::http;
using request_type = http::request<http::string_body>;
using response_type = http::response<http::string_body>;
namespace placeholders {
template <int N>
struct placeholder {};
#define make_place_holder(x) constexpr placeholder<x> _##x
make_place_holder(1);
make_place_holder(2);
make_place_holder(3);
make_place_holder(4);
make_place_holder(5);
make_place_holder(6);
make_place_holder(7);
make_place_holder(8);
make_place_holder(9);
#undef make_place_holder
// std::shared_ptr<bserv::session_type>
constexpr placeholder<-1> session;
// bserv::request_type&
constexpr placeholder<-2> request;
// bserv::response_type&
constexpr placeholder<-3> response;
// boost::json::object&&
constexpr placeholder<-4> json_params;
// std::shared_ptr<bserv::db_connection>
constexpr placeholder<-5> transaction;
} // placeholders
class bad_request_exception : public std::exception {
public:
bad_request_exception() = default;
const char* what() const noexcept { return "bad request"; }
};
namespace router_internal {
template <typename ...Types>
struct parameter_pack;
template <>
struct parameter_pack<> {};
template <typename Head, typename ...Tail>
struct parameter_pack<Head, Tail...>
: parameter_pack<Tail...> {
Head head_;
template <typename Head2, typename ...Tail2>
parameter_pack(Head2&& head, Tail2&& ...tail)
: parameter_pack<Tail...>{static_cast<Tail2&&>(tail)...},
head_{static_cast<Head2&&>(head)} {}
};
template <int Idx, typename ...Types>
struct get_parameter_pack;
template <int Idx, typename Head, typename ...Tail>
struct get_parameter_pack<Idx, Head, Tail...>
: get_parameter_pack<Idx - 1, Tail...> {};
template <typename Head, typename ...Tail>
struct get_parameter_pack<0, Head, Tail...> {
using type = parameter_pack<Head, Tail...>;
};
template <int Idx, typename ...Types>
decltype(auto) get_parameter_value(parameter_pack<Types...>& params) {
return (static_cast<
typename get_parameter_pack<Idx, Types...>::type&
>(params).head_);
}
template <int Idx, typename ...Types>
struct get_parameter;
template <int Idx, typename Head, typename ...Tail>
struct get_parameter<Idx, Head, Tail...>
: get_parameter<Idx - 1, Tail...> {};
template <typename Head, typename ...Tail>
struct get_parameter<0, Head, Tail...> {
using type = Head;
};
template <typename Type>
Type&& get_parameter_data(
const std::vector<std::string>&,
request_type&, response_type&, Type&& val) {
return static_cast<Type&&>(val);
}
template <int N, std::enable_if_t<(N >= 0), int> = 0>
const std::string& get_parameter_data(
const std::vector<std::string>& url_params,
request_type&, response_type&,
placeholders::placeholder<N>) {
return url_params[N];
}
std::shared_ptr<session_type> get_parameter_data(
const std::vector<std::string>&,
request_type& request, response_type& response,
placeholders::placeholder<-1>) {
std::string cookie_str{request[http::field::cookie]};
auto&& [cookie_dict, cookie_list]
= utils::parse_params(cookie_str, 0, ';');
boost::ignore_unused(cookie_list);
std::string session_id;
if (cookie_dict.count(SESSION_NAME) != 0) {
session_id = cookie_dict[SESSION_NAME];
}
std::shared_ptr<session_type> session_ptr;
if (session_mgr->get_or_create(session_id, session_ptr)) {
response.set(http::field::set_cookie, SESSION_NAME + "=" + session_id);
}
return session_ptr;
}
request_type& get_parameter_data(
const std::vector<std::string>&,
request_type& request, response_type&,
placeholders::placeholder<-2>) {
return request;
}
response_type& get_parameter_data(
const std::vector<std::string>&,
request_type&, response_type& response,
placeholders::placeholder<-3>) {
return response;
}
boost::json::object get_parameter_data(
const std::vector<std::string>&,
request_type& request, response_type&,
placeholders::placeholder<-4>) {
std::string target{request.target()};
auto&& [url, dict_params, list_params] = utils::parse_url(target);
boost::ignore_unused(url);
boost::json::object body;
if (!request.body().empty()) {
try {
body = boost::json::parse(request.body()).as_object();
} catch (const std::exception& e) {
throw bad_request_exception{};
}
}
for (auto& [k, v] : dict_params) {
if (!body.contains(k)) {
body[k] = v;
}
}
for (auto& [k, vs] : list_params) {
if (!body.contains(k)) {
boost::json::array a;
for (auto& v : vs) {
a.push_back(boost::json::string{v});
}
body[k] = a;
}
}
return body;
}
std::shared_ptr<db_connection> get_parameter_data(
const std::vector<std::string>&,
request_type&, response_type&,
placeholders::placeholder<-5>) {
return db_conn_mgr->get_or_block();
}
template <int Idx, typename Func, typename Params, typename ...Args>
struct path_handler;
template <int Idx, typename Ret, typename ...Args, typename ...Params>
struct path_handler<Idx, Ret (*)(Args ...), parameter_pack<Params...>> {
Ret invoke(Ret (*pf)(Args ...), parameter_pack<Params...>& params,
const std::vector<std::string>& url_params,
request_type& request, response_type& response) {
if constexpr (Idx == 0) return (*pf)();
else return static_cast<path_handler<
Idx - 1, Ret (*)(Args ...), parameter_pack<Params...>,
typename get_parameter<Idx - 1, Params...>::type>*
>(this)->invoke2(pf, params, url_params, request, response,
get_parameter_data(url_params, request, response,
get_parameter_value<Idx - 1>(params)));
}
};
template <int Idx, typename Ret, typename ...Args,
typename ...Params, typename Head, typename ...Tail>
struct path_handler<Idx, Ret (*)(Args ...),
parameter_pack<Params...>, Head, Tail...>
: path_handler<Idx + 1, Ret (*)(Args ...),
parameter_pack<Params...>, Tail...> {
template <
typename Head2, typename ...Tail2,
std::enable_if_t<sizeof...(Tail2) == sizeof...(Tail), int> = 0>
Ret invoke2(Ret (*pf)(Args ...), parameter_pack<Params...>& params,
const std::vector<std::string>& url_params,
request_type& request, response_type& response,
Head2&& head2, Tail2&& ...tail2) {
if constexpr (Idx == 0)
return (*pf)(static_cast<Head2&&>(head2),
static_cast<Tail2&&>(tail2)...);
else return static_cast<path_handler<
Idx - 1, Ret (*)(Args ...), parameter_pack<Params...>,
typename get_parameter<Idx - 1, Params...>::type, Head, Tail...>*
>(this)->invoke2(pf, params, url_params, request, response,
get_parameter_data(url_params, request, response,
get_parameter_value<Idx - 1>(params)),
static_cast<Head2&&>(head2), static_cast<Tail2&&>(tail2)...);
}
};
const std::vector<std::pair<std::regex, std::string>> url_regex_mapping{
{std::regex{"<int>"}, "([0-9]+)"},
{std::regex{"<str>"}, R"(([A-Za-z0-9_\.\-]+))"},
{std::regex{"<path>"}, R"(([A-Za-z0-9_/\.\-]+))"}
};
std::string get_re_url(const std::string& url) {
std::string re_url = url;
for (auto& [r, s] : url_regex_mapping)
re_url = std::regex_replace(re_url, r, s);
return re_url;
}
struct path_holder : std::enable_shared_from_this<path_holder> {
path_holder() = default;
virtual ~path_holder() = default;
virtual bool match(
const std::string&,
std::vector<std::string>&) const = 0;
virtual std::optional<boost::json::value> invoke(
const std::vector<std::string>&,
request_type&, response_type&) = 0;
};
template <typename Func, typename Params>
class path;
template <typename Ret, typename ...Args, typename ...Params>
class path<Ret (*)(Args ...), parameter_pack<Params...>>
: public path_holder {
private:
std::regex re_;
Ret (*pf_)(Args ...);
parameter_pack<Params...> params_;
path_handler<0, Ret (*)(Args ...), parameter_pack<Params...>, Params...> handler_;
public:
path(const std::string& url, Ret (*pf)(Args ...), Params&& ...params)
: re_{get_re_url(url)}, pf_{pf},
params_{static_cast<Params&&>(params)...} {}
bool match(const std::string& url, std::vector<std::string>& result) const {
std::smatch r;
bool matched = std::regex_match(url, r, re_);
if (matched) {
result.clear();
for (auto & sub : r)
result.push_back(sub.str());
}
return matched;
}
std::optional<boost::json::value> invoke(
const std::vector<std::string>& url_params,
request_type& request, response_type& response) {
return handler_.invoke(
pf_, params_, url_params,
request, response);
}
};
} // router_internal
template <typename Ret, typename ...Args, typename ...Params>
std::shared_ptr<router_internal::path<Ret (*)(Args ...),
router_internal::parameter_pack<Params...>>> make_path(
const std::string& url, Ret (*pf)(Args ...), Params&& ...params) {
return std::make_shared<
router_internal::path<Ret (*)(Args ...),
router_internal::parameter_pack<Params...>>
>(url, pf, static_cast<Params&&>(params)...);
}
template <typename Ret, typename ...Args, typename ...Params>
std::shared_ptr<router_internal::path<Ret (*)(Args ...),
router_internal::parameter_pack<Params...>>> make_path(
const char* url, Ret (*pf)(Args ...), Params&& ...params) {
return std::make_shared<
router_internal::path<Ret (*)(Args ...),
router_internal::parameter_pack<Params...>>
>(url, pf, static_cast<Params&&>(params)...);
}
class url_not_found_exception : public std::exception {
public:
url_not_found_exception() = default;
const char* what() const noexcept { return "url not found"; }
};
class router {
private:
using path_holder_type = std::shared_ptr<router_internal::path_holder>;
std::vector<path_holder_type> paths_;
public:
router(const std::initializer_list<path_holder_type>& paths)
: paths_{paths} {}
std::optional<boost::json::value> operator()(
const std::string& url, request_type& request, response_type& response) {
std::vector<std::string> url_params;
for (auto& ptr : paths_) {
if (ptr->match(url, url_params))
return ptr->invoke(url_params, request, response);
}
throw url_not_found_exception{};
}
};
} // bserv
#endif // _ROUTER_HPP