| #ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTING_H_ |
| #define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTING_H_ |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| #include <stdexcept> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/log/log.h" |
| #include "boost/container/flat_map.hpp" // NOLINT |
| #include "tlbmc/redfish/black_magic.h" |
| #include "tlbmc/redfish/common.h" |
| #include "tlbmc/redfish/request.h" |
| #include "tlbmc/redfish/response.h" |
| #include "tlbmc/redfish/verb.h" |
| #include "router_interface.h" |
| |
| namespace milotic_tlbmc { |
| namespace crow { |
| |
| class BaseRule { |
| public: |
| explicit BaseRule(std::string_view thisRule) : rule_(thisRule) {} |
| virtual ~BaseRule() = default; |
| BaseRule(const BaseRule&) = delete; |
| BaseRule(BaseRule&&) = delete; |
| BaseRule& operator=(const BaseRule&) = delete; |
| BaseRule& operator=(const BaseRule&&) = delete; |
| virtual void Validate() = 0; |
| virtual void Handle(const milotic_tlbmc::RedfishRequest& request, |
| milotic_tlbmc::RedfishResponse& response, |
| const RoutingParams& params) = 0; |
| size_t methods_bit_field_{ |
| 1 << static_cast<size_t>(milotic_tlbmc::HttpVerb::kGet)}; |
| |
| static_assert(std::numeric_limits<decltype(methods_bit_field_)>::digits > |
| milotic_tlbmc::kMethodNotAllowedIndex, |
| "Not enough bits to store bitfield"); |
| |
| std::string rule_; |
| friend class Router; |
| template <typename T> |
| friend struct RuleParameterTraits; |
| }; |
| |
| template <typename T> |
| struct RuleParameterTraits { |
| using self_t = T; |
| self_t& methods(boost::beast::http::verb method) { |
| self_t* self = static_cast<self_t*>(this); |
| std::optional<milotic_tlbmc::HttpVerb> verb = |
| milotic_tlbmc::HttpVerbFromBoost(method); |
| if (verb) { |
| self->methods_bit_field_ = 1U << static_cast<size_t>(*verb); |
| } |
| return *self; |
| } |
| }; |
| |
| namespace detail { |
| namespace routing_handler_call_helper { |
| template <typename T, int Pos> |
| struct CallPair { |
| using type = T; |
| static const int pos = Pos; |
| }; |
| |
| template <typename H1> |
| struct CallParams { |
| H1& handler; |
| const RoutingParams& params; |
| const milotic_tlbmc::RedfishRequest& req; |
| milotic_tlbmc::RedfishResponse& resp; |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename S1, typename S2> |
| struct Call {}; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1, typename... Args2> |
| struct Call<F, NInt, NUint, NDouble, NString, |
| black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> { |
| void operator()(F cparams) { |
| using pushed = typename black_magic::S<Args2...>::template push_back< |
| CallPair<std::string, NString>>; |
| Call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, |
| pushed>()(cparams); |
| } |
| }; |
| |
| template <typename F, int NInt, int NUint, int NDouble, int NString, |
| typename... Args1> |
| struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<>, |
| black_magic::S<Args1...>> { |
| void operator()(F cparams) { |
| cparams.handler( |
| cparams.req, cparams.resp, |
| cparams.params.template Get<typename Args1::type>(Args1::pos)...); |
| } |
| }; |
| |
| } // namespace routing_handler_call_helper |
| } // namespace detail |
| |
| template <typename... Args> |
| class TaggedRule : public BaseRule, |
| public RuleParameterTraits<TaggedRule<Args...>> { |
| public: |
| using self_t = TaggedRule<Args...>; |
| |
| explicit TaggedRule(std::string_view ruleIn) : BaseRule(ruleIn) {} |
| |
| void Validate() override { |
| if (!handler_) { |
| throw std::runtime_error("no handler for url " + rule_); |
| } |
| } |
| |
| template <typename Func> |
| typename std::enable_if< |
| !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && |
| !black_magic::CallHelper< |
| Func, |
| black_magic::S<milotic_tlbmc::RedfishRequest, Args...>>::value, |
| void>::type |
| operator()(Func&& f) { |
| static_assert( |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || |
| black_magic::CallHelper< |
| Func, black_magic::S<milotic_tlbmc::RedfishRequest, |
| Args...>>::value || |
| black_magic::CallHelper< |
| Func, black_magic::S<milotic_tlbmc::RedfishRequest, |
| milotic_tlbmc::RedfishResponse&, |
| Args...>>::value, |
| "Handler type is mismatched with URL parameters"); |
| static_assert( |
| std::is_same<void, |
| decltype(f(std::declval<milotic_tlbmc::RedfishRequest>(), |
| std::declval<milotic_tlbmc::RedfishResponse&>(), |
| std::declval<Args>()...))>::value, |
| "Handler function with response argument should have void " |
| "return " |
| "type"); |
| |
| handler_ = std::forward<Func>(f); |
| } |
| |
| template <typename Func> |
| void operator()(std::string_view name, Func&& f) { |
| (*this).template operator()<Func>(std::forward(f)); |
| } |
| |
| template <typename Func> |
| void ReplaceHandler(Func&& f) { |
| this->operator()(f); |
| } |
| |
| template <typename Func> |
| void AppendHandler(Func&& f) { |
| this->operator()( |
| [f = std::forward<Func>(f), previous_func = this->handler_]( |
| const milotic_tlbmc::RedfishRequest & req, |
| milotic_tlbmc::RedfishResponse & resp, Args... args) { |
| previous_func(req, resp, args...); |
| f(req, resp, args...); |
| }); |
| } |
| |
| void Handle(const milotic_tlbmc::RedfishRequest& req, |
| milotic_tlbmc::RedfishResponse& resp, |
| const RoutingParams& params) override { |
| detail::routing_handler_call_helper::Call< |
| detail::routing_handler_call_helper::CallParams<decltype(handler_)>, 0, |
| 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()( |
| detail::routing_handler_call_helper::CallParams<decltype(handler_)>{ |
| handler_, params, req, resp}); |
| } |
| |
| private: |
| std::function<void(const milotic_tlbmc::RedfishRequest&, |
| milotic_tlbmc::RedfishResponse& resp, Args...)> |
| handler_; |
| }; |
| |
| // Trie is thread-compatible. |
| // A trie is used to store the rules and efficiently find the rule for a given |
| // URL and a method. |
| class Trie { |
| public: |
| struct Node { |
| unsigned rule_index{}; |
| std::array<size_t, static_cast<size_t>(ParamType::kMax)> param_children{}; |
| using ChildMap = boost::container::flat_map< |
| std::string, unsigned, std::less<>, |
| std::vector<std::pair<std::string, unsigned>>>; |
| ChildMap children; |
| |
| bool IsSimpleNode() const { |
| return rule_index == 0 && |
| std::all_of(std::begin(param_children), std::end(param_children), |
| [](size_t x) { return x == 0U; }); |
| } |
| }; |
| |
| Trie() : nodes_(1) {} |
| |
| // Builds the trie and optimizes it. |
| void Validate() { Optimize(); } |
| |
| std::pair<unsigned, RoutingParams> Find( |
| std::string_view url, const Node* node = nullptr, size_t pos = 0, |
| RoutingParams* params = nullptr) const; |
| |
| void Add(const std::string& url, unsigned index); |
| |
| private: |
| void OptimizeNode(Node* node); |
| |
| // Optimizes the trie by merging nodes that do not have rules. |
| void Optimize() { OptimizeNode(&nodes_.front()); } |
| |
| const Node* Head() const { return &nodes_.front(); } |
| |
| unsigned NewNode() { |
| nodes_.resize(nodes_.size() + 1); |
| return static_cast<unsigned>(nodes_.size() - 1); |
| } |
| |
| std::vector<Node> nodes_; |
| }; |
| |
| // Router is thread-compatible. |
| // Router provides a way to register rules and find the rule for a given URL and |
| // a method. |
| // After creation, `Handle` is safe to call from multiple threads |
| // simultaneously. |
| class Router { |
| public: |
| struct FindRoute { |
| BaseRule* rule = nullptr; |
| RoutingParams params; |
| }; |
| |
| struct FindRouteResponse { |
| FindRoute route; |
| }; |
| |
| Router() = default; |
| |
| // Creates a new rule with the given tag and rule string. |
| // The rule string must end with '/'. |
| // Returns a reference to the created rule. |
| template <uint64_t N> |
| typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& |
| NewRuleTagged(std::string_view rule) { |
| using RuleT = |
| typename black_magic::Arguments<N>::type::template rebind<TaggedRule>; |
| if (!rule.empty() && rule.back() != '/') { |
| throw std::runtime_error("rule must end with '/'"); |
| } |
| std::unique_ptr<RuleT> rule_object = std::make_unique<RuleT>(rule); |
| RuleT* ptr = rule_object.get(); |
| all_rules_.emplace_back(std::move(rule_object)); |
| |
| return *ptr; |
| } |
| |
| template <uint64_t Tag> |
| void RemoveRuleTagged(std::string_view url, boost::beast::http::verb method) { |
| auto it = FindRule<Tag>(url, method); |
| // Incorrect plugin configuration should kill the program. |
| if (it == all_rules_.end()) { |
| LOG(FATAL) << "Cannot Remove Rule: Rule not found: " << url << " " |
| << method; |
| } |
| all_rules_.erase(it); |
| } |
| |
| template <uint64_t Tag, typename Func> |
| void ReplaceHandler(std::string_view url, boost::beast::http::verb method, |
| Func&& func) { |
| auto iter = FindRule<Tag>(url, method); |
| if (iter == all_rules_.end()) { |
| LOG(FATAL) << "Cannot Replace Handler: Rule not found: " << url << " " |
| << method; |
| } |
| using RuleT = |
| black_magic::Arguments<Tag>::type::template rebind<TaggedRule>; |
| RuleT* rule = dynamic_cast<RuleT*>(iter->get()); |
| rule->ReplaceHandler(func); |
| } |
| template <uint64_t Tag, typename Func> |
| void AppendHandler(std::string_view url, boost::beast::http::verb method, |
| Func&& func) { |
| auto iter = FindRule<Tag>(url, method); |
| if (iter == all_rules_.end()) { |
| LOG(FATAL) << "Cannot append Handler: Rule not found: " << url << " " |
| << method; |
| } |
| using RuleT = |
| typename black_magic::Arguments<Tag>::type::template rebind<TaggedRule>; |
| RuleT* rule = dynamic_cast<RuleT*>(iter->get()); |
| rule->AppendHandler(func); |
| } |
| |
| // Optimizes the trie so it can be used for fast lookup. |
| void Validate(); |
| |
| std::vector<std::string_view> GetRegisteredRoutes() const; |
| |
| // Handles the request by finding the rule for the request and calling the |
| // handler inside the rule. |
| void Handle(const milotic_tlbmc::RedfishRequest& req, |
| milotic_tlbmc::RedfishResponse& asyncResp) const; |
| |
| void SetSmartRouter(::crow::RouterInterface* smart_router) { |
| smart_router_ = smart_router; |
| } |
| |
| protected: |
| template <uint64_t Tag> |
| std::vector<std::unique_ptr<BaseRule>>::iterator FindRule( |
| std::string_view url, boost::beast::http::verb method) { |
| std::optional<HttpVerb> verb = HttpVerbFromBoost(method); |
| if (!verb) { |
| return all_rules_.end(); |
| } |
| for (auto iter = all_rules_.begin(); iter != all_rules_.end(); ++iter) { |
| if ((*iter)->rule_ == url && ((*iter)->methods_bit_field_ & |
| (1 << static_cast<size_t>(*verb))) > 0) { |
| return iter; |
| } |
| } |
| return all_rules_.end(); |
| } |
| |
| struct PerMethod { |
| std::vector<BaseRule*> rules; |
| Trie trie; |
| // rule index 0 has special meaning; preallocate it to avoid |
| // duplication. |
| PerMethod() : rules(1) {} |
| }; |
| |
| FindRoute FindRouteByIndex(std::string_view url, size_t index) const; |
| |
| FindRouteResponse FindRouteByRequest( |
| const milotic_tlbmc::RedfishRequest& req) const; |
| |
| void InternalAddRuleObject(const std::string& rule, BaseRule* rule_object); |
| |
| std::array<PerMethod, milotic_tlbmc::kMethodNotAllowedIndex + 1> per_methods_; |
| std::vector<std::unique_ptr<BaseRule>> all_rules_; |
| ::crow::RouterInterface* smart_router_ = nullptr; |
| }; |
| |
| } // namespace crow |
| } // namespace milotic_tlbmc |
| |
| #endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTING_H_ |