diff options
-rw-r--r-- | CMakeLists.txt.in | 4 | ||||
-rw-r--r-- | crow/include/crow.h | 1 | ||||
-rw-r--r-- | crow/include/crow/app.h | 8 | ||||
-rw-r--r-- | crow/include/crow/http_connection.h | 98 | ||||
-rw-r--r-- | crow/include/crow/http_response.h | 5 | ||||
-rw-r--r-- | crow/include/crow/http_server.h | 57 | ||||
-rw-r--r-- | crow/include/crow/routing.h | 27 | ||||
-rw-r--r-- | crow/include/crow/socket_adaptors.h | 200 | ||||
-rw-r--r-- | crow/include/crow/websocket.h | 26 | ||||
-rw-r--r-- | include/obmc_console.hpp | 2 | ||||
-rw-r--r-- | redfish-core/lib/service_root.hpp | 3 |
11 files changed, 134 insertions, 297 deletions
diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in index edba7923ef..0aed5d55e7 100644 --- a/CMakeLists.txt.in +++ b/CMakeLists.txt.in @@ -36,8 +36,8 @@ externalproject_add ( externalproject_add ( Boost URL - https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz - URL_MD5 d275cd85b00022313c171f602db59fc5 SOURCE_DIR + https://dl.bintray.com/boostorg/release/1.68.0/source/boost_1_68_0.tar.gz + URL_MD5 5d8b4503582fffa9eefdb9045359c239 SOURCE_DIR "${CMAKE_BINARY_DIR}/boost-src" BINARY_DIR "${CMAKE_BINARY_DIR}/boost-build" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND mkdir -p "${CMAKE_BINARY_DIR}/prefix/include/" && cp -R diff --git a/crow/include/crow.h b/crow/include/crow.h index 7e31cf335c..7fe640aa63 100644 --- a/crow/include/crow.h +++ b/crow/include/crow.h @@ -11,7 +11,6 @@ #include "crow/middleware_context.h" #include "crow/query_string.h" #include "crow/routing.h" -#include "crow/socket_adaptors.h" #include "crow/timer_queue.h" #include "crow/utility.h" #include "crow/websocket.h" diff --git a/crow/include/crow/app.h b/crow/include/crow/app.h index 9a2281afb9..9ce2f8bf21 100644 --- a/crow/include/crow/app.h +++ b/crow/include/crow/app.h @@ -29,13 +29,13 @@ template <typename... Middlewares> class Crow using self_t = Crow; #ifdef BMCWEB_ENABLE_SSL - using ssl_socket_t = SSLAdaptor; + 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 = SocketAdaptor; + 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> io = std::make_shared<boost::asio::io_context>()) : io(std::move(io)) @@ -49,7 +49,7 @@ template <typename... Middlewares> class Crow template <typename Adaptor> void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) { - router.handleUpgrade(req, res, adaptor); + router.handleUpgrade(req, res, std::move(adaptor)); } void handle(const Request& req, Response& res) diff --git a/crow/include/crow/http_connection.h b/crow/include/crow/http_connection.h index 297648e95a..6f1dafded0 100644 --- a/crow/include/crow/http_connection.h +++ b/crow/include/crow/http_connection.h @@ -15,12 +15,12 @@ #include "crow/http_response.h" #include "crow/logging.h" #include "crow/middleware_context.h" -#include "crow/socket_adaptors.h" #include "crow/timer_queue.h" #include "crow/utility.h" #ifdef BMCWEB_ENABLE_SSL -#include <boost/asio/ssl/stream.hpp> +#include <boost/asio/ssl.hpp> +#include <boost/beast/experimental/core/ssl_stream.hpp> #endif namespace crow @@ -251,9 +251,8 @@ class Connection const std::string& server_name, std::tuple<Middlewares...>* middlewares, std::function<std::string()>& get_cached_date_str_f, - detail::TimerQueue& timerQueue, - typename Adaptor::context* adaptorCtx) : - adaptor(ioService, adaptorCtx), + detail::TimerQueue& timerQueue, Adaptor adaptorIn) : + adaptor(std::move(adaptorIn)), handler(handler), serverName(server_name), middlewares(middlewares), getCachedDateStr(get_cached_date_str_f), timerQueue(timerQueue) { @@ -280,25 +279,36 @@ class Connection #endif } - decltype(std::declval<Adaptor>().rawSocket())& socket() + Adaptor& socket() { - return adaptor.rawSocket(); + return adaptor; } void start() { - adaptor.start([this](const boost::system::error_code& ec) { - if (!ec) - { - startDeadline(); - doReadHeaders(); - } - else - { - checkDestroy(); - } - }); + 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() @@ -318,21 +328,30 @@ class Connection } } - BMCWEB_LOG_INFO << "Request: " << adaptor.remoteEndpoint() << " " - << this << " HTTP/" << req->version() / 10 << "." - << req->version() % 10 << ' ' << req->methodString() - << " " << req->target(); + std::string epName; + boost::system::error_code ec; + tcp::endpoint ep = adaptor.lowest_layer().remote_endpoint(ec); + if (!ec) + { + epName = boost::lexical_cast<std::string>(ep); + } + + BMCWEB_LOG_INFO << "Request: " << epName << " " << this << " HTTP/" + << req->version() / 10 << "." << req->version() % 10 + << ' ' << req->methodString() << " " << req->target(); needToCallAfterHandlers = false; if (!isInvalidRequest) { res.completeRequestHandler = [] {}; - res.isAliveHelper = [this]() -> bool { return adaptor.isOpen(); }; + res.isAliveHelper = [this]() -> bool { + return adaptor.lowest_layer().is_open(); + }; ctx = detail::Context<Middlewares...>(); req->middlewareContext = (void*)&ctx; - req->ioService = &adaptor.getIoService(); + req->ioService = &adaptor.get_executor().context(); detail::middlewareCallHelper< 0, decltype(ctx), decltype(*middlewares), Middlewares...>( *middlewares, *req, res, ctx); @@ -352,10 +371,6 @@ class Connection }; needToCallAfterHandlers = true; handler->handle(*req, res); - if (req->keepAlive()) - { - res.addHeader("connection", "Keep-Alive"); - } } else { @@ -385,9 +400,9 @@ class Connection } // auto self = this->shared_from_this(); - res.completeRequestHandler = nullptr; + res.completeRequestHandler = res.completeRequestHandler = [] {}; - if (!adaptor.isOpen()) + if (!adaptor.lowest_layer().is_open()) { // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " << // isReading @@ -429,7 +444,7 @@ class Connection // Clean up any previous Connection. boost::beast::http::async_read_header( - adaptor.socket(), buffer, *parser, + adaptor, buffer, *parser, [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { isReading = false; @@ -446,7 +461,7 @@ class Connection { // if the adaptor isn't open anymore, and wasn't handed to a // websocket, treat as an error - if (!adaptor.isOpen() && !req->isUpgrade()) + if (!adaptor.lowest_layer().is_open() && !req->isUpgrade()) { errorWhileReading = true; } @@ -455,7 +470,7 @@ class Connection if (errorWhileReading) { cancelDeadlineTimer(); - adaptor.close(); + adaptor.lowest_layer().close(); BMCWEB_LOG_DEBUG << this << " from read(1)"; checkDestroy(); return; @@ -480,7 +495,7 @@ class Connection BMCWEB_LOG_DEBUG << this << " doRead"; boost::beast::http::async_read( - adaptor.socket(), buffer, *parser, + adaptor, buffer, *parser, [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { BMCWEB_LOG_ERROR << this << " async_read " << bytes_transferred @@ -495,7 +510,7 @@ class Connection } else { - if (!adaptor.isOpen()) + if (!adaptor.lowest_layer().is_open()) { errorWhileReading = true; } @@ -503,7 +518,7 @@ class Connection if (errorWhileReading) { cancelDeadlineTimer(); - adaptor.close(); + adaptor.lowest_layer().close(); BMCWEB_LOG_DEBUG << this << " from read(1)"; checkDestroy(); return; @@ -520,7 +535,7 @@ class Connection res.preparePayload(); serializer.emplace(*res.stringResponse); boost::beast::http::async_write( - adaptor.socket(), *serializer, + adaptor, *serializer, [&](const boost::system::error_code& ec, std::size_t bytes_transferred) { isWriting = false; @@ -533,9 +548,9 @@ class Connection checkDestroy(); return; } - if (!req->keepAlive()) + if (!res.keepAlive()) { - adaptor.close(); + adaptor.lowest_layer().close(); BMCWEB_LOG_DEBUG << this << " from write(1)"; checkDestroy(); return; @@ -577,11 +592,11 @@ class Connection cancelDeadlineTimer(); timerCancelKey = timerQueue.add([this] { - if (!adaptor.isOpen()) + if (!adaptor.lowest_layer().is_open()) { return; } - adaptor.close(); + adaptor.lowest_layer().close(); }); BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' ' << timerCancelKey; @@ -614,7 +629,6 @@ class Connection bool isWriting{}; bool needToCallAfterHandlers{}; bool needToStartReadAfterComplete{}; - bool addKeepAlive{}; std::tuple<Middlewares...>* middlewares; detail::Context<Middlewares...> ctx; diff --git a/crow/include/crow/http_response.h b/crow/include/crow/http_response.h index 6090a5b884..1deae34fbb 100644 --- a/crow/include/crow/http_response.h +++ b/crow/include/crow/http_response.h @@ -114,6 +114,11 @@ struct Response stringResponse->keep_alive(k); } + bool keepAlive() + { + return stringResponse->keep_alive(); + } + void preparePayload() { stringResponse->prepare_payload(); diff --git a/crow/include/crow/http_server.h b/crow/include/crow/http_server.h index ebf55707bc..e8cb762e12 100644 --- a/crow/include/crow/http_server.h +++ b/crow/include/crow/http_server.h @@ -17,6 +17,7 @@ #include "crow/timer_queue.h" #ifdef BMCWEB_ENABLE_SSL #include <boost/asio/ssl/context.hpp> +#include <boost/beast/experimental/core/ssl_stream.hpp> #endif namespace crow @@ -24,14 +25,14 @@ namespace crow using namespace boost; using tcp = asio::ip::tcp; -template <typename Handler, typename Adaptor = SocketAdaptor, +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::tuple<Middlewares...>* middlewares = nullptr, - typename Adaptor::context* adaptor_ctx = nullptr, + boost::asio::ssl::context* adaptor_ctx = nullptr, std::shared_ptr<boost::asio::io_context> io = std::make_shared<boost::asio::io_context>()) : ioService(std::move(io)), @@ -43,7 +44,7 @@ class Server Server(Handler* handler, const std::string& bindaddr, uint16_t port, std::tuple<Middlewares...>* middlewares = nullptr, - typename Adaptor::context* adaptor_ctx = nullptr, + boost::asio::ssl::context* adaptor_ctx = nullptr, std::shared_ptr<boost::asio::io_context> io = std::make_shared<boost::asio::io_context>()) : Server(handler, @@ -57,7 +58,7 @@ class Server Server(Handler* handler, int existing_socket, std::tuple<Middlewares...>* middlewares = nullptr, - typename Adaptor::context* adaptor_ctx = nullptr, + boost::asio::ssl::context* adaptor_ctx = nullptr, std::shared_ptr<boost::asio::io_context> io = std::make_shared<boost::asio::io_context>()) : Server(handler, @@ -163,21 +164,36 @@ class Server void doAccept() { - auto p = new Connection<Adaptor, Handler, Middlewares...>( - *ioService, handler, serverName, middlewares, getCachedDateStr, - timerQueue, adaptorCtx); - acceptor->async_accept( - p->socket(), [this, p](boost::system::error_code ec) { - if (!ec) - { - this->ioService->post([p] { p->start(); }); - } - else - { - delete p; - } - 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); + } + 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().lowest_layer(), + [this, p](boost::system::error_code ec) { + if (!ec) + { + this->ioService->post( + [p] { p->start(); }); + } + else + { + delete p; + } + doAccept(); + }); } private: @@ -200,8 +216,7 @@ class Server #ifdef BMCWEB_ENABLE_SSL bool useSsl{false}; - boost::asio::ssl::context sslContext{boost::asio::ssl::context::sslv23}; #endif - typename Adaptor::context* adaptorCtx; + boost::asio::ssl::context* adaptorCtx; }; // namespace crow } // namespace crow diff --git a/crow/include/crow/routing.h b/crow/include/crow/routing.h index 945f3611d8..7ba5171158 100644 --- a/crow/include/crow/routing.h +++ b/crow/include/crow/routing.h @@ -41,13 +41,16 @@ class BaseRule } virtual void handle(const Request&, Response&, const RoutingParams&) = 0; - virtual void handleUpgrade(const Request&, Response& res, SocketAdaptor&&) + virtual void handleUpgrade(const Request&, Response& res, + boost::asio::ip::tcp::socket&&) { res = Response(boost::beast::http::status::not_found); res.end(); } #ifdef BMCWEB_ENABLE_SSL - virtual void handleUpgrade(const Request&, Response& res, SSLAdaptor&&) + virtual void + handleUpgrade(const Request&, Response& res, + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&) { res = Response(boost::beast::http::status::not_found); res.end(); @@ -300,21 +303,23 @@ class WebSocketRule : public BaseRule } void handleUpgrade(const Request& req, Response&, - SocketAdaptor&& adaptor) override + boost::asio::ip::tcp::socket&& adaptor) override { - new crow::websocket::ConnectionImpl<SocketAdaptor>( + 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&, - SSLAdaptor&& adaptor) override - { - std::shared_ptr<crow::websocket::ConnectionImpl<SSLAdaptor>> - myConnection = - std::make_shared<crow::websocket::ConnectionImpl<SSLAdaptor>>( - req, std::move(adaptor), openHandler, messageHandler, - closeHandler, errorHandler); + 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 diff --git a/crow/include/crow/socket_adaptors.h b/crow/include/crow/socket_adaptors.h deleted file mode 100644 index 16ebe1f762..0000000000 --- a/crow/include/crow/socket_adaptors.h +++ /dev/null @@ -1,200 +0,0 @@ -#pragma once -#include <boost/asio.hpp> -#include <boost/lexical_cast.hpp> - -#include "crow/logging.h" - -#ifdef BMCWEB_ENABLE_SSL -#include <boost/asio/ssl.hpp> -#endif -namespace crow -{ -using namespace boost; -using tcp = asio::ip::tcp; - -struct SocketAdaptor -{ - using streamType = tcp::socket; - using secure = std::false_type; - using context = void; - SocketAdaptor(boost::asio::io_context& ioService, context* /*unused*/) : - socketCls(ioService) - { - } - - boost::asio::io_context& getIoService() - { - return socketCls.get_io_context(); - } - - tcp::socket& rawSocket() - { - return socketCls; - } - - tcp::socket& socket() - { - return socketCls; - } - - std::string remoteEndpoint() - { - boost::system::error_code ec; - tcp::endpoint ep = socketCls.remote_endpoint(ec); - if (ec) - { - return ""; - } - return boost::lexical_cast<std::string>(ep); - } - - bool isOpen() - { - return socketCls.is_open(); - } - - void close() - { - socketCls.close(); - } - - template <typename F> void start(F f) - { - boost::system::error_code ec; - f(ec); - } - - tcp::socket socketCls; -}; - -struct TestSocketAdaptor -{ - using secure = std::false_type; - using context = void; - TestSocketAdaptor(boost::asio::io_context& ioService, context* /*unused*/) : - socketCls(ioService) - { - } - - boost::asio::io_context& getIoService() - { - return socketCls.get_io_context(); - } - - tcp::socket& rawSocket() - { - return socketCls; - } - - tcp::socket& socket() - { - return socketCls; - } - - std::string remoteEndpoint() - { - return "Testhost"; - } - - bool isOpen() - { - return socketCls.is_open(); - } - - void close() - { - socketCls.close(); - } - - template <typename F> void start(F f) - { - f(boost::system::error_code()); - } - - tcp::socket socketCls; -}; - -#ifdef BMCWEB_ENABLE_SSL -struct SSLAdaptor -{ - using streamType = boost::asio::ssl::stream<tcp::socket>; - using secure = std::true_type; - using context = boost::asio::ssl::context; - using ssl_socket_t = boost::asio::ssl::stream<tcp::socket>; - SSLAdaptor(boost::asio::io_context& ioService, context* ctx) : - sslSocket(new ssl_socket_t(ioService, *ctx)) - { - } - - boost::asio::ssl::stream<tcp::socket>& socket() - { - return *sslSocket; - } - - tcp::socket::lowest_layer_type& rawSocket() - { - return sslSocket->lowest_layer(); - } - - std::string remoteEndpoint() - { - boost::system::error_code ec; - tcp::endpoint ep = rawSocket().remote_endpoint(ec); - if (ec) - { - return ""; - } - return boost::lexical_cast<std::string>(ep); - } - - bool isOpen() - { - /*TODO(ed) this is a bit of a cheat. - There are cases when running a websocket where sslSocket might have - std::move() called on it (to transfer ownership to - websocket::Connection) and be empty. This (and the check on close()) is - a cheat to do something sane in this scenario. the correct fix would - likely involve changing the http parser to return a specific code - meaning "has been upgraded" so that the doRead function knows not to try - to close the Connection which would fail, because the adapter is gone. - As is, doRead believes the parse failed, because isOpen now returns - False (which could also mean the client disconnected during parse) - UPdate: The parser does in fact have an "isUpgrade" method that is - intended for exactly this purpose. Todo is now to make doRead obey the - flag appropriately so this code can be changed back. - */ - if (sslSocket != nullptr) - { - return sslSocket->lowest_layer().is_open(); - } - return false; - } - - void close() - { - if (sslSocket == nullptr) - { - return; - } - boost::system::error_code ec; - - // Shut it down - this->sslSocket->lowest_layer().close(); - } - - boost::asio::io_context& getIoService() - { - return rawSocket().get_io_context(); - } - - template <typename F> void start(F f) - { - sslSocket->async_handshake( - boost::asio::ssl::stream_base::server, - [f](const boost::system::error_code& ec) { f(ec); }); - } - - std::unique_ptr<boost::asio::ssl::stream<tcp::socket>> sslSocket; -}; -#endif -} // namespace crow diff --git a/crow/include/crow/websocket.h b/crow/include/crow/websocket.h index c435d336b7..b545b5abcc 100644 --- a/crow/include/crow/websocket.h +++ b/crow/include/crow/websocket.h @@ -6,7 +6,6 @@ #include <functional> #include "crow/http_request.h" -#include "crow/socket_adaptors.h" #ifdef BMCWEB_ENABLE_SSL #include <boost/beast/websocket/ssl.hpp> @@ -27,7 +26,7 @@ struct Connection : std::enable_shared_from_this<Connection> virtual void sendText(const boost::beast::string_view msg) = 0; virtual void sendText(std::string&& msg) = 0; virtual void close(const boost::beast::string_view msg = "quit") = 0; - virtual boost::asio::io_context& getIoService() = 0; + virtual boost::asio::io_context& get_io_context() = 0; virtual ~Connection() = default; void userdata(void* u) @@ -49,15 +48,15 @@ template <typename Adaptor> class ConnectionImpl : public Connection { public: ConnectionImpl( - const crow::Request& req, Adaptor&& adaptorIn, + const crow::Request& req, 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) : - adaptor(std::move(adaptorIn)), - inString(), inBuffer(inString, 4096), ws(adaptor.socket()), - Connection(req), openHandler(std::move(open_handler)), + inString(), + inBuffer(inString, 4096), ws(std::move(adaptorIn)), Connection(req), + openHandler(std::move(open_handler)), messageHandler(std::move(message_handler)), closeHandler(std::move(close_handler)), errorHandler(std::move(error_handler)) @@ -65,9 +64,9 @@ template <typename Adaptor> class ConnectionImpl : public Connection BMCWEB_LOG_DEBUG << "Creating new connection " << this; } - boost::asio::io_context& getIoService() override + boost::asio::io_context& get_io_context() override { - return adaptor.getIoService(); + return ws.get_executor().context(); } void start() @@ -131,12 +130,15 @@ template <typename Adaptor> class ConnectionImpl : public Connection ws.async_close( boost::beast::websocket::close_code::normal, [this, 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; } - adaptor.close(); }); } @@ -217,11 +219,7 @@ template <typename Adaptor> class ConnectionImpl : public Connection } private: - Adaptor adaptor; - - boost::beast::websocket::stream< - std::add_lvalue_reference_t<typename Adaptor::streamType>> - ws; + boost::beast::websocket::stream<Adaptor> ws; std::string inString; boost::asio::dynamic_string_buffer<std::string::value_type, diff --git a/include/obmc_console.hpp b/include/obmc_console.hpp index 436f6c6cf1..5797613a11 100644 --- a/include/obmc_console.hpp +++ b/include/obmc_console.hpp @@ -116,7 +116,7 @@ void requestRoutes(CrowApp& app) host_socket = std::make_unique< boost::asio::local::stream_protocol::socket>( - conn.getIoService()); + conn.get_io_context()); host_socket->async_connect(ep, connectHandler); } }) diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp index 3b2aae532b..f746d434e3 100644 --- a/redfish-core/lib/service_root.hpp +++ b/redfish-core/lib/service_root.hpp @@ -41,7 +41,7 @@ class ServiceRoot : public Node const std::vector<std::string>& params) override { res.jsonValue["@odata.type"] = "#ServiceRoot.v1_1_1.ServiceRoot"; - res.jsonValue["@odata.id"] = "/redfish/v1/"; + res.jsonValue["@odata.id"] = "/redfish/v1"; res.jsonValue["@odata.context"] = "/redfish/v1/$metadata#ServiceRoot.ServiceRoot"; res.jsonValue["Id"] = "RootService"; @@ -57,6 +57,7 @@ class ServiceRoot : public Node res.jsonValue["Managers"] = {{"@odata.id", "/redfish/v1/Managers"}}; res.jsonValue["SessionService"] = { {"@odata.id", "/redfish/v1/SessionService"}}; + res.jsonValue["Managers"] = {{"@odata.id", "/redfish/v1/Managers"}}; res.jsonValue["Systems"] = {{"@odata.id", "/redfish/v1/Systems"}}; res.jsonValue["Registries"] = {{"@odata.id", "/redfish/v1/Registries"}}; |