Break out websockets
When running clang build analyzer it shows that one of the largest
templates in the Redfish compile unit (which is also the slowest compile
unit) is actually the beast websocket instantiations, taking about 6
seconds to compile on their own.
Luckily, the websocket layer is already split into Websocket and
WebsocketImpl classes that we inherited from crow. Unfortunately, crow
puts these two classes into the same file. So.
1. Move the WebSocketImpl class into its own header.
2. Move the websocket() upgrade routes in the websocket routing rule
into their own compile unit to take advantage of only needing
WebsocketImpl in this compile unit.
Tested: Drops build time by several seconds depending on what other
level of optimizations are present (1:15 -> 1:00)
[1] https://github.com/aras-p/ClangBuildAnalyzer
Change-Id: Ia0445eae4a793bb4ccb28136f30d2a05662c529c
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/http/routing/websocketrule.cpp b/http/routing/websocketrule.cpp
new file mode 100644
index 0000000..898a1d0
--- /dev/null
+++ b/http/routing/websocketrule.cpp
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+
+#include "websocketrule.hpp"
+
+#include "async_resp.hpp"
+#include "http_request.hpp"
+#include "logging.hpp"
+#include "websocket_impl.hpp"
+
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/ssl/stream.hpp>
+
+#include <memory>
+#include <utility>
+
+namespace crow
+{
+void WebSocketRule::handleUpgrade(
+ const Request& req, const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/,
+ boost::asio::ip::tcp::socket&& adaptor)
+{
+ BMCWEB_LOG_DEBUG("Websocket handles upgrade");
+ std::shared_ptr<
+ crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>
+ myConnection = std::make_shared<
+ crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>(
+ req.url(), req.session, std::move(adaptor), openHandler,
+ messageHandler, messageExHandler, closeHandler, errorHandler);
+ myConnection->start(req);
+}
+
+void WebSocketRule::handleUpgrade(
+ const Request& req, const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/,
+ boost::asio::ssl::stream<boost::asio::ip::tcp::socket>&& adaptor)
+{
+ BMCWEB_LOG_DEBUG("Websocket handles upgrade");
+ std::shared_ptr<crow::websocket::ConnectionImpl<
+ boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>>
+ myConnection = std::make_shared<crow::websocket::ConnectionImpl<
+ boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>>(
+ req.url(), req.session, std::move(adaptor), openHandler,
+ messageHandler, messageExHandler, closeHandler, errorHandler);
+ myConnection->start(req);
+}
+} // namespace crow
diff --git a/http/routing/websocketrule.hpp b/http/routing/websocketrule.hpp
index 0341022..56bc6ca 100644
--- a/http/routing/websocketrule.hpp
+++ b/http/routing/websocketrule.hpp
@@ -16,7 +16,6 @@
#include <memory>
#include <string>
#include <string_view>
-#include <utility>
#include <vector>
namespace crow
@@ -47,32 +46,12 @@
void handleUpgrade(const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/,
- boost::asio::ip::tcp::socket&& adaptor) override
- {
- BMCWEB_LOG_DEBUG("Websocket handles upgrade");
- std::shared_ptr<
- crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>
- myConnection = std::make_shared<
- crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>(
- req.url(), req.session, std::move(adaptor), openHandler,
- messageHandler, messageExHandler, closeHandler, errorHandler);
- myConnection->start(req);
- }
+ boost::asio::ip::tcp::socket&& adaptor) override;
void handleUpgrade(const Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/,
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>&&
- adaptor) override
- {
- BMCWEB_LOG_DEBUG("Websocket handles upgrade");
- std::shared_ptr<crow::websocket::ConnectionImpl<
- boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>>
- myConnection = std::make_shared<crow::websocket::ConnectionImpl<
- boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>>(
- req.url(), req.session, std::move(adaptor), openHandler,
- messageHandler, messageExHandler, closeHandler, errorHandler);
- myConnection->start(req);
- }
+ adaptor) override;
template <typename Func>
self_t& onopen(Func f)
diff --git a/http/websocket.hpp b/http/websocket.hpp
index e55725a..78d1eb4 100644
--- a/http/websocket.hpp
+++ b/http/websocket.hpp
@@ -1,39 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
#pragma once
-#include "bmcweb_config.h"
-
-#include "boost_formatters.hpp"
-#include "http_body.hpp"
-#include "http_request.hpp"
-#include "logging.hpp"
-#include "ossl_random.hpp"
-#include "sessions.hpp"
-
-#include <boost/asio/buffer.hpp>
-#include <boost/asio/error.hpp>
-#include <boost/asio/ssl/error.hpp>
-#include <boost/beast/core/error.hpp>
-#include <boost/beast/core/multi_buffer.hpp>
-#include <boost/beast/core/role.hpp>
-#include <boost/beast/http/field.hpp>
-#include <boost/beast/http/message.hpp>
-#include <boost/beast/http/status.hpp>
-#include <boost/beast/websocket/error.hpp>
-#include <boost/beast/websocket/rfc6455.hpp>
-#include <boost/beast/websocket/stream.hpp>
-#include <boost/beast/websocket/stream_base.hpp>
#include <boost/url/url_view.hpp>
-// NOLINTNEXTLINE(misc-include-cleaner)
-#include <boost/beast/websocket/ssl.hpp>
-
-#include <cstddef>
#include <functional>
#include <memory>
-#include <string>
#include <string_view>
-#include <utility>
namespace crow
{
@@ -66,322 +38,5 @@
virtual ~Connection() = default;
virtual boost::urls::url_view url() = 0;
};
-
-template <typename Adaptor>
-class ConnectionImpl : public Connection
-{
- using self_t = ConnectionImpl<Adaptor>;
-
- public:
- ConnectionImpl(
- const boost::urls::url_view& urlViewIn,
- const std::shared_ptr<persistent_data::UserSession>& sessionIn,
- Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
- std::function<void(Connection&, const std::string&, bool)>
- messageHandlerIn,
- std::function<void(crow::websocket::Connection&, std::string_view,
- crow::websocket::MessageType type,
- std::function<void()>&& whenComplete)>
- messageExHandlerIn,
- std::function<void(Connection&, const std::string&)> closeHandlerIn,
- std::function<void(Connection&)> errorHandlerIn) :
- uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
- openHandler(std::move(openHandlerIn)),
- messageHandler(std::move(messageHandlerIn)),
- messageExHandler(std::move(messageExHandlerIn)),
- closeHandler(std::move(closeHandlerIn)),
- errorHandler(std::move(errorHandlerIn)), session(sessionIn)
- {
- /* Turn on the timeouts on websocket stream to server role */
- ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
- boost::beast::role_type::server));
- BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
- }
-
- void start(const crow::Request& req)
- {
- BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
-
- using bf = boost::beast::http::field;
- std::string protocolHeader{
- req.getHeaderValue(bf::sec_websocket_protocol)};
-
- ws.set_option(boost::beast::websocket::stream_base::decorator(
- [session{session},
- protocolHeader](boost::beast::websocket::response_type& m) {
- if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
- {
- if (session != nullptr)
- {
- // use protocol for csrf checking
- if (session->cookieAuth &&
- !bmcweb::constantTimeStringCompare(
- protocolHeader, session->csrfToken))
- {
- BMCWEB_LOG_ERROR("Websocket CSRF error");
- m.result(boost::beast::http::status::unauthorized);
- return;
- }
- }
- }
- if (!protocolHeader.empty())
- {
- m.insert(bf::sec_websocket_protocol, protocolHeader);
- }
-
- 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");
- }));
-
- // Make a pointer to keep the req alive while we accept it.
- using Body = boost::beast::http::request<bmcweb::HttpBody>;
- std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
- Body* ptr = mobile.get();
- // Perform the websocket upgrade
- ws.async_accept(*ptr,
- std::bind_front(&self_t::acceptDone, this,
- shared_from_this(), std::move(mobile)));
- }
-
- void sendBinary(std::string_view msg) override
- {
- ws.binary(true);
- outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
- boost::asio::buffer(msg)));
- doWrite();
- }
-
- void sendEx(MessageType type, std::string_view msg,
- std::function<void()>&& onDone) override
- {
- if (doingWrite)
- {
- BMCWEB_LOG_CRITICAL(
- "Cannot mix sendEx usage with sendBinary or sendText");
- onDone();
- return;
- }
- ws.binary(type == MessageType::Binary);
-
- ws.async_write(boost::asio::buffer(msg),
- [weak(weak_from_this()), onDone{std::move(onDone)}](
- const boost::beast::error_code& ec, size_t) {
- std::shared_ptr<Connection> self = weak.lock();
- if (!self)
- {
- BMCWEB_LOG_ERROR("Connection went away");
- return;
- }
-
- // Call the done handler regardless of whether we
- // errored, but before we close things out
- onDone();
-
- if (ec)
- {
- BMCWEB_LOG_ERROR("Error in ws.async_write {}",
- ec);
- self->close("write error");
- }
- });
- }
-
- void sendText(std::string_view msg) override
- {
- ws.text(true);
- outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
- boost::asio::buffer(msg)));
- doWrite();
- }
-
- void close(std::string_view msg) override
- {
- ws.async_close(
- {boost::beast::websocket::close_code::normal, msg},
- [self(shared_from_this())](const boost::system::error_code& ec) {
- if (ec == boost::asio::error::operation_aborted)
- {
- return;
- }
- if (ec)
- {
- BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
- return;
- }
- });
- }
-
- boost::urls::url_view url() override
- {
- return uri;
- }
-
- void acceptDone(const std::shared_ptr<Connection>& /*self*/,
- const std::unique_ptr<
- boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
- const boost::system::error_code& ec)
- {
- if (ec)
- {
- BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
- return;
- }
- BMCWEB_LOG_DEBUG("Websocket accepted connection");
-
- if (openHandler)
- {
- openHandler(*this);
- }
- doRead();
- }
-
- void deferRead() override
- {
- readingDefered = true;
-
- // If we're not actively reading, we need to take ownership of
- // ourselves for a small portion of time, do that, and clear when we
- // resume.
- selfOwned = shared_from_this();
- }
-
- void resumeRead() override
- {
- readingDefered = false;
- doRead();
-
- // No longer need to keep ourselves alive now that read is active.
- selfOwned.reset();
- }
-
- void doRead()
- {
- if (readingDefered)
- {
- return;
- }
- ws.async_read(inBuffer, [this, self(shared_from_this())](
- const boost::beast::error_code& ec,
- size_t bytesRead) {
- if (ec)
- {
- if (ec != boost::beast::websocket::error::closed &&
- ec != boost::asio::error::eof &&
- ec != boost::asio::ssl::error::stream_truncated)
- {
- BMCWEB_LOG_ERROR("doRead error {}", ec);
- }
- if (closeHandler)
- {
- std::string reason{ws.reason().reason.c_str()};
- closeHandler(*this, reason);
- }
- return;
- }
-
- handleMessage(bytesRead);
- });
- }
- 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.size() == 0)
- {
- // Done for now
- return;
- }
- doingWrite = true;
- ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
- const boost::beast::error_code& ec,
- size_t bytesSent) {
- doingWrite = false;
- outBuffer.consume(bytesSent);
- 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:
- void handleMessage(size_t bytesRead)
- {
- if (messageExHandler)
- {
- // Note, because of the interactions with the read buffers,
- // this message handler overrides the normal message handler
- messageExHandler(*this, inString, MessageType::Binary,
- [this, self(shared_from_this()), bytesRead]() {
- if (self == nullptr)
- {
- return;
- }
-
- inBuffer.consume(bytesRead);
- inString.clear();
-
- doRead();
- });
- return;
- }
-
- if (messageHandler)
- {
- messageHandler(*this, inString, ws.got_text());
- }
- inBuffer.consume(bytesRead);
- inString.clear();
- doRead();
- }
-
- boost::urls::url uri;
-
- boost::beast::websocket::stream<Adaptor, false> ws;
-
- bool readingDefered = false;
- std::string inString;
- boost::asio::dynamic_string_buffer<std::string::value_type,
- std::string::traits_type,
- std::string::allocator_type>
- inBuffer;
-
- boost::beast::multi_buffer outBuffer;
- bool doingWrite = false;
-
- std::function<void(Connection&)> openHandler;
- std::function<void(Connection&, const std::string&, bool)> messageHandler;
- std::function<void(crow::websocket::Connection&, std::string_view,
- crow::websocket::MessageType type,
- std::function<void()>&& whenComplete)>
- messageExHandler;
- std::function<void(Connection&, const std::string&)> closeHandler;
- std::function<void(Connection&)> errorHandler;
- std::shared_ptr<persistent_data::UserSession> session;
-
- std::shared_ptr<Connection> selfOwned;
-};
} // namespace websocket
} // namespace crow
diff --git a/http/websocket_impl.hpp b/http/websocket_impl.hpp
new file mode 100644
index 0000000..1812f0f
--- /dev/null
+++ b/http/websocket_impl.hpp
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+#include "bmcweb_config.h"
+
+#include "boost_formatters.hpp"
+#include "http_body.hpp"
+#include "http_request.hpp"
+#include "logging.hpp"
+#include "ossl_random.hpp"
+#include "sessions.hpp"
+#include "websocket.hpp"
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/ssl/error.hpp>
+#include <boost/beast/core/error.hpp>
+#include <boost/beast/core/multi_buffer.hpp>
+#include <boost/beast/core/role.hpp>
+#include <boost/beast/http/field.hpp>
+#include <boost/beast/http/message.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/beast/websocket/error.hpp>
+#include <boost/beast/websocket/rfc6455.hpp>
+#include <boost/beast/websocket/stream.hpp>
+#include <boost/beast/websocket/stream_base.hpp>
+#include <boost/url/url_view.hpp>
+
+// NOLINTNEXTLINE(misc-include-cleaner)
+#include <boost/beast/websocket/ssl.hpp>
+
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace crow
+{
+namespace websocket
+{
+
+template <typename Adaptor>
+class ConnectionImpl : public Connection
+{
+ using self_t = ConnectionImpl<Adaptor>;
+
+ public:
+ ConnectionImpl(
+ const boost::urls::url_view& urlViewIn,
+ const std::shared_ptr<persistent_data::UserSession>& sessionIn,
+ Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
+ std::function<void(Connection&, const std::string&, bool)>
+ messageHandlerIn,
+ std::function<void(crow::websocket::Connection&, std::string_view,
+ crow::websocket::MessageType type,
+ std::function<void()>&& whenComplete)>
+ messageExHandlerIn,
+ std::function<void(Connection&, const std::string&)> closeHandlerIn,
+ std::function<void(Connection&)> errorHandlerIn) :
+ uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
+ openHandler(std::move(openHandlerIn)),
+ messageHandler(std::move(messageHandlerIn)),
+ messageExHandler(std::move(messageExHandlerIn)),
+ closeHandler(std::move(closeHandlerIn)),
+ errorHandler(std::move(errorHandlerIn)), session(sessionIn)
+ {
+ /* Turn on the timeouts on websocket stream to server role */
+ ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
+ boost::beast::role_type::server));
+ BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
+ }
+
+ void start(const crow::Request& req)
+ {
+ BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
+
+ using bf = boost::beast::http::field;
+ std::string protocolHeader{
+ req.getHeaderValue(bf::sec_websocket_protocol)};
+
+ ws.set_option(boost::beast::websocket::stream_base::decorator(
+ [session{session},
+ protocolHeader](boost::beast::websocket::response_type& m) {
+ if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
+ {
+ if (session != nullptr)
+ {
+ // use protocol for csrf checking
+ if (session->cookieAuth &&
+ !bmcweb::constantTimeStringCompare(
+ protocolHeader, session->csrfToken))
+ {
+ BMCWEB_LOG_ERROR("Websocket CSRF error");
+ m.result(boost::beast::http::status::unauthorized);
+ return;
+ }
+ }
+ }
+ if (!protocolHeader.empty())
+ {
+ m.insert(bf::sec_websocket_protocol, protocolHeader);
+ }
+
+ 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");
+ }));
+
+ // Make a pointer to keep the req alive while we accept it.
+ using Body = boost::beast::http::request<bmcweb::HttpBody>;
+ std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
+ Body* ptr = mobile.get();
+ // Perform the websocket upgrade
+ ws.async_accept(*ptr,
+ std::bind_front(&self_t::acceptDone, this,
+ shared_from_this(), std::move(mobile)));
+ }
+
+ void sendBinary(std::string_view msg) override
+ {
+ ws.binary(true);
+ outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
+ boost::asio::buffer(msg)));
+ doWrite();
+ }
+
+ void sendEx(MessageType type, std::string_view msg,
+ std::function<void()>&& onDone) override
+ {
+ if (doingWrite)
+ {
+ BMCWEB_LOG_CRITICAL(
+ "Cannot mix sendEx usage with sendBinary or sendText");
+ onDone();
+ return;
+ }
+ ws.binary(type == MessageType::Binary);
+
+ ws.async_write(boost::asio::buffer(msg),
+ [weak(weak_from_this()), onDone{std::move(onDone)}](
+ const boost::beast::error_code& ec, size_t) {
+ std::shared_ptr<Connection> self = weak.lock();
+ if (!self)
+ {
+ BMCWEB_LOG_ERROR("Connection went away");
+ return;
+ }
+
+ // Call the done handler regardless of whether we
+ // errored, but before we close things out
+ onDone();
+
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("Error in ws.async_write {}",
+ ec);
+ self->close("write error");
+ }
+ });
+ }
+
+ void sendText(std::string_view msg) override
+ {
+ ws.text(true);
+ outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
+ boost::asio::buffer(msg)));
+ doWrite();
+ }
+
+ void close(std::string_view msg) override
+ {
+ ws.async_close(
+ {boost::beast::websocket::close_code::normal, msg},
+ [self(shared_from_this())](const boost::system::error_code& ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ return;
+ }
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
+ return;
+ }
+ });
+ }
+
+ boost::urls::url_view url() override
+ {
+ return uri;
+ }
+
+ void acceptDone(const std::shared_ptr<Connection>& /*self*/,
+ const std::unique_ptr<
+ boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
+ const boost::system::error_code& ec)
+ {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
+ return;
+ }
+ BMCWEB_LOG_DEBUG("Websocket accepted connection");
+
+ if (openHandler)
+ {
+ openHandler(*this);
+ }
+ doRead();
+ }
+
+ void deferRead() override
+ {
+ readingDefered = true;
+
+ // If we're not actively reading, we need to take ownership of
+ // ourselves for a small portion of time, do that, and clear when we
+ // resume.
+ selfOwned = shared_from_this();
+ }
+
+ void resumeRead() override
+ {
+ readingDefered = false;
+ doRead();
+
+ // No longer need to keep ourselves alive now that read is active.
+ selfOwned.reset();
+ }
+
+ void doRead()
+ {
+ if (readingDefered)
+ {
+ return;
+ }
+ ws.async_read(inBuffer, [this, self(shared_from_this())](
+ const boost::beast::error_code& ec,
+ size_t bytesRead) {
+ if (ec)
+ {
+ if (ec != boost::beast::websocket::error::closed &&
+ ec != boost::asio::error::eof &&
+ ec != boost::asio::ssl::error::stream_truncated)
+ {
+ BMCWEB_LOG_ERROR("doRead error {}", ec);
+ }
+ if (closeHandler)
+ {
+ std::string reason{ws.reason().reason.c_str()};
+ closeHandler(*this, reason);
+ }
+ return;
+ }
+
+ handleMessage(bytesRead);
+ });
+ }
+ 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.size() == 0)
+ {
+ // Done for now
+ return;
+ }
+ doingWrite = true;
+ ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
+ const boost::beast::error_code& ec,
+ size_t bytesSent) {
+ doingWrite = false;
+ outBuffer.consume(bytesSent);
+ 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:
+ void handleMessage(size_t bytesRead)
+ {
+ if (messageExHandler)
+ {
+ // Note, because of the interactions with the read buffers,
+ // this message handler overrides the normal message handler
+ messageExHandler(*this, inString, MessageType::Binary,
+ [this, self(shared_from_this()), bytesRead]() {
+ if (self == nullptr)
+ {
+ return;
+ }
+
+ inBuffer.consume(bytesRead);
+ inString.clear();
+
+ doRead();
+ });
+ return;
+ }
+
+ if (messageHandler)
+ {
+ messageHandler(*this, inString, ws.got_text());
+ }
+ inBuffer.consume(bytesRead);
+ inString.clear();
+ doRead();
+ }
+
+ boost::urls::url uri;
+
+ boost::beast::websocket::stream<Adaptor, false> ws;
+
+ bool readingDefered = false;
+ std::string inString;
+ boost::asio::dynamic_string_buffer<std::string::value_type,
+ std::string::traits_type,
+ std::string::allocator_type>
+ inBuffer;
+
+ boost::beast::multi_buffer outBuffer;
+ bool doingWrite = false;
+
+ std::function<void(Connection&)> openHandler;
+ std::function<void(Connection&, const std::string&, bool)> messageHandler;
+ std::function<void(crow::websocket::Connection&, std::string_view,
+ crow::websocket::MessageType type,
+ std::function<void()>&& whenComplete)>
+ messageExHandler;
+ std::function<void(Connection&, const std::string&)> closeHandler;
+ std::function<void(Connection&)> errorHandler;
+ std::shared_ptr<persistent_data::UserSession> session;
+
+ std::shared_ptr<Connection> selfOwned;
+};
+} // namespace websocket
+} // namespace crow
diff --git a/meson.build b/meson.build
index a2c397e..440d9ee 100644
--- a/meson.build
+++ b/meson.build
@@ -356,6 +356,7 @@
srcfiles_bmcweb = files(
'http/mutual_tls.cpp',
+ 'http/routing/websocketrule.cpp',
'redfish-core/src/dbus_log_watcher.cpp',
'redfish-core/src/error_message_utils.cpp',
'redfish-core/src/error_messages.cpp',