Make references to crow less obvious
Recently, a number of people in the community have made the (admittedly
easy) mistake that we use a significant portion of crow.
Today, we use crow for the router, and the "app" structure, and even
those have been significantly modified to meet the bmc needs. All other
components have been replaced with Boost beast. This commit removes the
crow mentions from the Readme, and moves the crow folder to "http" to
camouflage it a little. No code content has changed.
Tested:
Code compiles. No functional change made to any executable code.
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Change-Id: Iceb57b26306cc8bdcfc77f3874246338864fd118
diff --git a/http/LICENSE b/http/LICENSE
new file mode 100644
index 0000000..7295908
--- /dev/null
+++ b/http/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2014, ipkn
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/http/app.h b/http/app.h
new file mode 100644
index 0000000..fd01340
--- /dev/null
+++ b/http/app.h
@@ -0,0 +1,263 @@
+#pragma once
+
+#include "privileges.hpp"
+
+#include <chrono>
+#include <cstdint>
+#include <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "http_request.h"
+#include "http_server.h"
+#include "logging.h"
+#include "middleware_context.h"
+#include "routing.h"
+#include "utility.h"
+
+#define BMCWEB_ROUTE(app, url) \
+ app.template route<crow::black_magic::get_parameter_tag(url)>(url)
+
+namespace crow
+{
+#ifdef BMCWEB_ENABLE_SSL
+using ssl_context_t = boost::asio::ssl::context;
+#endif
+template <typename... Middlewares> class Crow
+{
+ public:
+ using self_t = Crow;
+
+#ifdef BMCWEB_ENABLE_SSL
+ using ssl_socket_t = boost::beast::ssl_stream<boost::asio::ip::tcp::socket>;
+ using ssl_server_t = Server<Crow, ssl_socket_t, Middlewares...>;
+#else
+ using socket_t = boost::asio::ip::tcp::socket;
+ using server_t = Server<Crow, socket_t, Middlewares...>;
+#endif
+
+ explicit Crow(std::shared_ptr<boost::asio::io_context> ioIn =
+ std::make_shared<boost::asio::io_context>()) :
+ io(std::move(ioIn))
+ {
+ }
+ ~Crow()
+ {
+ this->stop();
+ }
+
+ template <typename Adaptor>
+ void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor)
+ {
+ router.handleUpgrade(req, res, std::move(adaptor));
+ }
+
+ void handle(const Request& req, Response& res)
+ {
+ router.handle(req, res);
+ }
+
+ DynamicRule& routeDynamic(std::string&& rule)
+ {
+ return router.newRuleDynamic(rule);
+ }
+
+ template <uint64_t Tag> auto& route(std::string&& rule)
+ {
+ return router.newRuleTagged<Tag>(std::move(rule));
+ }
+
+ self_t& socket(int existing_socket)
+ {
+ socketFd = existing_socket;
+ return *this;
+ }
+
+ self_t& port(std::uint16_t port)
+ {
+ portUint = port;
+ return *this;
+ }
+
+ self_t& bindaddr(std::string bindaddr)
+ {
+ bindaddrStr = bindaddr;
+ return *this;
+ }
+
+ void validate()
+ {
+ router.validate();
+ }
+
+ void run()
+ {
+ validate();
+#ifdef BMCWEB_ENABLE_SSL
+ if (-1 == socketFd)
+ {
+ sslServer = std::move(std::make_unique<ssl_server_t>(
+ this, bindaddrStr, portUint, sslContext, &middlewares, io));
+ }
+ else
+ {
+ sslServer = std::move(std::make_unique<ssl_server_t>(
+ this, socketFd, sslContext, &middlewares, io));
+ }
+ sslServer->setTickFunction(tickInterval, tickFunction);
+ sslServer->run();
+
+#else
+
+ if (-1 == socketFd)
+ {
+ server = std::move(std::make_unique<server_t>(
+ this, bindaddrStr, portUint, nullptr, &middlewares, io));
+ }
+ else
+ {
+ server = std::move(std::make_unique<server_t>(
+ this, socketFd, nullptr, &middlewares, io));
+ }
+ server->setTickFunction(tickInterval, tickFunction);
+ server->run();
+
+#endif
+ }
+
+ void stop()
+ {
+ io->stop();
+ }
+
+ void debugPrint()
+ {
+ BMCWEB_LOG_DEBUG << "Routing:";
+ router.debugPrint();
+ }
+
+ std::vector<const std::string*> getRoutes()
+ {
+ // TODO(ed) Should this be /?
+ const std::string root("");
+ return router.getRoutes(root);
+ }
+ std::vector<const std::string*> getRoutes(const std::string& parent)
+ {
+ return router.getRoutes(parent);
+ }
+
+#ifdef BMCWEB_ENABLE_SSL
+ self_t& sslFile(const std::string& crt_filename,
+ const std::string& key_filename)
+ {
+ sslContext = std::make_shared<ssl_context_t>(
+ boost::asio::ssl::context::tls_server);
+ sslContext->set_verify_mode(boost::asio::ssl::verify_peer);
+ sslContext->use_certificate_file(crt_filename, ssl_context_t::pem);
+ sslContext->use_private_key_file(key_filename, ssl_context_t::pem);
+ sslContext->set_options(boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::no_sslv3 |
+ boost::asio::ssl::context::no_tlsv1 |
+ boost::asio::ssl::context::no_tlsv1_1);
+ return *this;
+ }
+
+ self_t& sslFile(const std::string& pem_filename)
+ {
+ sslContext = std::make_shared<ssl_context_t>(
+ boost::asio::ssl::context::tls_server);
+ sslContext->set_verify_mode(boost::asio::ssl::verify_peer);
+ sslContext->load_verify_file(pem_filename);
+ sslContext->set_options(boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::no_sslv3 |
+ boost::asio::ssl::context::no_tlsv1 |
+ boost::asio::ssl::context::no_tlsv1_1);
+ return *this;
+ }
+
+ self_t& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx)
+ {
+ sslContext = std::move(ctx);
+ BMCWEB_LOG_INFO << "app::ssl context use_count="
+ << sslContext.use_count();
+ return *this;
+ }
+
+ std::shared_ptr<ssl_context_t> sslContext = nullptr;
+
+#else
+ template <typename T, typename... Remain> self_t& ssl_file(T&&, Remain&&...)
+ {
+ // We can't call .ssl() member function unless BMCWEB_ENABLE_SSL is
+ // defined.
+ static_assert(
+ // make static_assert dependent to T; always false
+ std::is_base_of<T, void>::value,
+ "Define BMCWEB_ENABLE_SSL to enable ssl support.");
+ return *this;
+ }
+
+ template <typename T> self_t& ssl(T&&)
+ {
+ // We can't call .ssl() member function unless BMCWEB_ENABLE_SSL is
+ // defined.
+ static_assert(
+ // make static_assert dependent to T; always false
+ std::is_base_of<T, void>::value,
+ "Define BMCWEB_ENABLE_SSL to enable ssl support.");
+ return *this;
+ }
+#endif
+
+ // middleware
+ using context_t = detail::Context<Middlewares...>;
+ template <typename T> typename T::Context& getContext(const Request& req)
+ {
+ static_assert(black_magic::Contains<T, Middlewares...>::value,
+ "App doesn't have the specified middleware type.");
+ auto& ctx = *reinterpret_cast<context_t*>(req.middlewareContext);
+ return ctx.template get<T>();
+ }
+
+ template <typename T> T& getMiddleware()
+ {
+ return utility::getElementByType<T, Middlewares...>(middlewares);
+ }
+
+ template <typename Duration, typename Func> self_t& tick(Duration d, Func f)
+ {
+ tickInterval = std::chrono::duration_cast<std::chrono::milliseconds>(d);
+ tickFunction = f;
+ return *this;
+ }
+
+ private:
+ std::shared_ptr<asio::io_context> io;
+#ifdef BMCWEB_ENABLE_SSL
+ uint16_t portUint = 443;
+#else
+ uint16_t portUint = 80;
+#endif
+ std::string bindaddrStr = "::";
+ int socketFd = -1;
+ Router router;
+
+ std::chrono::milliseconds tickInterval{};
+ std::function<void()> tickFunction;
+
+ std::tuple<Middlewares...> middlewares;
+
+#ifdef BMCWEB_ENABLE_SSL
+ std::unique_ptr<ssl_server_t> sslServer;
+#else
+ std::unique_ptr<server_t> server;
+#endif
+};
+template <typename... Middlewares> using App = Crow<Middlewares...>;
+using SimpleApp = Crow<>;
+} // namespace crow
diff --git a/http/common.h b/http/common.h
new file mode 100644
index 0000000..77c2074
--- /dev/null
+++ b/http/common.h
@@ -0,0 +1,128 @@
+#pragma once
+
+#include <boost/beast/http/verb.hpp>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "utility.h"
+
+namespace crow
+{
+
+inline std::string methodName(boost::beast::http::verb method)
+{
+ switch (method)
+ {
+ case boost::beast::http::verb::delete_:
+ return "DELETE";
+ case boost::beast::http::verb::get:
+ return "GET";
+ case boost::beast::http::verb::head:
+ return "HEAD";
+ case boost::beast::http::verb::post:
+ return "POST";
+ case boost::beast::http::verb::put:
+ return "PUT";
+ case boost::beast::http::verb::connect:
+ return "CONNECT";
+ case boost::beast::http::verb::options:
+ return "OPTIONS";
+ case boost::beast::http::verb::trace:
+ return "TRACE";
+ case boost::beast::http::verb::patch:
+ return "PATCH";
+ default:
+ return "invalid";
+ }
+}
+
+enum class ParamType
+{
+ INT,
+ UINT,
+ DOUBLE,
+ STRING,
+ PATH,
+
+ MAX
+};
+
+struct RoutingParams
+{
+ std::vector<int64_t> intParams;
+ std::vector<uint64_t> uintParams;
+ std::vector<double> doubleParams;
+ std::vector<std::string> stringParams;
+
+ void debugPrint() const
+ {
+ std::cerr << "RoutingParams" << std::endl;
+ for (auto i : intParams)
+ {
+ std::cerr << i << ", ";
+ }
+ std::cerr << std::endl;
+ for (auto i : uintParams)
+ {
+ std::cerr << i << ", ";
+ }
+ std::cerr << std::endl;
+ for (auto i : doubleParams)
+ {
+ std::cerr << i << ", ";
+ }
+ std::cerr << std::endl;
+ for (auto& i : stringParams)
+ {
+ std::cerr << i << ", ";
+ }
+ std::cerr << std::endl;
+ }
+
+ template <typename T> T get(unsigned) const;
+};
+
+template <> inline int64_t RoutingParams::get<int64_t>(unsigned index) const
+{
+ return intParams[index];
+}
+
+template <> inline uint64_t RoutingParams::get<uint64_t>(unsigned index) const
+{
+ return uintParams[index];
+}
+
+template <> inline double RoutingParams::get<double>(unsigned index) const
+{
+ return doubleParams[index];
+}
+
+template <>
+inline std::string RoutingParams::get<std::string>(unsigned index) const
+{
+ return stringParams[index];
+}
+
+} // namespace crow
+
+constexpr boost::beast::http::verb operator"" _method(const char* str,
+ size_t /*len*/)
+{
+ using verb = boost::beast::http::verb;
+ // clang-format off
+ return
+ crow::black_magic::isEquP(str, "GET", 3) ? verb::get :
+ crow::black_magic::isEquP(str, "DELETE", 6) ? verb::delete_ :
+ crow::black_magic::isEquP(str, "HEAD", 4) ? verb::head :
+ crow::black_magic::isEquP(str, "POST", 4) ? verb::post :
+ crow::black_magic::isEquP(str, "PUT", 3) ? verb::put :
+ crow::black_magic::isEquP(str, "OPTIONS", 7) ? verb::options :
+ crow::black_magic::isEquP(str, "CONNECT", 7) ? verb::connect :
+ crow::black_magic::isEquP(str, "TRACE", 5) ? verb::trace :
+ crow::black_magic::isEquP(str, "PATCH", 5) ? verb::patch :
+ crow::black_magic::isEquP(str, "PURGE", 5) ? verb::purge :
+ throw std::runtime_error("invalid http method");
+ // clang-format on
+}
diff --git a/http/http_connection.h b/http/http_connection.h
new file mode 100644
index 0000000..ef42f84
--- /dev/null
+++ b/http/http_connection.h
@@ -0,0 +1,671 @@
+#pragma once
+#include "http_utility.hpp"
+
+#include <atomic>
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/ssl.hpp>
+#include <boost/beast/core/flat_static_buffer.hpp>
+#if BOOST_VERSION >= 107000
+#include <boost/beast/ssl/ssl_stream.hpp>
+#else
+#include <boost/beast/experimental/core/ssl_stream.hpp>
+#endif
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+#include <chrono>
+#include <vector>
+
+#include "http_response.h"
+#include "logging.h"
+#include "middleware_context.h"
+#include "timer_queue.h"
+#include "utility.h"
+
+namespace crow
+{
+
+inline void prettyPrintJson(crow::Response& res)
+{
+ std::string value = res.jsonValue.dump(4, ' ', true);
+ utility::escapeHtml(value);
+ utility::convertToLinks(value);
+ res.body() = "<html>\n"
+ "<head>\n"
+ "<title>Redfish API</title>\n"
+ "<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"/styles/default.css\">\n"
+ "<script src=\"/highlight.pack.js\"></script>"
+ "<script>hljs.initHighlightingOnLoad();</script>"
+ "</head>\n"
+ "<body>\n"
+ "<div style=\"max-width: 576px;margin:0 auto;\">\n"
+ "<img src=\"/DMTF_Redfish_logo_2017.svg\" alt=\"redfish\" "
+ "height=\"406px\" "
+ "width=\"576px\">\n"
+ "<br>\n"
+ "<pre>\n"
+ "<code class=\"json\">" +
+ value +
+ "</code>\n"
+ "</pre>\n"
+ "</div>\n"
+ "</body>\n"
+ "</html>\n";
+ res.addHeader("Content-Type", "text/html;charset=UTF-8");
+}
+
+using namespace boost;
+using tcp = asio::ip::tcp;
+
+namespace detail
+{
+template <typename MW> struct CheckBeforeHandleArity3Const
+{
+ template <typename T,
+ void (T::*)(Request&, Response&, typename MW::Context&) const =
+ &T::beforeHandle>
+ struct Get
+ {
+ };
+};
+
+template <typename MW> struct CheckBeforeHandleArity3
+{
+ template <typename T, void (T::*)(Request&, Response&,
+ typename MW::Context&) = &T::beforeHandle>
+ struct Get
+ {
+ };
+};
+
+template <typename MW> struct CheckAfterHandleArity3Const
+{
+ template <typename T,
+ void (T::*)(Request&, Response&, typename MW::Context&) const =
+ &T::afterHandle>
+ struct Get
+ {
+ };
+};
+
+template <typename MW> struct CheckAfterHandleArity3
+{
+ template <typename T, void (T::*)(Request&, Response&,
+ typename MW::Context&) = &T::afterHandle>
+ struct Get
+ {
+ };
+};
+
+template <typename T> struct IsBeforeHandleArity3Impl
+{
+ template <typename C>
+ static std::true_type
+ f(typename CheckBeforeHandleArity3Const<T>::template Get<C>*);
+
+ template <typename C>
+ static std::true_type
+ f(typename CheckBeforeHandleArity3<T>::template Get<C>*);
+
+ template <typename C> static std::false_type f(...);
+
+ public:
+ static const bool value = decltype(f<T>(nullptr))::value;
+};
+
+template <typename T> struct IsAfterHandleArity3Impl
+{
+ template <typename C>
+ static std::true_type
+ f(typename CheckAfterHandleArity3Const<T>::template Get<C>*);
+
+ template <typename C>
+ static std::true_type
+ f(typename CheckAfterHandleArity3<T>::template Get<C>*);
+
+ template <typename C> static std::false_type f(...);
+
+ public:
+ static const bool value = decltype(f<T>(nullptr))::value;
+};
+
+template <typename MW, typename Context, typename ParentContext>
+typename std::enable_if<!IsBeforeHandleArity3Impl<MW>::value>::type
+ beforeHandlerCall(MW& mw, Request& req, Response& res, Context& ctx,
+ ParentContext& /*parent_ctx*/)
+{
+ mw.beforeHandle(req, res, ctx.template get<MW>(), ctx);
+}
+
+template <typename MW, typename Context, typename ParentContext>
+typename std::enable_if<IsBeforeHandleArity3Impl<MW>::value>::type
+ beforeHandlerCall(MW& mw, Request& req, Response& res, Context& ctx,
+ ParentContext& /*parent_ctx*/)
+{
+ mw.beforeHandle(req, res, ctx.template get<MW>());
+}
+
+template <typename MW, typename Context, typename ParentContext>
+typename std::enable_if<!IsAfterHandleArity3Impl<MW>::value>::type
+ afterHandlerCall(MW& mw, Request& req, Response& res, Context& ctx,
+ ParentContext& /*parent_ctx*/)
+{
+ mw.afterHandle(req, res, ctx.template get<MW>(), ctx);
+}
+
+template <typename MW, typename Context, typename ParentContext>
+typename std::enable_if<IsAfterHandleArity3Impl<MW>::value>::type
+ afterHandlerCall(MW& mw, Request& req, Response& res, Context& ctx,
+ ParentContext& /*parent_ctx*/)
+{
+ mw.afterHandle(req, res, ctx.template get<MW>());
+}
+
+template <size_t N, typename Context, typename Container, typename CurrentMW,
+ typename... Middlewares>
+bool middlewareCallHelper(Container& middlewares, Request& req, Response& res,
+ Context& ctx)
+{
+ using parent_context_t = typename Context::template partial<N - 1>;
+ beforeHandlerCall<CurrentMW, Context, parent_context_t>(
+ std::get<N>(middlewares), req, res, ctx,
+ static_cast<parent_context_t&>(ctx));
+
+ if (res.isCompleted())
+ {
+ afterHandlerCall<CurrentMW, Context, parent_context_t>(
+ std::get<N>(middlewares), req, res, ctx,
+ static_cast<parent_context_t&>(ctx));
+ return true;
+ }
+
+ if (middlewareCallHelper<N + 1, Context, Container, Middlewares...>(
+ middlewares, req, res, ctx))
+ {
+ afterHandlerCall<CurrentMW, Context, parent_context_t>(
+ std::get<N>(middlewares), req, res, ctx,
+ static_cast<parent_context_t&>(ctx));
+ return true;
+ }
+
+ return false;
+}
+
+template <size_t N, typename Context, typename Container>
+bool middlewareCallHelper(Container& /*middlewares*/, Request& /*req*/,
+ Response& /*res*/, Context& /*ctx*/)
+{
+ return false;
+}
+
+template <size_t N, typename Context, typename Container>
+typename std::enable_if<(N < 0)>::type
+ afterHandlersCallHelper(Container& /*middlewares*/, Context& /*Context*/,
+ Request& /*req*/, Response& /*res*/)
+{
+}
+
+template <size_t N, typename Context, typename Container>
+typename std::enable_if<(N == 0)>::type
+ afterHandlersCallHelper(Container& middlewares, Context& ctx, Request& req,
+ Response& res)
+{
+ using parent_context_t = typename Context::template partial<N - 1>;
+ using CurrentMW = typename std::tuple_element<
+ N, typename std::remove_reference<Container>::type>::type;
+ afterHandlerCall<CurrentMW, Context, parent_context_t>(
+ std::get<N>(middlewares), req, res, ctx,
+ static_cast<parent_context_t&>(ctx));
+}
+
+template <size_t N, typename Context, typename Container>
+typename std::enable_if<(N > 0)>::type
+ afterHandlersCallHelper(Container& middlewares, Context& ctx, Request& req,
+ Response& res)
+{
+ using parent_context_t = typename Context::template partial<N - 1>;
+ using CurrentMW = typename std::tuple_element<
+ N, typename std::remove_reference<Container>::type>::type;
+ afterHandlerCall<CurrentMW, Context, parent_context_t>(
+ std::get<N>(middlewares), req, res, ctx,
+ static_cast<parent_context_t&>(ctx));
+ afterHandlersCallHelper<N - 1, Context, Container>(middlewares, ctx, req,
+ res);
+}
+} // namespace detail
+
+#ifdef BMCWEB_ENABLE_DEBUG
+static std::atomic<int> connectionCount;
+#endif
+
+// request body limit size: 30M
+constexpr unsigned int httpReqBodyLimit = 1024 * 1024 * 30;
+
+template <typename Adaptor, typename Handler, typename... Middlewares>
+class Connection
+{
+ public:
+ Connection(boost::asio::io_context& ioService, Handler* handlerIn,
+ const std::string& ServerNameIn,
+ std::tuple<Middlewares...>* middlewaresIn,
+ std::function<std::string()>& get_cached_date_str_f,
+ detail::TimerQueue& timerQueueIn, Adaptor adaptorIn) :
+ adaptor(std::move(adaptorIn)),
+ handler(handlerIn), serverName(ServerNameIn),
+ middlewares(middlewaresIn), getCachedDateStr(get_cached_date_str_f),
+ timerQueue(timerQueueIn)
+ {
+ parser.emplace(std::piecewise_construct, std::make_tuple());
+ // Temporarily changed to 30MB; Need to modify uploading/authentication
+ // mechanism
+ parser->body_limit(httpReqBodyLimit);
+ req.emplace(parser->get());
+#ifdef BMCWEB_ENABLE_DEBUG
+ connectionCount++;
+ BMCWEB_LOG_DEBUG << this << " Connection open, total "
+ << connectionCount;
+#endif
+ }
+
+ ~Connection()
+ {
+ res.completeRequestHandler = nullptr;
+ cancelDeadlineTimer();
+#ifdef BMCWEB_ENABLE_DEBUG
+ connectionCount--;
+ BMCWEB_LOG_DEBUG << this << " Connection closed, total "
+ << connectionCount;
+#endif
+ }
+
+ Adaptor& socket()
+ {
+ return adaptor;
+ }
+
+ void start()
+ {
+
+ startDeadline();
+ // TODO(ed) Abstract this to a more clever class with the idea of an
+ // asynchronous "start"
+ if constexpr (std::is_same_v<Adaptor,
+ boost::beast::ssl_stream<
+ boost::asio::ip::tcp::socket>>)
+ {
+ adaptor.async_handshake(
+ boost::asio::ssl::stream_base::server,
+ [this](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ checkDestroy();
+ return;
+ }
+ doReadHeaders();
+ });
+ }
+ else
+ {
+ doReadHeaders();
+ }
+ }
+
+ void handle()
+ {
+ cancelDeadlineTimer();
+ bool isInvalidRequest = false;
+
+ // Check for HTTP version 1.1.
+ if (req->version() == 11)
+ {
+ if (req->getHeaderValue(boost::beast::http::field::host).empty())
+ {
+ isInvalidRequest = true;
+ res.result(boost::beast::http::status::bad_request);
+ }
+ }
+
+ BMCWEB_LOG_INFO << "Request: "
+ << " " << this << " HTTP/" << req->version() / 10 << "."
+ << req->version() % 10 << ' ' << req->methodString()
+ << " " << req->target();
+
+ needToCallAfterHandlers = false;
+
+ if (!isInvalidRequest)
+ {
+ res.completeRequestHandler = [] {};
+ res.isAliveHelper = [this]() -> bool { return isAlive(); };
+
+ ctx = detail::Context<Middlewares...>();
+ req->middlewareContext = static_cast<void*>(&ctx);
+ req->ioService = static_cast<decltype(req->ioService)>(
+ &adaptor.get_executor().context());
+ detail::middlewareCallHelper<
+ 0U, decltype(ctx), decltype(*middlewares), Middlewares...>(
+ *middlewares, *req, res, ctx);
+
+ if (!res.completed)
+ {
+ if (req->isUpgrade() &&
+ boost::iequals(
+ req->getHeaderValue(boost::beast::http::field::upgrade),
+ "websocket"))
+ {
+ handler->handleUpgrade(*req, res, std::move(adaptor));
+ return;
+ }
+ res.completeRequestHandler = [this] {
+ this->completeRequest();
+ };
+ needToCallAfterHandlers = true;
+ handler->handle(*req, res);
+ }
+ else
+ {
+ completeRequest();
+ }
+ }
+ else
+ {
+ completeRequest();
+ }
+ }
+
+ bool isAlive()
+ {
+
+ if constexpr (std::is_same_v<Adaptor,
+ boost::beast::ssl_stream<
+ boost::asio::ip::tcp::socket>>)
+ {
+ return adaptor.next_layer().is_open();
+ }
+ else
+ {
+ return adaptor.is_open();
+ }
+ }
+ void close()
+ {
+
+ if constexpr (std::is_same_v<Adaptor,
+ boost::beast::ssl_stream<
+ boost::asio::ip::tcp::socket>>)
+ {
+ adaptor.next_layer().close();
+ }
+ else
+ {
+ adaptor.close();
+ }
+ }
+
+ void completeRequest()
+ {
+ BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' '
+ << res.resultInt() << " keepalive=" << req->keepAlive();
+
+ if (needToCallAfterHandlers)
+ {
+ needToCallAfterHandlers = false;
+
+ // call all afterHandler of middlewares
+ detail::afterHandlersCallHelper<sizeof...(Middlewares) - 1,
+ decltype(ctx),
+ decltype(*middlewares)>(
+ *middlewares, ctx, *req, res);
+ }
+
+ // auto self = this->shared_from_this();
+ res.completeRequestHandler = res.completeRequestHandler = [] {};
+
+ if (!isAlive())
+ {
+ // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " <<
+ // isReading
+ // << ' ' << isWriting;
+ // delete this;
+ return;
+ }
+ if (res.body().empty() && !res.jsonValue.empty())
+ {
+ if (http_helpers::requestPrefersHtml(*req))
+ {
+ prettyPrintJson(res);
+ }
+ else
+ {
+ res.jsonMode();
+ res.body() = res.jsonValue.dump(2, ' ', true);
+ }
+ }
+
+ if (res.resultInt() >= 400 && res.body().empty())
+ {
+ res.body() = std::string(res.reason());
+ }
+
+ if (res.result() == boost::beast::http::status::no_content)
+ {
+ // Boost beast throws if content is provided on a no-content
+ // response. Ideally, this would never happen, but in the case that
+ // it does, we don't want to throw.
+ BMCWEB_LOG_CRITICAL
+ << "Response content provided but code was no-content";
+ res.body().clear();
+ }
+
+ res.addHeader(boost::beast::http::field::server, serverName);
+ res.addHeader(boost::beast::http::field::date, getCachedDateStr());
+
+ res.keepAlive(req->keepAlive());
+
+ doWrite();
+ }
+
+ private:
+ void doReadHeaders()
+ {
+ // auto self = this->shared_from_this();
+ isReading = true;
+ BMCWEB_LOG_DEBUG << this << " doReadHeaders";
+
+ // Clean up any previous Connection.
+ boost::beast::http::async_read_header(
+ adaptor, buffer, *parser,
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ isReading = false;
+ BMCWEB_LOG_ERROR << this << " async_read_header "
+ << bytes_transferred << " Bytes";
+ bool errorWhileReading = false;
+ if (ec)
+ {
+ errorWhileReading = true;
+ BMCWEB_LOG_ERROR
+ << this << " Error while reading: " << ec.message();
+ }
+ else
+ {
+ // if the adaptor isn't open anymore, and wasn't handed to a
+ // websocket, treat as an error
+ if (!isAlive() && !req->isUpgrade())
+ {
+ errorWhileReading = true;
+ }
+ }
+
+ if (errorWhileReading)
+ {
+ cancelDeadlineTimer();
+ close();
+ BMCWEB_LOG_DEBUG << this << " from read(1)";
+ checkDestroy();
+ return;
+ }
+
+ // Compute the url parameters for the request
+ req->url = req->target();
+ std::size_t index = req->url.find("?");
+ if (index != std::string_view::npos)
+ {
+ req->url = req->url.substr(0, index);
+ }
+ req->urlParams = QueryString(std::string(req->target()));
+ doRead();
+ });
+ }
+
+ void doRead()
+ {
+ // auto self = this->shared_from_this();
+ isReading = true;
+ BMCWEB_LOG_DEBUG << this << " doRead";
+
+ boost::beast::http::async_read(
+ adaptor, buffer, *parser,
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ BMCWEB_LOG_ERROR << this << " async_read " << bytes_transferred
+ << " Bytes";
+ isReading = false;
+
+ bool errorWhileReading = false;
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Error while reading: " << ec.message();
+ errorWhileReading = true;
+ }
+ else
+ {
+ if (!isAlive())
+ {
+ errorWhileReading = true;
+ }
+ }
+ if (errorWhileReading)
+ {
+ cancelDeadlineTimer();
+ close();
+ BMCWEB_LOG_DEBUG << this << " from read(1)";
+ checkDestroy();
+ return;
+ }
+ handle();
+ });
+ }
+
+ void doWrite()
+ {
+ // auto self = this->shared_from_this();
+ isWriting = true;
+ BMCWEB_LOG_DEBUG << "Doing Write";
+ res.preparePayload();
+ serializer.emplace(*res.stringResponse);
+ boost::beast::http::async_write(
+ adaptor, *serializer,
+ [&](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ isWriting = false;
+ BMCWEB_LOG_DEBUG << this << " Wrote " << bytes_transferred
+ << " bytes";
+
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << this << " from write(2)";
+ checkDestroy();
+ return;
+ }
+ if (!res.keepAlive())
+ {
+ close();
+ BMCWEB_LOG_DEBUG << this << " from write(1)";
+ checkDestroy();
+ return;
+ }
+
+ serializer.reset();
+ BMCWEB_LOG_DEBUG << this << " Clearing response";
+ res.clear();
+ parser.emplace(std::piecewise_construct, std::make_tuple());
+ parser->body_limit(httpReqBodyLimit); // reset body limit for
+ // newly created parser
+ buffer.consume(buffer.size());
+
+ req.emplace(parser->get());
+ doReadHeaders();
+ });
+ }
+
+ void checkDestroy()
+ {
+ BMCWEB_LOG_DEBUG << this << " isReading " << isReading << " isWriting "
+ << isWriting;
+ if (!isReading && !isWriting)
+ {
+ BMCWEB_LOG_DEBUG << this << " delete (idle) ";
+ delete this;
+ }
+ }
+
+ void cancelDeadlineTimer()
+ {
+ BMCWEB_LOG_DEBUG << this << " timer cancelled: " << &timerQueue << ' '
+ << timerCancelKey;
+ timerQueue.cancel(timerCancelKey);
+ }
+
+ void startDeadline()
+ {
+ cancelDeadlineTimer();
+
+ timerCancelKey = timerQueue.add([this] {
+ if (!isAlive())
+ {
+ return;
+ }
+ close();
+ });
+ BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' '
+ << timerCancelKey;
+ }
+
+ private:
+ Adaptor adaptor;
+ Handler* handler;
+
+ // Making this a std::optional allows it to be efficiently destroyed and
+ // re-created on Connection reset
+ std::optional<
+ boost::beast::http::request_parser<boost::beast::http::string_body>>
+ parser;
+
+ boost::beast::flat_static_buffer<8192> buffer;
+
+ std::optional<boost::beast::http::response_serializer<
+ boost::beast::http::string_body>>
+ serializer;
+
+ std::optional<crow::Request> req;
+ crow::Response res;
+
+ const std::string& serverName;
+
+ size_t timerCancelKey = 0;
+
+ bool isReading{};
+ bool isWriting{};
+ bool needToCallAfterHandlers{};
+ bool needToStartReadAfterComplete{};
+
+ std::tuple<Middlewares...>* middlewares;
+ detail::Context<Middlewares...> ctx;
+
+ std::function<std::string()>& getCachedDateStr;
+ detail::TimerQueue& timerQueue;
+};
+} // namespace crow
diff --git a/http/http_request.h b/http/http_request.h
new file mode 100644
index 0000000..caae93a
--- /dev/null
+++ b/http/http_request.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "sessions.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/websocket.hpp>
+
+#include "common.h"
+#include "query_string.h"
+
+namespace crow
+{
+
+struct Request
+{
+ boost::beast::http::request<boost::beast::http::string_body>& req;
+ std::string_view url{};
+ QueryString urlParams{};
+ bool isSecure{false};
+
+ const std::string& body;
+
+ void* middlewareContext{};
+ boost::asio::io_context* ioService{};
+
+ std::shared_ptr<crow::persistent_data::UserSession> session;
+
+ Request(
+ boost::beast::http::request<boost::beast::http::string_body>& reqIn) :
+ req(reqIn),
+ body(reqIn.body())
+ {
+ }
+
+ boost::beast::http::verb method() const
+ {
+ return req.method();
+ }
+
+ const std::string_view getHeaderValue(std::string_view key) const
+ {
+ return req[key];
+ }
+
+ const std::string_view getHeaderValue(boost::beast::http::field key) const
+ {
+ return req[key];
+ }
+
+ const std::string_view methodString() const
+ {
+ return req.method_string();
+ }
+
+ const std::string_view target() const
+ {
+ return req.target();
+ }
+
+ unsigned version()
+ {
+ return req.version();
+ }
+
+ bool isUpgrade()
+ {
+ return boost::beast::websocket::is_upgrade(req);
+ }
+
+ bool keepAlive()
+ {
+ return req.keep_alive();
+ }
+};
+
+} // namespace crow
diff --git a/http/http_response.h b/http/http_response.h
new file mode 100644
index 0000000..6d7ca26
--- /dev/null
+++ b/http/http_response.h
@@ -0,0 +1,160 @@
+#pragma once
+#include "nlohmann/json.hpp"
+
+#include <boost/beast/http.hpp>
+#include <string>
+
+#include "http_request.h"
+#include "logging.h"
+
+namespace crow
+{
+
+template <typename Adaptor, typename Handler, typename... Middlewares>
+class Connection;
+
+struct Response
+{
+ template <typename Adaptor, typename Handler, typename... Middlewares>
+ friend class crow::Connection;
+ using response_type =
+ boost::beast::http::response<boost::beast::http::string_body>;
+
+ std::optional<response_type> stringResponse;
+
+ nlohmann::json jsonValue;
+
+ void addHeader(const std::string_view key, const std::string_view value)
+ {
+ stringResponse->set(key, value);
+ }
+
+ void addHeader(boost::beast::http::field key, std::string_view value)
+ {
+ stringResponse->set(key, value);
+ }
+
+ Response() : stringResponse(response_type{})
+ {
+ }
+
+ Response(Response&& r)
+ {
+ BMCWEB_LOG_DEBUG << "Moving response containers";
+ *this = std::move(r);
+ }
+
+ ~Response()
+ {
+ BMCWEB_LOG_DEBUG << this << " Destroying response";
+ }
+
+ Response& operator=(const Response& r) = delete;
+
+ Response& operator=(Response&& r) noexcept
+ {
+ BMCWEB_LOG_DEBUG << "Moving response containers";
+ stringResponse = std::move(r.stringResponse);
+ r.stringResponse.emplace(response_type{});
+ jsonValue = std::move(r.jsonValue);
+ completed = r.completed;
+ return *this;
+ }
+
+ void result(boost::beast::http::status v)
+ {
+ stringResponse->result(v);
+ }
+
+ boost::beast::http::status result()
+ {
+ return stringResponse->result();
+ }
+
+ unsigned resultInt()
+ {
+ return stringResponse->result_int();
+ }
+
+ std::string_view reason()
+ {
+ return stringResponse->reason();
+ }
+
+ bool isCompleted() const noexcept
+ {
+ return completed;
+ }
+
+ std::string& body()
+ {
+ return stringResponse->body();
+ }
+
+ void keepAlive(bool k)
+ {
+ stringResponse->keep_alive(k);
+ }
+
+ bool keepAlive()
+ {
+ return stringResponse->keep_alive();
+ }
+
+ void preparePayload()
+ {
+ stringResponse->prepare_payload();
+ };
+
+ void clear()
+ {
+ BMCWEB_LOG_DEBUG << this << " Clearing response containers";
+ stringResponse.emplace(response_type{});
+ jsonValue.clear();
+ completed = false;
+ }
+
+ void write(std::string_view body_part)
+ {
+ stringResponse->body() += std::string(body_part);
+ }
+
+ void end()
+ {
+ if (completed)
+ {
+ BMCWEB_LOG_ERROR << "Response was ended twice";
+ return;
+ }
+ completed = true;
+ BMCWEB_LOG_DEBUG << "calling completion handler";
+ if (completeRequestHandler)
+ {
+ BMCWEB_LOG_DEBUG << "completion handler was valid";
+ completeRequestHandler();
+ }
+ }
+
+ void end(std::string_view body_part)
+ {
+ write(body_part);
+ end();
+ }
+
+ bool isAlive()
+ {
+ return isAliveHelper && isAliveHelper();
+ }
+
+ private:
+ bool completed{};
+ std::function<void()> completeRequestHandler;
+ std::function<bool()> isAliveHelper;
+
+ // In case of a JSON object, set the Content-Type header
+ void jsonMode()
+ {
+ addHeader("Content-Type", "application/json");
+ }
+};
+} // namespace crow
diff --git a/http/http_server.h b/http/http_server.h
new file mode 100644
index 0000000..ef50bf7
--- /dev/null
+++ b/http/http_server.h
@@ -0,0 +1,295 @@
+#pragma once
+
+#include <atomic>
+#include <boost/asio/ip/address.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/signal_set.hpp>
+#include <boost/asio/ssl/context.hpp>
+#include <boost/asio/steady_timer.hpp>
+#if BOOST_VERSION >= 107000
+#include <boost/beast/ssl/ssl_stream.hpp>
+#else
+#include <boost/beast/experimental/core/ssl_stream.hpp>
+#endif
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <chrono>
+#include <cstdint>
+#include <filesystem>
+#include <future>
+#include <memory>
+#include <ssl_key_handler.hpp>
+#include <utility>
+#include <vector>
+
+#include "http_connection.h"
+#include "logging.h"
+#include "timer_queue.h"
+
+namespace crow
+{
+using namespace boost;
+using tcp = asio::ip::tcp;
+
+template <typename Handler, typename Adaptor = boost::asio::ip::tcp::socket,
+ typename... Middlewares>
+class Server
+{
+ public:
+ Server(Handler* handler, std::unique_ptr<tcp::acceptor>&& acceptor,
+ std::shared_ptr<boost::asio::ssl::context> adaptor_ctx,
+ std::tuple<Middlewares...>* middlewares = nullptr,
+ std::shared_ptr<boost::asio::io_context> io =
+ std::make_shared<boost::asio::io_context>()) :
+ ioService(std::move(io)),
+ acceptor(std::move(acceptor)),
+ signals(*ioService, SIGINT, SIGTERM, SIGHUP), tickTimer(*ioService),
+ handler(handler), middlewares(middlewares), adaptorCtx(adaptor_ctx)
+ {
+ }
+
+ Server(Handler* handler, const std::string& bindaddr, uint16_t port,
+ std::shared_ptr<boost::asio::ssl::context> adaptor_ctx,
+ std::tuple<Middlewares...>* middlewares = nullptr,
+ std::shared_ptr<boost::asio::io_context> io =
+ std::make_shared<boost::asio::io_context>()) :
+ Server(handler,
+ std::make_unique<tcp::acceptor>(
+ *io, tcp::endpoint(boost::asio::ip::make_address(bindaddr),
+ port)),
+ adaptor_ctx, middlewares, io)
+ {
+ }
+
+ Server(Handler* handler, int existing_socket,
+ std::shared_ptr<boost::asio::ssl::context> adaptor_ctx,
+ std::tuple<Middlewares...>* middlewares = nullptr,
+ std::shared_ptr<boost::asio::io_context> io =
+ std::make_shared<boost::asio::io_context>()) :
+ Server(handler,
+ std::make_unique<tcp::acceptor>(*io, boost::asio::ip::tcp::v6(),
+ existing_socket),
+ adaptor_ctx, middlewares, io)
+ {
+ }
+
+ void setTickFunction(std::chrono::milliseconds d, std::function<void()> f)
+ {
+ tickInterval = d;
+ tickFunction = f;
+ }
+
+ void onTick()
+ {
+ tickFunction();
+ tickTimer.expires_after(
+ std::chrono::milliseconds(tickInterval.count()));
+ tickTimer.async_wait([this](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ return;
+ }
+ onTick();
+ });
+ }
+
+ void updateDateStr()
+ {
+ time_t lastTimeT = time(0);
+ tm myTm{};
+
+ gmtime_r(&lastTimeT, &myTm);
+
+ dateStr.resize(100);
+ size_t dateStrSz =
+ strftime(&dateStr[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &myTm);
+ dateStr.resize(dateStrSz);
+ };
+
+ void run()
+ {
+ loadCertificate();
+ updateDateStr();
+
+ getCachedDateStr = [this]() -> std::string {
+ static std::chrono::time_point<std::chrono::steady_clock>
+ lastDateUpdate = std::chrono::steady_clock::now();
+ if (std::chrono::steady_clock::now() - lastDateUpdate >=
+ std::chrono::seconds(10))
+ {
+ lastDateUpdate = std::chrono::steady_clock::now();
+ updateDateStr();
+ }
+ return this->dateStr;
+ };
+
+ boost::asio::steady_timer timer(*ioService);
+ timer.expires_after(std::chrono::seconds(1));
+
+ std::function<void(const boost::system::error_code& ec)> timerHandler;
+ timerHandler = [&](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ return;
+ }
+ timerQueue.process();
+ timer.expires_after(std::chrono::seconds(1));
+ timer.async_wait(timerHandler);
+ };
+ timer.async_wait(timerHandler);
+
+ if (tickFunction && tickInterval.count() > 0)
+ {
+ tickTimer.expires_after(
+ std::chrono::milliseconds(tickInterval.count()));
+ tickTimer.async_wait([this](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ return;
+ }
+ onTick();
+ });
+ }
+
+ BMCWEB_LOG_INFO << serverName << " server is running, local endpoint "
+ << acceptor->local_endpoint();
+ startAsyncWaitForSignal();
+ doAccept();
+ }
+
+ void loadCertificate()
+ {
+#ifdef BMCWEB_ENABLE_SSL
+ namespace fs = std::filesystem;
+ // Cleanup older certificate file existing in the system
+ fs::path oldCert = "/home/root/server.pem";
+ if (fs::exists(oldCert))
+ {
+ fs::remove("/home/root/server.pem");
+ }
+ fs::path certPath = "/etc/ssl/certs/https/";
+ // if path does not exist create the path so that
+ // self signed certificate can be created in the
+ // path
+ if (!fs::exists(certPath))
+ {
+ fs::create_directories(certPath);
+ }
+ fs::path certFile = certPath / "server.pem";
+ BMCWEB_LOG_INFO << "Building SSL Context file=" << certFile;
+ std::string sslPemFile(certFile);
+ ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
+ std::shared_ptr<boost::asio::ssl::context> sslContext =
+ ensuressl::getSslContext(sslPemFile);
+ adaptorCtx = sslContext;
+ handler->ssl(std::move(sslContext));
+#endif
+ }
+
+ void startAsyncWaitForSignal()
+ {
+ signals.async_wait([this](const boost::system::error_code& ec,
+ int signalNo) {
+ if (ec)
+ {
+ BMCWEB_LOG_INFO << "Error in signal handler" << ec.message();
+ }
+ else
+ {
+ if (signalNo == SIGHUP)
+ {
+ BMCWEB_LOG_INFO << "Receivied reload signal";
+ loadCertificate();
+ this->startAsyncWaitForSignal();
+ }
+ else
+ {
+ stop();
+ }
+ }
+ });
+ }
+
+ void stop()
+ {
+ ioService->stop();
+ }
+
+ void doAccept()
+ {
+ std::optional<Adaptor> adaptorTemp;
+ if constexpr (std::is_same<Adaptor,
+ boost::beast::ssl_stream<
+ boost::asio::ip::tcp::socket>>::value)
+ {
+ adaptorTemp = Adaptor(*ioService, *adaptorCtx);
+ Connection<Adaptor, Handler, Middlewares...>* p =
+ new Connection<Adaptor, Handler, Middlewares...>(
+ *ioService, handler, serverName, middlewares,
+ getCachedDateStr, timerQueue,
+ std::move(adaptorTemp.value()));
+
+ acceptor->async_accept(p->socket().next_layer(),
+ [this, p](boost::system::error_code ec) {
+ if (!ec)
+ {
+ boost::asio::post(
+ *this->ioService,
+ [p] { p->start(); });
+ }
+ else
+ {
+ delete p;
+ }
+ doAccept();
+ });
+ }
+ else
+ {
+ adaptorTemp = Adaptor(*ioService);
+ Connection<Adaptor, Handler, Middlewares...>* p =
+ new Connection<Adaptor, Handler, Middlewares...>(
+ *ioService, handler, serverName, middlewares,
+ getCachedDateStr, timerQueue,
+ std::move(adaptorTemp.value()));
+
+ acceptor->async_accept(
+ p->socket(), [this, p](boost::system::error_code ec) {
+ if (!ec)
+ {
+ boost::asio::post(*this->ioService,
+ [p] { p->start(); });
+ }
+ else
+ {
+ delete p;
+ }
+ doAccept();
+ });
+ }
+ }
+
+ private:
+ std::shared_ptr<asio::io_context> ioService;
+ detail::TimerQueue timerQueue;
+ std::function<std::string()> getCachedDateStr;
+ std::unique_ptr<tcp::acceptor> acceptor;
+ boost::asio::signal_set signals;
+ boost::asio::steady_timer tickTimer;
+
+ std::string dateStr;
+
+ Handler* handler;
+ std::string serverName = "iBMC";
+
+ std::chrono::milliseconds tickInterval{};
+ std::function<void()> tickFunction;
+
+ std::tuple<Middlewares...>* middlewares;
+
+#ifdef BMCWEB_ENABLE_SSL
+ bool useSsl{false};
+#endif
+ std::shared_ptr<boost::asio::ssl::context> adaptorCtx;
+}; // namespace crow
+} // namespace crow
diff --git a/http/logging.h b/http/logging.h
new file mode 100644
index 0000000..a608f1f
--- /dev/null
+++ b/http/logging.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include <cstdio>
+#include <cstdlib>
+#include <ctime>
+#include <filesystem>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace crow
+{
+enum class LogLevel
+{
+ Debug = 0,
+ Info,
+ Warning,
+ Error,
+ Critical,
+};
+
+class logger
+{
+ private:
+ //
+ static std::string timestamp()
+ {
+ std::string date;
+ date.resize(32, '\0');
+ time_t t = time(0);
+
+ tm myTm{};
+
+ gmtime_r(&t, &myTm);
+
+ size_t sz =
+ strftime(date.data(), date.size(), "%Y-%m-%d %H:%M:%S", &myTm);
+ date.resize(sz);
+ return date;
+ }
+
+ public:
+ logger(const std::string& prefix, const std::string& filename,
+ const size_t line, LogLevel levelIn) :
+ level(levelIn)
+ {
+#ifdef BMCWEB_ENABLE_LOGGING
+ stringstream << "(" << timestamp() << ") [" << prefix << " "
+ << std::filesystem::path(filename).filename() << ":"
+ << line << "] ";
+#endif
+ }
+ ~logger()
+ {
+ if (level >= get_current_log_level())
+ {
+#ifdef BMCWEB_ENABLE_LOGGING
+ stringstream << std::endl;
+ std::cerr << stringstream.str();
+#endif
+ }
+ }
+
+ //
+ template <typename T> logger& operator<<(T const& value)
+ {
+ if (level >= get_current_log_level())
+ {
+#ifdef BMCWEB_ENABLE_LOGGING
+ stringstream << value;
+#endif
+ }
+ return *this;
+ }
+
+ //
+ static void setLogLevel(LogLevel level)
+ {
+ getLogLevelRef() = level;
+ }
+
+ static LogLevel get_current_log_level()
+ {
+ return getLogLevelRef();
+ }
+
+ private:
+ //
+ static LogLevel& getLogLevelRef()
+ {
+ static auto currentLevel = static_cast<LogLevel>(1);
+ return currentLevel;
+ }
+
+ //
+ std::ostringstream stringstream;
+ LogLevel level;
+};
+} // namespace crow
+
+#define BMCWEB_LOG_CRITICAL \
+ if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \
+ crow::logger("CRITICAL", __FILE__, __LINE__, crow::LogLevel::Critical)
+#define BMCWEB_LOG_ERROR \
+ if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \
+ crow::logger("ERROR", __FILE__, __LINE__, crow::LogLevel::Error)
+#define BMCWEB_LOG_WARNING \
+ if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \
+ crow::logger("WARNING", __FILE__, __LINE__, crow::LogLevel::Warning)
+#define BMCWEB_LOG_INFO \
+ if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \
+ crow::logger("INFO", __FILE__, __LINE__, crow::LogLevel::Info)
+#define BMCWEB_LOG_DEBUG \
+ if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \
+ crow::logger("DEBUG", __FILE__, __LINE__, crow::LogLevel::Debug)
diff --git a/http/middleware_context.h b/http/middleware_context.h
new file mode 100644
index 0000000..fbe1d80
--- /dev/null
+++ b/http/middleware_context.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "http_request.h"
+#include "http_response.h"
+#include "utility.h"
+
+namespace crow
+{
+namespace detail
+{
+template <typename... Middlewares>
+struct PartialContext
+ : public black_magic::PopBack<Middlewares...>::template rebind<
+ PartialContext>,
+ public black_magic::LastElementType<Middlewares...>::type::Context
+{
+ using parent_context = typename black_magic::PopBack<
+ Middlewares...>::template rebind<::crow::detail::PartialContext>;
+ template <size_t N>
+ using partial = typename std::conditional<
+ N == sizeof...(Middlewares) - 1, PartialContext,
+ typename parent_context::template partial<N>>::type;
+
+ template <typename T> typename T::Context& get()
+ {
+ return static_cast<typename T::Context&>(*this);
+ }
+};
+
+template <> struct PartialContext<>
+{
+ template <size_t> using partial = PartialContext;
+};
+
+template <size_t N, typename Context, typename Container, typename CurrentMW,
+ typename... Middlewares>
+bool middlewareCallHelper(Container& middlewares, Request& req, Response& res,
+ Context& ctx);
+
+template <typename... Middlewares>
+struct Context : private PartialContext<Middlewares...>
+// struct Context : private Middlewares::context... // simple but less type-safe
+{
+ template <size_t N, typename Context, typename Container>
+ friend typename std::enable_if<(N == 0)>::type
+ afterHandlersCallHelper(Container& middlewares, Context& ctx,
+ Request& req, Response& res);
+ template <size_t N, typename Context, typename Container>
+ friend typename std::enable_if<(N > 0)>::type
+ afterHandlersCallHelper(Container& middlewares, Context& ctx,
+ Request& req, Response& res);
+
+ template <size_t N, typename Context, typename Container,
+ typename CurrentMW, typename... Middlewares2>
+ friend bool middlewareCallHelper(Container& middlewares, Request& req,
+ Response& res, Context& ctx);
+
+ template <typename T> typename T::Context& get()
+ {
+ return static_cast<typename T::Context&>(*this);
+ }
+
+ template <size_t N>
+ using partial =
+ typename PartialContext<Middlewares...>::template partial<N>;
+};
+} // namespace detail
+} // namespace crow
diff --git a/http/query_string.h b/http/query_string.h
new file mode 100644
index 0000000..0cd21e4
--- /dev/null
+++ b/http/query_string.h
@@ -0,0 +1,418 @@
+#pragma once
+
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace crow
+{
+// ----------------------------------------------------------------------------
+// qs_parse (modified)
+// https://github.com/bartgrantham/qs_parse
+// ----------------------------------------------------------------------------
+/* Similar to strncmp, but handles URL-encoding for either string */
+int qsStrncmp(const char* s, const char* qs, size_t n);
+
+/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
+ * Also decodes the value portion of the k/v pair *in-place*. In a future
+ * enhancement it will also have a compile-time option of sorting qs_kv
+ * alphabetically by key. */
+size_t qsParse(char* qs, char* qs_kv[], size_t qs_kv_size);
+
+/* Used by qs_parse to decode the value portion of a k/v pair */
+int qsDecode(char* qs);
+
+/* Looks up the value according to the key on a pre-processed query string
+ * A future enhancement will be a compile-time option to look up the key
+ * in a pre-sorted qs_kv array via a binary search. */
+// char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
+char* qsK2v(const char* key, char* const* qs_kv, int qs_kv_size, int nth);
+
+/* Non-destructive lookup of value, based on key. User provides the
+ * destinaton string and length. */
+char* qsScanvalue(const char* key, const char* qs, char* val, size_t val_len);
+
+// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
+#undef _qsSORTING
+
+// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
+#define BMCWEB_QS_ISHEX(x) \
+ ((((x) >= '0' && (x) <= '9') || ((x) >= 'A' && (x) <= 'F') || \
+ ((x) >= 'a' && (x) <= 'f')) \
+ ? 1 \
+ : 0)
+#define BMCWEB_QS_HEX2DEC(x) \
+ (((x) >= '0' && (x) <= '9') \
+ ? (x)-48 \
+ : ((x) >= 'A' && (x) <= 'F') \
+ ? (x)-55 \
+ : ((x) >= 'a' && (x) <= 'f') ? (x)-87 : 0)
+#define BMCWEB_QS_ISQSCHR(x) \
+ ((((x) == '=') || ((x) == '#') || ((x) == '&') || ((x) == '\0')) ? 0 : 1)
+
+inline int qsStrncmp(const char* s, const char* qs, size_t n)
+{
+ int i = 0;
+ char u1, u2;
+ char unyb, lnyb;
+
+ while (n-- > 0)
+ {
+ u1 = *s++;
+ u2 = *qs++;
+
+ if (!BMCWEB_QS_ISQSCHR(u1))
+ {
+ u1 = '\0';
+ }
+ if (!BMCWEB_QS_ISQSCHR(u2))
+ {
+ u2 = '\0';
+ }
+
+ if (u1 == '+')
+ {
+ u1 = ' ';
+ }
+ if (u1 == '%') // easier/safer than scanf
+ {
+ unyb = static_cast<char>(*s++);
+ lnyb = static_cast<char>(*s++);
+ if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb))
+ {
+ u1 = static_cast<char>((BMCWEB_QS_HEX2DEC(unyb) * 16) +
+ BMCWEB_QS_HEX2DEC(lnyb));
+ }
+ else
+ {
+ u1 = '\0';
+ }
+ }
+
+ if (u2 == '+')
+ {
+ u2 = ' ';
+ }
+ if (u2 == '%') // easier/safer than scanf
+ {
+ unyb = static_cast<char>(*qs++);
+ lnyb = static_cast<char>(*qs++);
+ if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb))
+ {
+ u2 = static_cast<char>((BMCWEB_QS_HEX2DEC(unyb) * 16) +
+ BMCWEB_QS_HEX2DEC(lnyb));
+ }
+ else
+ {
+ u2 = '\0';
+ }
+ }
+
+ if (u1 != u2)
+ {
+ return u1 - u2;
+ }
+ if (u1 == '\0')
+ {
+ return 0;
+ }
+ i++;
+ }
+ if (BMCWEB_QS_ISQSCHR(*qs))
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+inline size_t qsParse(char* qs, char* qs_kv[], size_t qs_kv_size)
+{
+ size_t i;
+ size_t j;
+ char* substrPtr;
+
+ for (i = 0; i < qs_kv_size; i++)
+ {
+ qs_kv[i] = NULL;
+ }
+
+ // find the beginning of the k/v substrings or the fragment
+ substrPtr = qs + strcspn(qs, "?#");
+ if (substrPtr[0] != '\0')
+ {
+ substrPtr++;
+ }
+ else
+ {
+ return 0; // no query or fragment
+ }
+
+ i = 0;
+ while (i < qs_kv_size)
+ {
+ qs_kv[i] = substrPtr;
+ j = strcspn(substrPtr, "&");
+ if (substrPtr[j] == '\0')
+ {
+ break;
+ }
+ substrPtr += j + 1;
+ i++;
+ }
+ i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
+
+ // we only decode the values in place, the keys could have '='s in them
+ // which will hose our ability to distinguish keys from values later
+ for (j = 0; j < i; j++)
+ {
+ substrPtr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
+ if (substrPtr[0] == '&' || substrPtr[0] == '\0')
+ { // blank value: skip decoding
+ substrPtr[0] = '\0';
+ }
+ else
+ {
+ qsDecode(++substrPtr);
+ }
+ }
+
+#ifdef _qsSORTING
+// TODO: qsort qs_kv, using qs_strncmp() for the comparison
+#endif
+
+ return i;
+}
+
+inline int qsDecode(char* qs)
+{
+ int i = 0, j = 0;
+
+ while (BMCWEB_QS_ISQSCHR(qs[j]))
+ {
+ if (qs[j] == '+')
+ {
+ qs[i] = ' ';
+ }
+ else if (qs[j] == '%') // easier/safer than scanf
+ {
+ if (!BMCWEB_QS_ISHEX(qs[j + 1]) || !BMCWEB_QS_ISHEX(qs[j + 2]))
+ {
+ qs[i] = '\0';
+ return i;
+ }
+ qs[i] = static_cast<char>(BMCWEB_QS_HEX2DEC(qs[j + 1] * 16) +
+ BMCWEB_QS_HEX2DEC(qs[j + 2]));
+ j += 2;
+ }
+ else
+ {
+ qs[i] = qs[j];
+ }
+ i++;
+ j++;
+ }
+ qs[i] = '\0';
+
+ return i;
+}
+
+inline char* qsK2v(const char* key, char* const* qs_kv, int qs_kv_size,
+ int nth = 0)
+{
+ int i;
+ size_t keyLen, skip;
+
+ keyLen = strlen(key);
+
+#ifdef _qsSORTING
+// TODO: binary search for key in the sorted qs_kv
+#else // _qsSORTING
+ for (i = 0; i < qs_kv_size; i++)
+ {
+ // we rely on the unambiguous '=' to find the value in our k/v pair
+ if (qsStrncmp(key, qs_kv[i], keyLen) == 0)
+ {
+ skip = strcspn(qs_kv[i], "=");
+ if (qs_kv[i][skip] == '=')
+ {
+ skip++;
+ }
+ // return (zero-char value) ? ptr to trailing '\0' : ptr to value
+ if (nth == 0)
+ {
+ return qs_kv[i] + skip;
+ }
+ else
+ {
+ --nth;
+ }
+ }
+ }
+#endif // _qsSORTING
+
+ return NULL;
+}
+
+inline char* qsScanvalue(const char* key, const char* qs, char* val,
+ size_t val_len)
+{
+ size_t i, keyLen;
+ const char* tmp;
+
+ // find the beginning of the k/v substrings
+ if ((tmp = strchr(qs, '?')) != NULL)
+ {
+ qs = tmp + 1;
+ }
+
+ keyLen = strlen(key);
+ while (qs[0] != '#' && qs[0] != '\0')
+ {
+ if (qsStrncmp(key, qs, keyLen) == 0)
+ {
+ break;
+ }
+ qs += strcspn(qs, "&") + 1;
+ }
+
+ if (qs[0] == '\0')
+ {
+ return NULL;
+ }
+
+ qs += strcspn(qs, "=&#");
+ if (qs[0] == '=')
+ {
+ qs++;
+ i = strcspn(qs, "&=#");
+ strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1));
+ qsDecode(val);
+ }
+ else
+ {
+ if (val_len > 0)
+ {
+ val[0] = '\0';
+ }
+ }
+
+ return val;
+}
+} // namespace crow
+// ----------------------------------------------------------------------------
+
+namespace crow
+{
+class QueryString
+{
+ public:
+ static const size_t maxKeyValuePairsCount = 256;
+
+ QueryString() = default;
+
+ QueryString(const QueryString& qs) : url(qs.url)
+ {
+ for (auto p : qs.keyValuePairs)
+ {
+ keyValuePairs.push_back(
+ const_cast<char*>(p - qs.url.c_str() + url.c_str()));
+ }
+ }
+
+ QueryString& operator=(const QueryString& qs)
+ {
+ url = qs.url;
+ keyValuePairs.clear();
+ for (auto p : qs.keyValuePairs)
+ {
+ keyValuePairs.push_back(
+ const_cast<char*>(p - qs.url.c_str() + url.c_str()));
+ }
+ return *this;
+ }
+
+ QueryString& operator=(QueryString&& qs)
+ {
+ keyValuePairs = std::move(qs.keyValuePairs);
+ auto* oldData = const_cast<char*>(qs.url.c_str());
+ url = std::move(qs.url);
+ for (auto& p : keyValuePairs)
+ {
+ p += const_cast<char*>(url.c_str()) - oldData;
+ }
+ return *this;
+ }
+
+ explicit QueryString(std::string newUrl) : url(std::move(newUrl))
+ {
+ if (url.empty())
+ {
+ return;
+ }
+
+ keyValuePairs.resize(maxKeyValuePairsCount);
+
+ size_t count =
+ qsParse(&url[0], &keyValuePairs[0], maxKeyValuePairsCount);
+ keyValuePairs.resize(count);
+ }
+
+ void clear()
+ {
+ keyValuePairs.clear();
+ url.clear();
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const QueryString& qs)
+ {
+ os << "[ ";
+ for (size_t i = 0; i < qs.keyValuePairs.size(); ++i)
+ {
+ if (i != 0u)
+ {
+ os << ", ";
+ }
+ os << qs.keyValuePairs[i];
+ }
+ os << " ]";
+ return os;
+ }
+
+ char* get(const std::string& name) const
+ {
+ char* ret = qsK2v(name.c_str(), keyValuePairs.data(),
+ static_cast<int>(keyValuePairs.size()));
+ return ret;
+ }
+
+ std::vector<char*> getList(const std::string& name) const
+ {
+ std::vector<char*> ret;
+ std::string plus = name + "[]";
+ char* element = nullptr;
+
+ int count = 0;
+ while (1)
+ {
+ element = qsK2v(plus.c_str(), keyValuePairs.data(),
+ static_cast<int>(keyValuePairs.size()), count++);
+ if (element == nullptr)
+ {
+ break;
+ }
+ ret.push_back(element);
+ }
+ return ret;
+ }
+
+ private:
+ std::string url;
+ std::vector<char*> keyValuePairs;
+};
+
+} // namespace crow
diff --git a/http/routing.h b/http/routing.h
new file mode 100644
index 0000000..b2355e9
--- /dev/null
+++ b/http/routing.h
@@ -0,0 +1,1334 @@
+#pragma once
+
+#include "privileges.hpp"
+#include "sessions.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <boost/container/small_vector.hpp>
+#include <boost/lexical_cast.hpp>
+#include <cerrno>
+#include <cstdint>
+#include <cstdlib>
+#include <limits>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "common.h"
+#include "http_request.h"
+#include "http_response.h"
+#include "logging.h"
+#include "utility.h"
+#include "websocket.h"
+
+namespace crow
+{
+
+constexpr int maxHttpVerbCount =
+ static_cast<int>(boost::beast::http::verb::unlink);
+
+class BaseRule
+{
+ public:
+ BaseRule(std::string thisRule) : rule(std::move(thisRule))
+ {
+ }
+
+ virtual ~BaseRule()
+ {
+ }
+
+ virtual void validate() = 0;
+ std::unique_ptr<BaseRule> upgrade()
+ {
+ if (ruleToUpgrade)
+ return std::move(ruleToUpgrade);
+ return {};
+ }
+
+ virtual void handle(const Request&, Response&, const RoutingParams&) = 0;
+ virtual void handleUpgrade(const Request&, Response& res,
+ boost::asio::ip::tcp::socket&&)
+ {
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ }
+#ifdef BMCWEB_ENABLE_SSL
+ virtual void
+ handleUpgrade(const Request&, Response& res,
+ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&)
+ {
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ }
+#endif
+
+ size_t getMethods()
+ {
+ return methodsBitfield;
+ }
+
+ bool checkPrivileges(const redfish::Privileges& userPrivileges)
+ {
+ // If there are no privileges assigned, assume no privileges
+ // required
+ if (privilegesSet.empty())
+ {
+ return true;
+ }
+
+ for (const redfish::Privileges& requiredPrivileges : privilegesSet)
+ {
+ if (userPrivileges.isSupersetOf(requiredPrivileges))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ size_t methodsBitfield{
+ 1 << static_cast<size_t>(boost::beast::http::verb::get)};
+
+ std::vector<redfish::Privileges> privilegesSet;
+
+ std::string rule;
+ std::string nameStr;
+
+ std::unique_ptr<BaseRule> ruleToUpgrade;
+
+ friend class Router;
+ template <typename T> friend struct RuleParameterTraits;
+};
+
+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 Request& req;
+ Response& res;
+};
+
+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<int64_t, Args1...>,
+ black_magic::S<Args2...>>
+{
+ void operator()(F cparams)
+ {
+ using pushed = typename black_magic::S<Args2...>::template push_back<
+ CallPair<int64_t, NInt>>;
+ Call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>,
+ pushed>()(cparams);
+ }
+};
+
+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<uint64_t, Args1...>, black_magic::S<Args2...>>
+{
+ void operator()(F cparams)
+ {
+ using pushed = typename black_magic::S<Args2...>::template push_back<
+ CallPair<uint64_t, NUint>>;
+ Call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>,
+ pushed>()(cparams);
+ }
+};
+
+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<double, Args1...>,
+ black_magic::S<Args2...>>
+{
+ void operator()(F cparams)
+ {
+ using pushed = typename black_magic::S<Args2...>::template push_back<
+ CallPair<double, NDouble>>;
+ Call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>,
+ pushed>()(cparams);
+ }
+};
+
+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.res,
+ cparams.params.template get<typename Args1::type>(Args1::pos)...);
+ }
+};
+
+template <typename Func, typename... ArgsWrapped> struct Wrapped
+{
+ template <typename... Args>
+ void set(
+ Func f,
+ typename std::enable_if<
+ !std::is_same<
+ typename std::tuple_element<0, std::tuple<Args..., void>>::type,
+ const Request&>::value,
+ int>::type = 0)
+ {
+ handler = [f = std::move(f)](const Request&, Response& res,
+ Args... args) {
+ res.result(f(args...));
+ res.end();
+ };
+ }
+
+ template <typename Req, typename... Args> struct ReqHandlerWrapper
+ {
+ ReqHandlerWrapper(Func f) : f(std::move(f))
+ {
+ }
+
+ void operator()(const Request& req, Response& res, Args... args)
+ {
+ res.result(f(req, args...));
+ res.end();
+ }
+
+ Func f;
+ };
+
+ template <typename... Args>
+ void set(
+ Func f,
+ typename std::enable_if<
+ std::is_same<
+ typename std::tuple_element<0, std::tuple<Args..., void>>::type,
+ const Request&>::value &&
+ !std::is_same<typename std::tuple_element<
+ 1, std::tuple<Args..., void, void>>::type,
+ Response&>::value,
+ int>::type = 0)
+ {
+ handler = ReqHandlerWrapper<Args...>(std::move(f));
+ /*handler = (
+ [f = std::move(f)]
+ (const Request& req, Response& res, Args... args){
+ res.result(f(req, args...));
+ res.end();
+ });*/
+ }
+
+ template <typename... Args>
+ void set(
+ Func f,
+ typename std::enable_if<
+ std::is_same<
+ typename std::tuple_element<0, std::tuple<Args..., void>>::type,
+ const Request&>::value &&
+ std::is_same<typename std::tuple_element<
+ 1, std::tuple<Args..., void, void>>::type,
+ Response&>::value,
+ int>::type = 0)
+ {
+ handler = std::move(f);
+ }
+
+ template <typename... Args> struct HandlerTypeHelper
+ {
+ using type =
+ std::function<void(const crow::Request&, crow::Response&, Args...)>;
+ using args_type =
+ black_magic::S<typename black_magic::promote_t<Args>...>;
+ };
+
+ template <typename... Args>
+ struct HandlerTypeHelper<const Request&, Args...>
+ {
+ using type =
+ std::function<void(const crow::Request&, crow::Response&, Args...)>;
+ using args_type =
+ black_magic::S<typename black_magic::promote_t<Args>...>;
+ };
+
+ template <typename... Args>
+ struct HandlerTypeHelper<const Request&, Response&, Args...>
+ {
+ using type =
+ std::function<void(const crow::Request&, crow::Response&, Args...)>;
+ using args_type =
+ black_magic::S<typename black_magic::promote_t<Args>...>;
+ };
+
+ typename HandlerTypeHelper<ArgsWrapped...>::type handler;
+
+ void operator()(const Request& req, Response& res,
+ const RoutingParams& params)
+ {
+ detail::routing_handler_call_helper::Call<
+ detail::routing_handler_call_helper::CallParams<decltype(handler)>,
+ 0, 0, 0, 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type,
+ black_magic::S<>>()(
+ detail::routing_handler_call_helper::CallParams<decltype(handler)>{
+ handler, params, req, res});
+ }
+};
+} // namespace routing_handler_call_helper
+} // namespace detail
+
+class WebSocketRule : public BaseRule
+{
+ using self_t = WebSocketRule;
+
+ public:
+ WebSocketRule(std::string rule) : BaseRule(std::move(rule))
+ {
+ }
+
+ void validate() override
+ {
+ }
+
+ void handle(const Request&, Response& res, const RoutingParams&) override
+ {
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ }
+
+ void handleUpgrade(const Request& req, Response&,
+ boost::asio::ip::tcp::socket&& adaptor) override
+ {
+ new crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>(
+ req, std::move(adaptor), openHandler, messageHandler, closeHandler,
+ errorHandler);
+ }
+#ifdef BMCWEB_ENABLE_SSL
+ void handleUpgrade(const Request& req, Response&,
+ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&
+ adaptor) override
+ {
+ std::shared_ptr<crow::websocket::ConnectionImpl<
+ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>
+ myConnection = std::make_shared<crow::websocket::ConnectionImpl<
+ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>(
+ req, std::move(adaptor), openHandler, messageHandler,
+ closeHandler, errorHandler);
+ myConnection->start();
+ }
+#endif
+
+ template <typename Func> self_t& onopen(Func f)
+ {
+ openHandler = f;
+ return *this;
+ }
+
+ template <typename Func> self_t& onmessage(Func f)
+ {
+ messageHandler = f;
+ return *this;
+ }
+
+ template <typename Func> self_t& onclose(Func f)
+ {
+ closeHandler = f;
+ return *this;
+ }
+
+ template <typename Func> self_t& onerror(Func f)
+ {
+ errorHandler = f;
+ return *this;
+ }
+
+ protected:
+ std::function<void(crow::websocket::Connection&)> openHandler;
+ std::function<void(crow::websocket::Connection&, const std::string&, bool)>
+ messageHandler;
+ std::function<void(crow::websocket::Connection&, const std::string&)>
+ closeHandler;
+ std::function<void(crow::websocket::Connection&)> errorHandler;
+};
+
+template <typename T> struct RuleParameterTraits
+{
+ using self_t = T;
+ WebSocketRule& websocket()
+ {
+ self_t* self = static_cast<self_t*>(this);
+ WebSocketRule* p = new WebSocketRule(self->rule);
+ self->ruleToUpgrade.reset(p);
+ return *p;
+ }
+
+ self_t& name(std::string name) noexcept
+ {
+ self_t* self = static_cast<self_t*>(this);
+ self->nameStr = std::move(name);
+ return *self;
+ }
+
+ self_t& methods(boost::beast::http::verb method)
+ {
+ self_t* self = static_cast<self_t*>(this);
+ self->methodsBitfield = 1U << static_cast<size_t>(method);
+ return *self;
+ }
+
+ template <typename... MethodArgs>
+ self_t& methods(boost::beast::http::verb method, MethodArgs... args_method)
+ {
+ self_t* self = static_cast<self_t*>(this);
+ methods(args_method...);
+ self->methodsBitfield |= 1U << static_cast<size_t>(method);
+ return *self;
+ }
+
+ template <typename... MethodArgs>
+ self_t& requires(std::initializer_list<const char*> l)
+ {
+ self_t* self = static_cast<self_t*>(this);
+ self->privilegesSet.emplace_back(l);
+ return *self;
+ }
+
+ template <typename... MethodArgs>
+ self_t& requires(const std::vector<redfish::Privileges>& p)
+ {
+ self_t* self = static_cast<self_t*>(this);
+ for (const redfish::Privileges& privilege : p)
+ {
+ self->privilegesSet.emplace_back(privilege);
+ }
+ return *self;
+ }
+};
+
+class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule>
+{
+ public:
+ DynamicRule(std::string rule) : BaseRule(std::move(rule))
+ {
+ }
+
+ void validate() override
+ {
+ if (!erasedHandler)
+ {
+ throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") +
+ "no handler for url " + rule);
+ }
+ }
+
+ void handle(const Request& req, Response& res,
+ const RoutingParams& params) override
+ {
+ erasedHandler(req, res, params);
+ }
+
+ template <typename Func> void operator()(Func f)
+ {
+ using function_t = utility::function_traits<Func>;
+
+ erasedHandler =
+ wrap(std::move(f), black_magic::gen_seq<function_t::arity>());
+ }
+
+ // enable_if Arg1 == request && Arg2 == Response
+ // enable_if Arg1 == request && Arg2 != resposne
+ // enable_if Arg1 != request
+
+ template <typename Func, unsigned... Indices>
+
+ std::function<void(const Request&, Response&, const RoutingParams&)>
+ wrap(Func f, black_magic::Seq<Indices...>)
+ {
+ using function_t = utility::function_traits<Func>;
+
+ if (!black_magic::isParameterTagCompatible(
+ black_magic::getParameterTagRuntime(rule.c_str()),
+ black_magic::compute_parameter_tag_from_args_list<
+ typename function_t::template arg<Indices>...>::value))
+ {
+ throw std::runtime_error("routeDynamic: Handler type is mismatched "
+ "with URL parameters: " +
+ rule);
+ }
+ auto ret = detail::routing_handler_call_helper::Wrapped<
+ Func, typename function_t::template arg<Indices>...>();
+ ret.template set<typename function_t::template arg<Indices>...>(
+ std::move(f));
+ return ret;
+ }
+
+ template <typename Func> void operator()(std::string name, Func&& f)
+ {
+ nameStr = std::move(name);
+ (*this).template operator()<Func>(std::forward(f));
+ }
+
+ private:
+ std::function<void(const Request&, Response&, const RoutingParams&)>
+ erasedHandler;
+};
+
+template <typename... Args>
+class TaggedRule : public BaseRule,
+ public RuleParameterTraits<TaggedRule<Args...>>
+{
+ public:
+ using self_t = TaggedRule<Args...>;
+
+ TaggedRule(std::string ruleIn) : BaseRule(std::move(ruleIn))
+ {
+ }
+
+ void validate() override
+ {
+ if (!handler)
+ {
+ throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") +
+ "no handler for url " + rule);
+ }
+ }
+
+ template <typename Func>
+ typename std::enable_if<
+ black_magic::CallHelper<Func, black_magic::S<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<crow::Request, Args...>>::value,
+ "Handler type is mismatched with URL parameters");
+ static_assert(
+ !std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
+ "Handler function cannot have void return type; valid return "
+ "types: "
+ "string, int, crow::resposne, nlohmann::json");
+
+ handler = [f = std::move(f)](const Request&, Response& res,
+ Args... args) {
+ res.result(f(args...));
+ res.end();
+ };
+ }
+
+ template <typename Func>
+ typename std::enable_if<
+ !black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
+ black_magic::CallHelper<
+ Func, black_magic::S<crow::Request, 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<crow::Request, Args...>>::value,
+ "Handler type is mismatched with URL parameters");
+ static_assert(
+ !std::is_same<void, decltype(f(std::declval<crow::Request>(),
+ std::declval<Args>()...))>::value,
+ "Handler function cannot have void return type; valid return "
+ "types: "
+ "string, int, crow::resposne,nlohmann::json");
+
+ handler = [f = std::move(f)](const crow::Request& req,
+ crow::Response& res, Args... args) {
+ res.result(f(req, args...));
+ res.end();
+ };
+ }
+
+ template <typename Func>
+ typename std::enable_if<
+ !black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
+ !black_magic::CallHelper<
+ Func, black_magic::S<crow::Request, 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<crow::Request, Args...>>::value ||
+ black_magic::CallHelper<
+ Func, black_magic::S<crow::Request, crow::Response&,
+ Args...>>::value,
+ "Handler type is mismatched with URL parameters");
+ static_assert(
+ std::is_same<void, decltype(f(std::declval<crow::Request>(),
+ std::declval<crow::Response&>(),
+ std::declval<Args>()...))>::value,
+ "Handler function with response argument should have void "
+ "return "
+ "type");
+
+ handler = std::move(f);
+ }
+
+ template <typename Func> void operator()(std::string name, Func&& f)
+ {
+ nameStr = std::move(name);
+ (*this).template operator()<Func>(std::forward(f));
+ }
+
+ void handle(const Request& req, Response& res,
+ 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, res});
+ }
+
+ private:
+ std::function<void(const crow::Request&, crow::Response&, Args...)> handler;
+};
+
+const int ruleSpecialRedirectSlash = 1;
+
+class Trie
+{
+ public:
+ struct Node
+ {
+ unsigned ruleIndex{};
+ std::array<size_t, static_cast<size_t>(ParamType::MAX)>
+ paramChildrens{};
+ boost::container::flat_map<std::string, unsigned> children;
+
+ bool isSimpleNode() const
+ {
+ return !ruleIndex && std::all_of(std::begin(paramChildrens),
+ std::end(paramChildrens),
+ [](size_t x) { return !x; });
+ }
+ };
+
+ Trie() : nodes(1)
+ {
+ }
+
+ private:
+ void optimizeNode(Node* node)
+ {
+ for (size_t x : node->paramChildrens)
+ {
+ if (!x)
+ continue;
+ Node* child = &nodes[x];
+ optimizeNode(child);
+ }
+ if (node->children.empty())
+ return;
+ bool mergeWithChild = true;
+ for (const std::pair<std::string, unsigned>& kv : node->children)
+ {
+ Node* child = &nodes[kv.second];
+ if (!child->isSimpleNode())
+ {
+ mergeWithChild = false;
+ break;
+ }
+ }
+ if (mergeWithChild)
+ {
+ decltype(node->children) merged;
+ for (const std::pair<std::string, unsigned>& kv : node->children)
+ {
+ Node* child = &nodes[kv.second];
+ for (const std::pair<std::string, unsigned>& childKv :
+ child->children)
+ {
+ merged[kv.first + childKv.first] = childKv.second;
+ }
+ }
+ node->children = std::move(merged);
+ optimizeNode(node);
+ }
+ else
+ {
+ for (const std::pair<std::string, unsigned>& kv : node->children)
+ {
+ Node* child = &nodes[kv.second];
+ optimizeNode(child);
+ }
+ }
+ }
+
+ void optimize()
+ {
+ optimizeNode(head());
+ }
+
+ public:
+ void validate()
+ {
+ if (!head()->isSimpleNode())
+ throw std::runtime_error(
+ "Internal error: Trie header should be simple!");
+ optimize();
+ }
+
+ void findRouteIndexes(const std::string& req_url,
+ std::vector<unsigned>& route_indexes,
+ const Node* node = nullptr, unsigned pos = 0) const
+ {
+ if (node == nullptr)
+ {
+ node = head();
+ }
+ for (const std::pair<std::string, unsigned>& kv : node->children)
+ {
+ const std::string& fragment = kv.first;
+ const Node* child = &nodes[kv.second];
+ if (pos >= req_url.size())
+ {
+ if (child->ruleIndex != 0 && fragment != "/")
+ {
+ route_indexes.push_back(child->ruleIndex);
+ }
+ findRouteIndexes(req_url, route_indexes, child,
+ static_cast<unsigned>(pos + fragment.size()));
+ }
+ else
+ {
+ if (req_url.compare(pos, fragment.size(), fragment) == 0)
+ {
+ findRouteIndexes(
+ req_url, route_indexes, child,
+ static_cast<unsigned>(pos + fragment.size()));
+ }
+ }
+ }
+ }
+
+ std::pair<unsigned, RoutingParams>
+ find(const std::string_view req_url, const Node* node = nullptr,
+ size_t pos = 0, RoutingParams* params = nullptr) const
+ {
+ RoutingParams empty;
+ if (params == nullptr)
+ params = ∅
+
+ unsigned found{};
+ RoutingParams matchParams;
+
+ if (node == nullptr)
+ node = head();
+ if (pos == req_url.size())
+ return {node->ruleIndex, *params};
+
+ auto updateFound =
+ [&found, &matchParams](std::pair<unsigned, RoutingParams>& ret) {
+ if (ret.first && (!found || found > ret.first))
+ {
+ found = ret.first;
+ matchParams = std::move(ret.second);
+ }
+ };
+
+ if (node->paramChildrens[static_cast<size_t>(ParamType::INT)])
+ {
+ char c = req_url[pos];
+ if ((c >= '0' && c <= '9') || c == '+' || c == '-')
+ {
+ char* eptr;
+ errno = 0;
+ long long int value =
+ std::strtoll(req_url.data() + pos, &eptr, 10);
+ if (errno != ERANGE && eptr != req_url.data() + pos)
+ {
+ params->intParams.push_back(value);
+ std::pair<unsigned, RoutingParams> ret = find(
+ req_url,
+ &nodes[node->paramChildrens[static_cast<size_t>(
+ ParamType::INT)]],
+ static_cast<size_t>(eptr - req_url.data()), params);
+ updateFound(ret);
+ params->intParams.pop_back();
+ }
+ }
+ }
+
+ if (node->paramChildrens[static_cast<size_t>(ParamType::UINT)])
+ {
+ char c = req_url[pos];
+ if ((c >= '0' && c <= '9') || c == '+')
+ {
+ char* eptr;
+ errno = 0;
+ unsigned long long int value =
+ std::strtoull(req_url.data() + pos, &eptr, 10);
+ if (errno != ERANGE && eptr != req_url.data() + pos)
+ {
+ params->uintParams.push_back(value);
+ std::pair<unsigned, RoutingParams> ret = find(
+ req_url,
+ &nodes[node->paramChildrens[static_cast<size_t>(
+ ParamType::UINT)]],
+ static_cast<size_t>(eptr - req_url.data()), params);
+ updateFound(ret);
+ params->uintParams.pop_back();
+ }
+ }
+ }
+
+ if (node->paramChildrens[static_cast<size_t>(ParamType::DOUBLE)])
+ {
+ char c = req_url[pos];
+ if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')
+ {
+ char* eptr;
+ errno = 0;
+ double value = std::strtod(req_url.data() + pos, &eptr);
+ if (errno != ERANGE && eptr != req_url.data() + pos)
+ {
+ params->doubleParams.push_back(value);
+ std::pair<unsigned, RoutingParams> ret = find(
+ req_url,
+ &nodes[node->paramChildrens[static_cast<size_t>(
+ ParamType::DOUBLE)]],
+ static_cast<size_t>(eptr - req_url.data()), params);
+ updateFound(ret);
+ params->doubleParams.pop_back();
+ }
+ }
+ }
+
+ if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)])
+ {
+ size_t epos = pos;
+ for (; epos < req_url.size(); epos++)
+ {
+ if (req_url[epos] == '/')
+ break;
+ }
+
+ if (epos != pos)
+ {
+ params->stringParams.emplace_back(
+ req_url.substr(pos, epos - pos));
+ std::pair<unsigned, RoutingParams> ret =
+ find(req_url,
+ &nodes[node->paramChildrens[static_cast<size_t>(
+ ParamType::STRING)]],
+ epos, params);
+ updateFound(ret);
+ params->stringParams.pop_back();
+ }
+ }
+
+ if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)])
+ {
+ size_t epos = req_url.size();
+
+ if (epos != pos)
+ {
+ params->stringParams.emplace_back(
+ req_url.substr(pos, epos - pos));
+ std::pair<unsigned, RoutingParams> ret =
+ find(req_url,
+ &nodes[node->paramChildrens[static_cast<size_t>(
+ ParamType::PATH)]],
+ epos, params);
+ updateFound(ret);
+ params->stringParams.pop_back();
+ }
+ }
+
+ for (const std::pair<std::string, unsigned>& kv : node->children)
+ {
+ const std::string& fragment = kv.first;
+ const Node* child = &nodes[kv.second];
+
+ if (req_url.compare(pos, fragment.size(), fragment) == 0)
+ {
+ std::pair<unsigned, RoutingParams> ret =
+ find(req_url, child, pos + fragment.size(), params);
+ updateFound(ret);
+ }
+ }
+
+ return {found, matchParams};
+ }
+
+ void add(const std::string& url, unsigned ruleIndex)
+ {
+ size_t idx = 0;
+
+ for (unsigned i = 0; i < url.size(); i++)
+ {
+ char c = url[i];
+ if (c == '<')
+ {
+ const static std::array<std::pair<ParamType, std::string>, 7>
+ paramTraits = {{
+ {ParamType::INT, "<int>"},
+ {ParamType::UINT, "<uint>"},
+ {ParamType::DOUBLE, "<float>"},
+ {ParamType::DOUBLE, "<double>"},
+ {ParamType::STRING, "<str>"},
+ {ParamType::STRING, "<string>"},
+ {ParamType::PATH, "<path>"},
+ }};
+
+ for (const std::pair<ParamType, std::string>& x : paramTraits)
+ {
+ if (url.compare(i, x.second.size(), x.second) == 0)
+ {
+ size_t index = static_cast<size_t>(x.first);
+ if (!nodes[idx].paramChildrens[index])
+ {
+ unsigned newNodeIdx = newNode();
+ nodes[idx].paramChildrens[index] = newNodeIdx;
+ }
+ idx = nodes[idx].paramChildrens[index];
+ i += static_cast<unsigned>(x.second.size());
+ break;
+ }
+ }
+
+ i--;
+ }
+ else
+ {
+ std::string piece(&c, 1);
+ if (!nodes[idx].children.count(piece))
+ {
+ unsigned newNodeIdx = newNode();
+ nodes[idx].children.emplace(piece, newNodeIdx);
+ }
+ idx = nodes[idx].children[piece];
+ }
+ }
+ if (nodes[idx].ruleIndex)
+ throw std::runtime_error("handler already exists for " + url);
+ nodes[idx].ruleIndex = ruleIndex;
+ }
+
+ private:
+ void debugNodePrint(Node* n, size_t level)
+ {
+ for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++)
+ {
+ if (n->paramChildrens[i])
+ {
+ BMCWEB_LOG_DEBUG << std::string(
+ 2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/;
+ switch (static_cast<ParamType>(i))
+ {
+ case ParamType::INT:
+ BMCWEB_LOG_DEBUG << "<int>";
+ break;
+ case ParamType::UINT:
+ BMCWEB_LOG_DEBUG << "<uint>";
+ break;
+ case ParamType::DOUBLE:
+ BMCWEB_LOG_DEBUG << "<float>";
+ break;
+ case ParamType::STRING:
+ BMCWEB_LOG_DEBUG << "<str>";
+ break;
+ case ParamType::PATH:
+ BMCWEB_LOG_DEBUG << "<path>";
+ break;
+ default:
+ BMCWEB_LOG_DEBUG << "<ERROR>";
+ break;
+ }
+
+ debugNodePrint(&nodes[n->paramChildrens[i]], level + 1);
+ }
+ }
+ for (const std::pair<std::string, unsigned>& kv : n->children)
+ {
+ BMCWEB_LOG_DEBUG
+ << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/
+ << kv.first;
+ debugNodePrint(&nodes[kv.second], level + 1);
+ }
+ }
+
+ public:
+ void debugPrint()
+ {
+ debugNodePrint(head(), 0U);
+ }
+
+ private:
+ const Node* head() const
+ {
+ return &nodes.front();
+ }
+
+ Node* head()
+ {
+ return &nodes.front();
+ }
+
+ unsigned newNode()
+ {
+ nodes.resize(nodes.size() + 1);
+ return static_cast<unsigned>(nodes.size() - 1);
+ }
+
+ std::vector<Node> nodes;
+};
+
+class Router
+{
+ public:
+ Router()
+ {
+ }
+
+ DynamicRule& newRuleDynamic(const std::string& rule)
+ {
+ std::unique_ptr<DynamicRule> ruleObject =
+ std::make_unique<DynamicRule>(rule);
+ DynamicRule* ptr = ruleObject.get();
+ allRules.emplace_back(std::move(ruleObject));
+
+ return *ptr;
+ }
+
+ template <uint64_t N>
+ typename black_magic::Arguments<N>::type::template rebind<TaggedRule>&
+ newRuleTagged(const std::string& rule)
+ {
+ using RuleT = typename black_magic::Arguments<N>::type::template rebind<
+ TaggedRule>;
+ std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
+ RuleT* ptr = ruleObject.get();
+ allRules.emplace_back(std::move(ruleObject));
+
+ return *ptr;
+ }
+
+ void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject)
+ {
+ if (ruleObject == nullptr)
+ {
+ return;
+ }
+ for (uint32_t method = 0, method_bit = 1; method < maxHttpVerbCount;
+ method++, method_bit <<= 1)
+ {
+ if (ruleObject->methodsBitfield & method_bit)
+ {
+ perMethods[method].rules.emplace_back(ruleObject);
+ perMethods[method].trie.add(
+ rule, static_cast<unsigned>(
+ perMethods[method].rules.size() - 1U));
+ // directory case:
+ // request to `/about' url matches `/about/' rule
+ if (rule.size() > 2 && rule.back() == '/')
+ {
+ perMethods[method].trie.add(
+ rule.substr(0, rule.size() - 1),
+ static_cast<unsigned>(perMethods[method].rules.size() -
+ 1));
+ }
+ }
+ }
+ }
+
+ void validate()
+ {
+ for (std::unique_ptr<BaseRule>& rule : allRules)
+ {
+ if (rule)
+ {
+ std::unique_ptr<BaseRule> upgraded = rule->upgrade();
+ if (upgraded)
+ rule = std::move(upgraded);
+ rule->validate();
+ internalAddRuleObject(rule->rule, rule.get());
+ }
+ }
+ for (PerMethod& perMethod : perMethods)
+ {
+ perMethod.trie.validate();
+ }
+ }
+
+ template <typename Adaptor>
+ void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor)
+ {
+ if (static_cast<size_t>(req.method()) >= perMethods.size())
+ return;
+
+ PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())];
+ Trie& trie = perMethod.trie;
+ std::vector<BaseRule*>& rules = perMethod.rules;
+
+ const std::pair<unsigned, RoutingParams>& found = trie.find(req.url);
+ unsigned ruleIndex = found.first;
+ if (!ruleIndex)
+ {
+ BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url;
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ return;
+ }
+
+ if (ruleIndex >= rules.size())
+ throw std::runtime_error("Trie internal structure corrupted!");
+
+ if (ruleIndex == ruleSpecialRedirectSlash)
+ {
+ BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: "
+ << req.url;
+ res.result(boost::beast::http::status::moved_permanently);
+
+ // TODO absolute url building
+ if (req.getHeaderValue("Host").empty())
+ {
+ res.addHeader("Location", std::string(req.url) + "/");
+ }
+ else
+ {
+ res.addHeader(
+ "Location",
+ req.isSecure
+ ? "https://"
+ : "http://" + std::string(req.getHeaderValue("Host")) +
+ std::string(req.url) + "/");
+ }
+ res.end();
+ return;
+ }
+
+ if ((rules[ruleIndex]->getMethods() &
+ (1U << static_cast<size_t>(req.method()))) == 0)
+ {
+ BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url
+ << " with " << req.methodString() << "("
+ << static_cast<uint32_t>(req.method()) << ") / "
+ << rules[ruleIndex]->getMethods();
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ return;
+ }
+
+ BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rules[ruleIndex]->rule
+ << "' " << static_cast<uint32_t>(req.method()) << " / "
+ << rules[ruleIndex]->getMethods();
+
+ // any uncaught exceptions become 500s
+ try
+ {
+ rules[ruleIndex]->handleUpgrade(req, res, std::move(adaptor));
+ }
+ catch (std::exception& e)
+ {
+ BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what();
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+ catch (...)
+ {
+ BMCWEB_LOG_ERROR
+ << "An uncaught exception occurred. The type was unknown "
+ "so no information was available.";
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+ }
+
+ void handle(const Request& req, Response& res)
+ {
+ if (static_cast<size_t>(req.method()) >= perMethods.size())
+ return;
+ PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())];
+ Trie& trie = perMethod.trie;
+ std::vector<BaseRule*>& rules = perMethod.rules;
+
+ const std::pair<unsigned, RoutingParams>& found = trie.find(req.url);
+
+ unsigned ruleIndex = found.first;
+
+ if (!ruleIndex)
+ {
+ // Check to see if this url exists at any verb
+ for (const PerMethod& p : perMethods)
+ {
+ const std::pair<unsigned, RoutingParams>& found =
+ p.trie.find(req.url);
+ if (found.first > 0)
+ {
+ res.result(boost::beast::http::status::method_not_allowed);
+ res.end();
+ return;
+ }
+ }
+ BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url;
+ res.result(boost::beast::http::status::not_found);
+ res.end();
+ return;
+ }
+
+ if (ruleIndex >= rules.size())
+ throw std::runtime_error("Trie internal structure corrupted!");
+
+ if (ruleIndex == ruleSpecialRedirectSlash)
+ {
+ BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: "
+ << req.url;
+ res.result(boost::beast::http::status::moved_permanently);
+
+ // TODO absolute url building
+ if (req.getHeaderValue("Host").empty())
+ {
+ res.addHeader("Location", std::string(req.url) + "/");
+ }
+ else
+ {
+ res.addHeader("Location",
+ (req.isSecure ? "https://" : "http://") +
+ std::string(req.getHeaderValue("Host")) +
+ std::string(req.url) + "/");
+ }
+ res.end();
+ return;
+ }
+
+ if ((rules[ruleIndex]->getMethods() &
+ (1U << static_cast<uint32_t>(req.method()))) == 0)
+ {
+ BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url
+ << " with " << req.methodString() << "("
+ << static_cast<uint32_t>(req.method()) << ") / "
+ << rules[ruleIndex]->getMethods();
+ res.result(boost::beast::http::status::method_not_allowed);
+ res.end();
+ return;
+ }
+
+ BMCWEB_LOG_DEBUG << "Matched rule '" << rules[ruleIndex]->rule << "' "
+ << static_cast<uint32_t>(req.method()) << " / "
+ << rules[ruleIndex]->getMethods();
+
+ redfish::Privileges userPrivileges;
+ if (req.session != nullptr)
+ {
+ // Get the user role from the session.
+ const std::string& userRole =
+ persistent_data::UserRoleMap::getInstance().getUserRole(
+ req.session->username);
+
+ BMCWEB_LOG_DEBUG << "USER ROLE=" << userRole;
+
+ // Get the user privileges from the role
+ userPrivileges = redfish::getUserPrivileges(userRole);
+ }
+
+ if (!rules[ruleIndex]->checkPrivileges(userPrivileges))
+ {
+ res.result(boost::beast::http::status::forbidden);
+ res.end();
+ return;
+ }
+
+ // any uncaught exceptions become 500s
+ try
+ {
+ rules[ruleIndex]->handle(req, res, found.second);
+ }
+ catch (std::exception& e)
+ {
+ BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what();
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+ catch (...)
+ {
+ BMCWEB_LOG_ERROR
+ << "An uncaught exception occurred. The type was unknown "
+ "so no information was available.";
+ res.result(boost::beast::http::status::internal_server_error);
+ res.end();
+ return;
+ }
+ }
+
+ void debugPrint()
+ {
+ for (size_t i = 0; i < perMethods.size(); i++)
+ {
+ BMCWEB_LOG_DEBUG
+ << methodName(static_cast<boost::beast::http::verb>(i));
+ perMethods[i].trie.debugPrint();
+ }
+ }
+
+ std::vector<const std::string*> getRoutes(const std::string& parent)
+ {
+ std::vector<const std::string*> ret;
+
+ for (const PerMethod& pm : perMethods)
+ {
+ std::vector<unsigned> x;
+ pm.trie.findRouteIndexes(parent, x);
+ for (unsigned index : x)
+ {
+ ret.push_back(&pm.rules[index]->rule);
+ }
+ }
+ return ret;
+ }
+
+ private:
+ struct PerMethod
+ {
+ std::vector<BaseRule*> rules;
+ Trie trie;
+ // rule index 0, 1 has special meaning; preallocate it to avoid
+ // duplication.
+ PerMethod() : rules(2)
+ {
+ }
+ };
+ std::array<PerMethod, maxHttpVerbCount> perMethods;
+ std::vector<std::unique_ptr<BaseRule>> allRules;
+};
+} // namespace crow
diff --git a/http/timer_queue.h b/http/timer_queue.h
new file mode 100644
index 0000000..26eea13
--- /dev/null
+++ b/http/timer_queue.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <boost/circular_buffer.hpp>
+#include <boost/circular_buffer/space_optimized.hpp>
+#include <chrono>
+#include <functional>
+
+#include "logging.h"
+
+namespace crow
+{
+namespace detail
+{
+// fast timer queue for fixed tick value.
+class TimerQueue
+{
+ public:
+ TimerQueue()
+ {
+ dq.set_capacity(100);
+ }
+
+ void cancel(size_t k)
+ {
+ size_t index = k - step;
+ if (index < dq.size())
+ {
+ dq[index].second = nullptr;
+ }
+ }
+
+ size_t add(std::function<void()> f)
+ {
+ dq.push_back(
+ std::make_pair(std::chrono::steady_clock::now(), std::move(f)));
+ size_t ret = step + dq.size() - 1;
+
+ BMCWEB_LOG_DEBUG << "timer add inside: " << this << ' ' << ret;
+ return ret;
+ }
+
+ void process()
+ {
+ auto now = std::chrono::steady_clock::now();
+ while (!dq.empty())
+ {
+ auto& x = dq.front();
+ if (now - x.first < std::chrono::seconds(5))
+ {
+ break;
+ }
+ if (x.second)
+ {
+ BMCWEB_LOG_DEBUG << "timer call: " << this << ' ' << step;
+ // we know that timer handlers are very simple currenty; call
+ // here
+ x.second();
+ }
+ dq.pop_front();
+ step++;
+ }
+ }
+
+ private:
+ using storage_type =
+ std::pair<std::chrono::time_point<std::chrono::steady_clock>,
+ std::function<void()>>;
+
+ boost::circular_buffer_space_optimized<storage_type,
+ std::allocator<storage_type>>
+ dq{};
+
+ // boost::circular_buffer<storage_type> dq{20};
+ // std::deque<storage_type> dq{};
+ size_t step{};
+};
+} // namespace detail
+} // namespace crow
diff --git a/http/utility.h b/http/utility.h
new file mode 100644
index 0000000..d08d548
--- /dev/null
+++ b/http/utility.h
@@ -0,0 +1,753 @@
+#pragma once
+
+#include "nlohmann/json.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <functional>
+#include <regex>
+#include <stdexcept>
+#include <string>
+#include <tuple>
+
+namespace crow
+{
+namespace black_magic
+{
+struct OutOfRange
+{
+ OutOfRange(unsigned /*pos*/, unsigned /*length*/)
+ {
+ }
+};
+constexpr unsigned requiresInRange(unsigned i, unsigned len)
+{
+ return i >= len ? throw OutOfRange(i, len) : i;
+}
+
+class ConstStr
+{
+ const char* const beginPtr;
+ unsigned sizeUint;
+
+ public:
+ template <unsigned N>
+ constexpr ConstStr(const char (&arr)[N]) : beginPtr(arr), sizeUint(N - 1)
+ {
+ static_assert(N >= 1, "not a string literal");
+ }
+ constexpr char operator[](unsigned i) const
+ {
+ return requiresInRange(i, sizeUint), beginPtr[i];
+ }
+
+ constexpr operator const char*() const
+ {
+ return beginPtr;
+ }
+
+ constexpr const char* begin() const
+ {
+ return beginPtr;
+ }
+ constexpr const char* end() const
+ {
+ return beginPtr + sizeUint;
+ }
+
+ constexpr unsigned size() const
+ {
+ return sizeUint;
+ }
+};
+
+constexpr unsigned findClosingTag(ConstStr s, unsigned p)
+{
+ return s[p] == '>' ? p : findClosingTag(s, p + 1);
+}
+
+constexpr bool isValid(ConstStr s, unsigned i = 0, int f = 0)
+{
+ return i == s.size()
+ ? f == 0
+ : f < 0 || f >= 2
+ ? false
+ : s[i] == '<' ? isValid(s, i + 1, f + 1)
+ : s[i] == '>' ? isValid(s, i + 1, f - 1)
+ : isValid(s, i + 1, f);
+}
+
+constexpr bool isEquP(const char* a, const char* b, unsigned n)
+{
+ return *a == 0 && *b == 0 && n == 0
+ ? true
+ : (*a == 0 || *b == 0)
+ ? false
+ : n == 0 ? true
+ : *a != *b ? false : isEquP(a + 1, b + 1, n - 1);
+}
+
+constexpr bool isEquN(ConstStr a, unsigned ai, ConstStr b, unsigned bi,
+ unsigned n)
+{
+ return ai + n > a.size() || bi + n > b.size()
+ ? false
+ : n == 0 ? true
+ : a[ai] != b[bi] ? false
+ : isEquN(a, ai + 1, b, bi + 1, n - 1);
+}
+
+constexpr bool isInt(ConstStr s, unsigned i)
+{
+ return isEquN(s, i, "<int>", 0, 5);
+}
+
+constexpr bool isUint(ConstStr s, unsigned i)
+{
+ return isEquN(s, i, "<uint>", 0, 6);
+}
+
+constexpr bool isFloat(ConstStr s, unsigned i)
+{
+ return isEquN(s, i, "<float>", 0, 7) || isEquN(s, i, "<double>", 0, 8);
+}
+
+constexpr bool isStr(ConstStr s, unsigned i)
+{
+ return isEquN(s, i, "<str>", 0, 5) || isEquN(s, i, "<string>", 0, 8);
+}
+
+constexpr bool isPath(ConstStr s, unsigned i)
+{
+ return isEquN(s, i, "<path>", 0, 6);
+}
+
+template <typename T> struct parameter_tag
+{
+ static const int value = 0;
+};
+#define BMCWEB_INTERNAL_PARAMETER_TAG(t, i) \
+ template <> struct parameter_tag<t> \
+ { \
+ static const int value = i; \
+ }
+BMCWEB_INTERNAL_PARAMETER_TAG(int, 1);
+BMCWEB_INTERNAL_PARAMETER_TAG(char, 1);
+BMCWEB_INTERNAL_PARAMETER_TAG(short, 1);
+BMCWEB_INTERNAL_PARAMETER_TAG(long, 1);
+BMCWEB_INTERNAL_PARAMETER_TAG(long long, 1);
+BMCWEB_INTERNAL_PARAMETER_TAG(unsigned int, 2);
+BMCWEB_INTERNAL_PARAMETER_TAG(unsigned char, 2);
+BMCWEB_INTERNAL_PARAMETER_TAG(unsigned short, 2);
+BMCWEB_INTERNAL_PARAMETER_TAG(unsigned long, 2);
+BMCWEB_INTERNAL_PARAMETER_TAG(unsigned long long, 2);
+BMCWEB_INTERNAL_PARAMETER_TAG(double, 3);
+BMCWEB_INTERNAL_PARAMETER_TAG(std::string, 4);
+#undef BMCWEB_INTERNAL_PARAMETER_TAG
+template <typename... Args> struct compute_parameter_tag_from_args_list;
+
+template <> struct compute_parameter_tag_from_args_list<>
+{
+ static const int value = 0;
+};
+
+template <typename Arg, typename... Args>
+struct compute_parameter_tag_from_args_list<Arg, Args...>
+{
+ static const int subValue =
+ compute_parameter_tag_from_args_list<Args...>::value;
+ static const int value =
+ parameter_tag<typename std::decay<Arg>::type>::value
+ ? subValue * 6 +
+ parameter_tag<typename std::decay<Arg>::type>::value
+ : subValue;
+};
+
+static inline bool isParameterTagCompatible(uint64_t a, uint64_t b)
+{
+ if (a == 0)
+ {
+ return b == 0;
+ }
+ if (b == 0)
+ {
+ return a == 0;
+ }
+ uint64_t sa = a % 6;
+ uint64_t sb = a % 6;
+ if (sa == 5)
+ {
+ sa = 4;
+ }
+ if (sb == 5)
+ {
+ sb = 4;
+ }
+ if (sa != sb)
+ {
+ return false;
+ }
+ return isParameterTagCompatible(a / 6, b / 6);
+}
+
+static inline unsigned findClosingTagRuntime(const char* s, unsigned p)
+{
+ return s[p] == 0 ? throw std::runtime_error("unmatched tag <")
+ : s[p] == '>' ? p : findClosingTagRuntime(s, p + 1);
+}
+
+static inline uint64_t getParameterTagRuntime(const char* s, unsigned p = 0)
+{
+ return s[p] == 0
+ ? 0
+ : s[p] == '<'
+ ? (std::strncmp(s + p, "<int>", 5) == 0
+ ? getParameterTagRuntime(
+ s, findClosingTagRuntime(s, p)) *
+ 6 +
+ 1
+ : std::strncmp(s + p, "<uint>", 6) == 0
+ ? getParameterTagRuntime(
+ s, findClosingTagRuntime(s, p)) *
+ 6 +
+ 2
+ : (std::strncmp(s + p, "<float>", 7) == 0 ||
+ std::strncmp(s + p, "<double>", 8) == 0)
+ ? getParameterTagRuntime(
+ s, findClosingTagRuntime(s, p)) *
+ 6 +
+ 3
+ : (std::strncmp(s + p, "<str>", 5) ==
+ 0 ||
+ std::strncmp(s + p, "<string>", 8) ==
+ 0)
+ ? getParameterTagRuntime(
+ s, findClosingTagRuntime(
+ s, p)) *
+ 6 +
+ 4
+ : std::strncmp(s + p, "<path>",
+ 6) == 0
+ ? getParameterTagRuntime(
+ s,
+ findClosingTagRuntime(
+ s, p)) *
+ 6 +
+ 5
+ : throw std::runtime_error(
+ "invalid parameter "
+ "type"))
+ : getParameterTagRuntime(s, p + 1);
+}
+
+constexpr uint64_t get_parameter_tag(ConstStr s, unsigned p = 0)
+{
+ return p == s.size()
+ ? 0
+ : s[p] == '<'
+ ? (isInt(s, p)
+ ? get_parameter_tag(s, findClosingTag(s, p)) * 6 + 1
+ : isUint(s, p)
+ ? get_parameter_tag(s, findClosingTag(s, p)) *
+ 6 +
+ 2
+ : isFloat(s, p)
+ ? get_parameter_tag(
+ s, findClosingTag(s, p)) *
+ 6 +
+ 3
+ : isStr(s, p)
+ ? get_parameter_tag(
+ s, findClosingTag(s, p)) *
+ 6 +
+ 4
+ : isPath(s, p)
+ ? get_parameter_tag(
+ s, findClosingTag(
+ s, p)) *
+ 6 +
+ 5
+ : throw std::runtime_error(
+ "invalid parameter "
+ "type"))
+ : get_parameter_tag(s, p + 1);
+}
+
+template <typename... T> struct S
+{
+ template <typename U> using push = S<U, T...>;
+ template <typename U> using push_back = S<T..., U>;
+ template <template <typename... Args> class U> using rebind = U<T...>;
+};
+template <typename F, typename Set> struct CallHelper;
+template <typename F, typename... Args> struct CallHelper<F, S<Args...>>
+{
+ template <typename F1, typename... Args1,
+ typename = decltype(std::declval<F1>()(std::declval<Args1>()...))>
+ static char __test(int);
+
+ template <typename...> static int __test(...);
+
+ static constexpr bool value = sizeof(__test<F, Args...>(0)) == sizeof(char);
+};
+
+template <uint64_t N> struct SingleTagToType
+{
+};
+
+template <> struct SingleTagToType<1>
+{
+ using type = int64_t;
+};
+
+template <> struct SingleTagToType<2>
+{
+ using type = uint64_t;
+};
+
+template <> struct SingleTagToType<3>
+{
+ using type = double;
+};
+
+template <> struct SingleTagToType<4>
+{
+ using type = std::string;
+};
+
+template <> struct SingleTagToType<5>
+{
+ using type = std::string;
+};
+
+template <uint64_t Tag> struct Arguments
+{
+ using subarguments = typename Arguments<Tag / 6>::type;
+ using type = typename subarguments::template push<
+ typename SingleTagToType<Tag % 6>::type>;
+};
+
+template <> struct Arguments<0>
+{
+ using type = S<>;
+};
+
+template <typename... T> struct LastElementType
+{
+ using type =
+ typename std::tuple_element<sizeof...(T) - 1, std::tuple<T...>>::type;
+};
+
+template <> struct LastElementType<>
+{
+};
+
+// from
+// http://stackoverflow.com/questions/13072359/c11-compile-time-array-with-logarithmic-evaluation-depth
+template <class T> using Invoke = typename T::type;
+
+template <unsigned...> struct Seq
+{
+ using type = Seq;
+};
+
+template <class S1, class S2> struct concat;
+
+template <unsigned... I1, unsigned... I2>
+struct concat<Seq<I1...>, Seq<I2...>> : Seq<I1..., (sizeof...(I1) + I2)...>
+{
+};
+
+template <class S1, class S2> using Concat = Invoke<concat<S1, S2>>;
+
+template <size_t N> struct gen_seq;
+template <size_t N> using GenSeq = Invoke<gen_seq<N>>;
+
+template <size_t N> struct gen_seq : Concat<GenSeq<N / 2>, GenSeq<N - N / 2>>
+{
+};
+
+template <> struct gen_seq<0> : Seq<>
+{
+};
+template <> struct gen_seq<1> : Seq<0>
+{
+};
+
+template <typename Seq, typename Tuple> struct PopBackHelper;
+
+template <unsigned... N, typename Tuple> struct PopBackHelper<Seq<N...>, Tuple>
+{
+ template <template <typename... Args> class U>
+ using rebind = U<typename std::tuple_element<N, Tuple>::type...>;
+};
+
+template <typename... T>
+struct PopBack //: public PopBackHelper<typename
+ // gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>
+{
+ template <template <typename... Args> class U>
+ using rebind =
+ typename PopBackHelper<typename gen_seq<sizeof...(T) - 1UL>::type,
+ std::tuple<T...>>::template rebind<U>;
+};
+
+template <> struct PopBack<>
+{
+ template <template <typename... Args> class U> using rebind = U<>;
+};
+
+// from
+// http://stackoverflow.com/questions/2118541/check-if-c0x-parameter-pack-contains-a-type
+template <typename Tp, typename... List> struct Contains : std::true_type
+{
+};
+
+template <typename Tp, typename Head, typename... Rest>
+struct Contains<Tp, Head, Rest...>
+ : std::conditional<std::is_same<Tp, Head>::value, std::true_type,
+ Contains<Tp, Rest...>>::type
+{
+};
+
+template <typename Tp> struct Contains<Tp> : std::false_type
+{
+};
+
+template <typename T> struct EmptyContext
+{
+};
+
+template <typename T> struct promote
+{
+ using type = T;
+};
+
+#define BMCWEB_INTERNAL_PROMOTE_TYPE(t1, t2) \
+ template <> struct promote<t1> \
+ { \
+ using type = t2; \
+ }
+
+BMCWEB_INTERNAL_PROMOTE_TYPE(char, int64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(short, int64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(int, int64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(long, int64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(long long, int64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(unsigned char, uint64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(unsigned short, uint64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(unsigned int, uint64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(unsigned long, uint64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(unsigned long long, uint64_t);
+BMCWEB_INTERNAL_PROMOTE_TYPE(float, double);
+#undef BMCWEB_INTERNAL_PROMOTE_TYPE
+
+template <typename T> using promote_t = typename promote<T>::type;
+
+} // namespace black_magic
+
+namespace detail
+{
+
+template <class T, std::size_t N, class... Args>
+struct GetIndexOfElementFromTupleByTypeImpl
+{
+ static constexpr std::size_t value = N;
+};
+
+template <class T, std::size_t N, class... Args>
+struct GetIndexOfElementFromTupleByTypeImpl<T, N, T, Args...>
+{
+ static constexpr std::size_t value = N;
+};
+
+template <class T, std::size_t N, class U, class... Args>
+struct GetIndexOfElementFromTupleByTypeImpl<T, N, U, Args...>
+{
+ static constexpr std::size_t value =
+ GetIndexOfElementFromTupleByTypeImpl<T, N + 1, Args...>::value;
+};
+
+} // namespace detail
+
+namespace utility
+{
+template <class T, class... Args> T& getElementByType(std::tuple<Args...>& t)
+{
+ return std::get<
+ detail::GetIndexOfElementFromTupleByTypeImpl<T, 0, Args...>::value>(t);
+}
+
+template <typename T> struct function_traits;
+
+template <typename T>
+struct function_traits : public function_traits<decltype(&T::operator())>
+{
+ using parent_t = function_traits<decltype(&T::operator())>;
+ static const size_t arity = parent_t::arity;
+ using result_type = typename parent_t::result_type;
+ template <size_t i> using arg = typename parent_t::template arg<i>;
+};
+
+template <typename ClassType, typename r, typename... Args>
+struct function_traits<r (ClassType::*)(Args...) const>
+{
+ static const size_t arity = sizeof...(Args);
+
+ using result_type = r;
+
+ template <size_t i>
+ using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
+};
+
+template <typename ClassType, typename r, typename... Args>
+struct function_traits<r (ClassType::*)(Args...)>
+{
+ static const size_t arity = sizeof...(Args);
+
+ using result_type = r;
+
+ template <size_t i>
+ using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
+};
+
+template <typename r, typename... Args>
+struct function_traits<std::function<r(Args...)>>
+{
+ static const size_t arity = sizeof...(Args);
+
+ using result_type = r;
+
+ template <size_t i>
+ using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
+};
+
+inline static std::string base64encode(
+ const char* data, size_t size,
+ const char* key =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
+{
+ std::string ret;
+ ret.resize((size + 2) / 3 * 4);
+ auto it = ret.begin();
+ while (size >= 3)
+ {
+ *it++ = key[(static_cast<unsigned char>(*data) & 0xFC) >> 2];
+ unsigned char h = static_cast<unsigned char>(
+ (static_cast<unsigned char>(*data++) & 0x03u) << 4u);
+ *it++ = key[h | ((static_cast<unsigned char>(*data) & 0xF0) >> 4)];
+ h = static_cast<unsigned char>(
+ (static_cast<unsigned char>(*data++) & 0x0F) << 2u);
+ *it++ = key[h | ((static_cast<unsigned char>(*data) & 0xC0) >> 6)];
+ *it++ = key[static_cast<unsigned char>(*data++) & 0x3F];
+
+ size -= 3;
+ }
+ if (size == 1)
+ {
+ *it++ = key[(static_cast<unsigned char>(*data) & 0xFC) >> 2];
+ unsigned char h = static_cast<unsigned char>(
+ (static_cast<unsigned char>(*data++) & 0x03) << 4u);
+ *it++ = key[h];
+ *it++ = '=';
+ *it++ = '=';
+ }
+ else if (size == 2)
+ {
+ *it++ = key[(static_cast<unsigned char>(*data) & 0xFC) >> 2];
+ unsigned char h = static_cast<unsigned char>(
+ (static_cast<unsigned char>(*data++) & 0x03) << 4u);
+ *it++ = key[h | ((static_cast<unsigned char>(*data) & 0xF0) >> 4)];
+ h = static_cast<unsigned char>(
+ (static_cast<unsigned char>(*data++) & 0x0F) << 2u);
+ *it++ = key[h];
+ *it++ = '=';
+ }
+ return ret;
+}
+
+inline static std::string base64encodeUrlsafe(const char* data, size_t size)
+{
+ return base64encode(
+ data, size,
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
+}
+
+// TODO this is temporary and should be deleted once base64 is refactored out of
+// crow
+inline bool base64Decode(const std::string_view input, std::string& output)
+{
+ static const char nop = static_cast<char>(-1);
+ // See note on encoding_data[] in above function
+ static const char decodingData[] = {
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, 62, nop, nop, nop, 63, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, nop, nop, nop, nop, nop, nop, nop, 0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, nop, nop, nop, nop, nop, nop, 26,
+ 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
+ nop, nop, nop, nop};
+
+ size_t inputLength = input.size();
+
+ // allocate space for output string
+ output.clear();
+ output.reserve(((inputLength + 2) / 3) * 4);
+
+ // for each 4-bytes sequence from the input, extract 4 6-bits sequences by
+ // droping first two bits
+ // and regenerate into 3 8-bits sequences
+
+ for (size_t i = 0; i < inputLength; i++)
+ {
+ char base64code0;
+ char base64code1;
+ char base64code2 = 0; // initialized to 0 to suppress warnings
+ char base64code3;
+
+ base64code0 = decodingData[static_cast<int>(input[i])]; // NOLINT
+ if (base64code0 == nop)
+ { // non base64 character
+ return false;
+ }
+ if (!(++i < inputLength))
+ { // we need at least two input bytes for first
+ // byte output
+ return false;
+ }
+ base64code1 = decodingData[static_cast<int>(input[i])]; // NOLINT
+ if (base64code1 == nop)
+ { // non base64 character
+ return false;
+ }
+ output +=
+ static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3));
+
+ if (++i < inputLength)
+ {
+ char c = input[i];
+ if (c == '=')
+ { // padding , end of input
+ return (base64code1 & 0x0f) == 0;
+ }
+ base64code2 = decodingData[static_cast<int>(input[i])]; // NOLINT
+ if (base64code2 == nop)
+ { // non base64 character
+ return false;
+ }
+ output += static_cast<char>(((base64code1 << 4) & 0xf0) |
+ ((base64code2 >> 2) & 0x0f));
+ }
+
+ if (++i < inputLength)
+ {
+ char c = input[i];
+ if (c == '=')
+ { // padding , end of input
+ return (base64code2 & 0x03) == 0;
+ }
+ base64code3 = decodingData[static_cast<int>(input[i])]; // NOLINT
+ if (base64code3 == nop)
+ { // non base64 character
+ return false;
+ }
+ output +=
+ static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3));
+ }
+ }
+
+ return true;
+}
+
+inline void escapeHtml(std::string& data)
+{
+ std::string buffer;
+ // less than 5% of characters should be larger, so reserve a buffer of the
+ // right size
+ buffer.reserve(data.size() * 11 / 10);
+ for (size_t pos = 0; pos != data.size(); ++pos)
+ {
+ switch (data[pos])
+ {
+ case '&':
+ buffer.append("&");
+ break;
+ case '\"':
+ buffer.append(""");
+ break;
+ case '\'':
+ buffer.append("'");
+ break;
+ case '<':
+ buffer.append("<");
+ break;
+ case '>':
+ buffer.append(">");
+ break;
+ default:
+ buffer.append(&data[pos], 1);
+ break;
+ }
+ }
+ data.swap(buffer);
+}
+
+inline void convertToLinks(std::string& s)
+{
+ const static std::regex r{"("@odata\\.((id)|(Context))"[ \\n]*:[ "
+ "\\n]*)("((?!").*)")"};
+ s = std::regex_replace(s, r, "$1<a href=\"$6\">$5</a>");
+
+ const static std::regex nextLink{
+ "("Members@odata\\.((nextLink))"[ \\n]*:[ "
+ "\\n]*)("((?!").*)")"};
+ s = std::regex_replace(s, nextLink, "$1<a href=\"$5\">$4</a>");
+
+ const static std::regex uri{"("((Uri))"[ \\n]*:[ "
+ "\\n]*)("((?!").*)")"};
+ s = std::regex_replace(s, uri, "$1<a href=\"$5\">$4</a>");
+}
+
+/**
+ * Method returns Date Time information according to requested format
+ *
+ * @param[in] time time in second since the Epoch
+ *
+ * @return Date Time according to requested format
+ */
+inline std::string getDateTime(const std::time_t& time)
+{
+ std::array<char, 128> dateTime;
+ std::string redfishDateTime("0000-00-00T00:00:00Z00:00");
+
+ if (std::strftime(dateTime.begin(), dateTime.size(), "%FT%T%z",
+ std::localtime(&time)))
+ {
+ // insert the colon required by the ISO 8601 standard
+ redfishDateTime = std::string(dateTime.data());
+ redfishDateTime.insert(redfishDateTime.end() - 2, ':');
+ }
+
+ return redfishDateTime;
+}
+
+inline std::string dateTimeNow()
+{
+ std::time_t time = std::time(nullptr);
+ return getDateTime(time);
+}
+
+} // namespace utility
+} // namespace crow
diff --git a/http/websocket.h b/http/websocket.h
new file mode 100644
index 0000000..61b3e5a
--- /dev/null
+++ b/http/websocket.h
@@ -0,0 +1,250 @@
+#pragma once
+#include <array>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/beast/websocket.hpp>
+#include <functional>
+
+#include "http_request.h"
+
+#ifdef BMCWEB_ENABLE_SSL
+#include <boost/beast/websocket/ssl.hpp>
+#endif
+
+namespace crow
+{
+namespace websocket
+{
+struct Connection : std::enable_shared_from_this<Connection>
+{
+ public:
+ explicit Connection(const crow::Request& reqIn) :
+ req(reqIn), userdataPtr(nullptr){};
+
+ virtual void sendBinary(const std::string_view msg) = 0;
+ virtual void sendBinary(std::string&& msg) = 0;
+ virtual void sendText(const std::string_view msg) = 0;
+ virtual void sendText(std::string&& msg) = 0;
+ virtual void close(const std::string_view msg = "quit") = 0;
+ virtual boost::asio::io_context& get_io_context() = 0;
+ virtual ~Connection() = default;
+
+ void userdata(void* u)
+ {
+ userdataPtr = u;
+ }
+ void* userdata()
+ {
+ return userdataPtr;
+ }
+
+ crow::Request req;
+
+ private:
+ void* userdataPtr;
+};
+
+template <typename Adaptor> class ConnectionImpl : public Connection
+{
+ public:
+ ConnectionImpl(
+ const crow::Request& reqIn, Adaptor adaptorIn,
+ std::function<void(Connection&)> open_handler,
+ std::function<void(Connection&, const std::string&, bool)>
+ message_handler,
+ std::function<void(Connection&, const std::string&)> close_handler,
+ std::function<void(Connection&)> error_handler) :
+ Connection(reqIn),
+ ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
+ openHandler(std::move(open_handler)),
+ messageHandler(std::move(message_handler)),
+ closeHandler(std::move(close_handler)),
+ errorHandler(std::move(error_handler))
+ {
+ BMCWEB_LOG_DEBUG << "Creating new connection " << this;
+ }
+
+ boost::asio::io_context& get_io_context() override
+ {
+ return static_cast<boost::asio::io_context&>(
+ ws.get_executor().context());
+ }
+
+ void start()
+ {
+ BMCWEB_LOG_DEBUG << "starting connection " << this;
+
+ using bf = boost::beast::http::field;
+
+ std::string_view protocol =
+ req.getHeaderValue(bf::sec_websocket_protocol);
+
+ // Perform the websocket upgrade
+ ws.async_accept_ex(
+ req.req,
+ [protocol{std::string(protocol)}](
+ boost::beast::websocket::response_type& m) {
+ if (!protocol.empty())
+ {
+ m.insert(bf::sec_websocket_protocol, protocol);
+ }
+
+ m.insert(bf::strict_transport_security, "max-age=31536000; "
+ "includeSubdomains; "
+ "preload");
+ m.insert(bf::pragma, "no-cache");
+ m.insert(bf::cache_control, "no-Store,no-Cache");
+ m.insert("Content-Security-Policy", "default-src 'self'");
+ m.insert("X-XSS-Protection", "1; "
+ "mode=block");
+ m.insert("X-Content-Type-Options", "nosniff");
+ },
+ [this, self(shared_from_this())](boost::system::error_code ec) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
+ return;
+ }
+ acceptDone();
+ });
+ }
+
+ void sendBinary(const std::string_view msg) override
+ {
+ ws.binary(true);
+ outBuffer.emplace_back(msg);
+ doWrite();
+ }
+
+ void sendBinary(std::string&& msg) override
+ {
+ ws.binary(true);
+ outBuffer.emplace_back(std::move(msg));
+ doWrite();
+ }
+
+ void sendText(const std::string_view msg) override
+ {
+ ws.text(true);
+ outBuffer.emplace_back(msg);
+ doWrite();
+ }
+
+ void sendText(std::string&& msg) override
+ {
+ ws.text(true);
+ outBuffer.emplace_back(std::move(msg));
+ doWrite();
+ }
+
+ void close(const std::string_view msg) override
+ {
+ ws.async_close(
+ boost::beast::websocket::close_code::normal,
+ [self(shared_from_this())](boost::system::error_code ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ return;
+ }
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
+ return;
+ }
+ });
+ }
+
+ void acceptDone()
+ {
+ BMCWEB_LOG_DEBUG << "Websocket accepted connection";
+
+ if (openHandler)
+ {
+ openHandler(*this);
+ }
+ doRead();
+ }
+
+ void doRead()
+ {
+ ws.async_read(inBuffer,
+ [this, self(shared_from_this())](
+ boost::beast::error_code ec, std::size_t bytes_read) {
+ if (ec)
+ {
+ if (ec != boost::beast::websocket::error::closed)
+ {
+ BMCWEB_LOG_ERROR << "doRead error " << ec;
+ }
+ if (closeHandler)
+ {
+ std::string_view reason = ws.reason().reason;
+ closeHandler(*this, std::string(reason));
+ }
+ return;
+ }
+ if (messageHandler)
+ {
+ messageHandler(*this, inString, ws.got_text());
+ }
+ inBuffer.consume(bytes_read);
+ inString.clear();
+ doRead();
+ });
+ }
+
+ void doWrite()
+ {
+ // If we're already doing a write, ignore the request, it will be picked
+ // up when the current write is complete
+ if (doingWrite)
+ {
+ return;
+ }
+
+ if (outBuffer.empty())
+ {
+ // Done for now
+ return;
+ }
+ doingWrite = true;
+ ws.async_write(
+ boost::asio::buffer(outBuffer.front()),
+ [this, self(shared_from_this())](boost::beast::error_code ec,
+ std::size_t bytes_written) {
+ doingWrite = false;
+ outBuffer.erase(outBuffer.begin());
+ if (ec == boost::beast::websocket::error::closed)
+ {
+ // Do nothing here. doRead handler will call the
+ // closeHandler.
+ close("Write error");
+ return;
+ }
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
+ return;
+ }
+ doWrite();
+ });
+ }
+
+ private:
+ boost::beast::websocket::stream<Adaptor> ws;
+
+ std::string inString;
+ boost::asio::dynamic_string_buffer<std::string::value_type,
+ std::string::traits_type,
+ std::string::allocator_type>
+ inBuffer;
+ std::vector<std::string> outBuffer;
+ bool doingWrite = false;
+
+ std::function<void(Connection&)> openHandler;
+ std::function<void(Connection&, const std::string&, bool)> messageHandler;
+ std::function<void(Connection&, const std::string&)> closeHandler;
+ std::function<void(Connection&)> errorHandler;
+};
+} // namespace websocket
+} // namespace crow