summaryrefslogtreecommitdiff
path: root/indra/llcorehttp/llwebsocketmgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcorehttp/llwebsocketmgr.cpp')
-rw-r--r--indra/llcorehttp/llwebsocketmgr.cpp710
1 files changed, 710 insertions, 0 deletions
diff --git a/indra/llcorehttp/llwebsocketmgr.cpp b/indra/llcorehttp/llwebsocketmgr.cpp
new file mode 100644
index 0000000000..be1ab54efd
--- /dev/null
+++ b/indra/llcorehttp/llwebsocketmgr.cpp
@@ -0,0 +1,710 @@
+/**
+ * @file llwebsocketmgr.cpp
+ * @brief WebSocket manager singleton implementation
+ *
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2025, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llwebsocketmgr.h"
+#include "llerror.h"
+#include "llsdserialize.h"
+#include "llhost.h"
+#include "llsdjson.h"
+
+#include <websocketpp/config/boost_config.hpp>
+#include <websocketpp/config/asio_no_tls.hpp>
+#include <websocketpp/client.hpp>
+#include <websocketpp/server.hpp>
+
+#include <thread>
+#include <atomic>
+#include <chrono>
+
+//------------------------------------------------------------------------
+namespace
+{
+ using Server_t = websocketpp::server<websocketpp::config::asio>;
+ using Client_t = websocketpp::client<websocketpp::config::asio>;
+ using Connection_t = websocketpp::connection<websocketpp::config::asio>;
+}
+
+//------------------------------------------------------------------------
+
+//------------------------------------------------------------------------
+// LLWebsocketMgr Implementation
+//
+
+void LLWebsocketMgr::initSingleton()
+{ }
+
+void LLWebsocketMgr::cleanupSingleton()
+{
+ stopAllServers();
+}
+
+void LLWebsocketMgr::update()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ std::vector<WSServer::ptr_t> stops;
+
+ for (auto &[name, server] : mServers)
+ {
+ if (server && server->isRunning())
+ {
+ if (!server->update())
+ {
+ stops.push_back(server);
+ }
+ }
+ }
+
+ for (const auto& server : stops)
+ {
+ if (server)
+ {
+ LL_DEBUGS("WebSocket") << "Stopping server: " << server->mServerName << LL_ENDL;
+ removeServer(server->mServerName);
+ }
+ }
+}
+
+
+LLWebsocketMgr::WSServer::ptr_t LLWebsocketMgr::findServerByName(const std::string &name) const
+{
+ auto it = mServers.find(std::string(name));
+ if (it != mServers.end())
+ {
+ return it->second;
+ }
+ return nullptr;
+}
+
+bool LLWebsocketMgr::addServer(const LLWebsocketMgr::WSServer::ptr_t& server)
+{
+ if (!server)
+ {
+ LL_WARNS("WebSocket") << "Attempted to add a null server" << LL_ENDL;
+ return false;
+ }
+
+ auto it = mServers.find(server->mServerName);
+ if (it != mServers.end())
+ {
+ LL_WARNS("WebSocket") << "Server with name " << server->mServerName << " already exists" << LL_ENDL;
+ return false;
+ }
+ mServers[server->mServerName] = server;
+ LL_INFOS("WebSocket") << "Added WebSocket server: " << server->mServerName << LL_ENDL;
+ return true;
+}
+
+bool LLWebsocketMgr::removeServer(const std::string& name)
+{
+ auto it = mServers.find(name);
+ if (it == mServers.end())
+ {
+ LL_WARNS("WebSocket") << "No server found with name " << name << " to remove" << LL_ENDL;
+ return false;
+ }
+ if (it->second && it->second->isRunning())
+ it->second->stop();
+ mServers.erase(it);
+ LL_INFOS("WebSocket") << "Removed WebSocket server: " << name << LL_ENDL;
+ return true;
+}
+
+
+bool LLWebsocketMgr::startServer(const std::string &name) const
+{
+ LLWebsocketMgr::WSServer::ptr_t server = findServerByName(name);
+ if (!server)
+ {
+ LL_WARNS("WebSocket") << "No server found with name " << name << " to start" << LL_ENDL;
+ return false;
+ }
+ if (server->isRunning())
+ {
+ LL_WARNS("WebSocket") << "Server " << name << " is already running" << LL_ENDL;
+ return false;
+ }
+ return server->start();
+}
+
+void LLWebsocketMgr::stopServer(const std::string& name) const
+{
+ LLWebsocketMgr::WSServer::ptr_t server = findServerByName(name);
+ if (!server)
+ {
+ LL_WARNS("WebSocket") << "No server found with name " << name << " to stop" << LL_ENDL;
+ return;
+ }
+ if (!server->isRunning())
+ {
+ LL_WARNS("WebSocket") << "Server " << name << " is not running" << LL_ENDL;
+ return;
+ }
+ server->stop();
+}
+
+void LLWebsocketMgr::stopAllServers()
+{
+ for (auto &[name, server] : mServers)
+ {
+ if (server && server->isRunning())
+ {
+ LL_INFOS("WebSocket") << "Stopping server: " << name << LL_ENDL;
+ server->stop();
+ }
+ }
+
+ mServers.clear();
+}
+
+//------------------------------------------------------------------------
+struct Server_impl
+{
+ Server_impl(LLWebsocketMgr::WSServer *owner, U16 port, bool local_only) :
+ mOwner(owner),
+ mPort(port),
+ mLocalOnly(local_only)
+ {
+
+ mServer.set_open_handler([this](websocketpp::connection_hdl hdl) { this->onOpen(hdl); });
+ mServer.set_close_handler([this](websocketpp::connection_hdl hdl) { this->onClose(hdl); });
+ mServer.set_message_handler([this](websocketpp::connection_hdl hdl, Server_t::message_ptr msg) { this->onMessage(hdl, msg); });
+ }
+
+ ~Server_impl() = default;
+
+ /**
+ * @brief Initialize the websocketpp server and configure listening
+ *
+ * Performs the initial setup of the websocketpp server by calling init_asio()
+ * to initialize the ASIO networking layer, then configures the server to listen
+ * on the specified port. The binding behavior depends on the mLocalOnly flag:
+ * - If mLocalOnly is true: binds to "127.0.0.1" (localhost only)
+ * - If mLocalOnly is false: binds to all available network interfaces
+ */
+ void init()
+ {
+ mServer.init_asio();
+ try {
+ if (mLocalOnly)
+ {
+ std::stringstream port_str;
+ port_str << mPort;
+ mServer.listen("127.0.0.1", port_str.str());
+ }
+ else
+ {
+ mServer.listen(mPort);
+ }
+ }
+ catch (const websocketpp::exception& e)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server listen exception: " << e.what() << LL_ENDL;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server listen std::exception: " << e.what() << LL_ENDL;
+ }
+ catch (...)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server listen unknown exception" << LL_ENDL;
+ }
+ }
+
+ /**
+ * @brief Start the websocket server and begin accepting connections
+ * @return true if server started successfully, false on error
+ */
+ bool start()
+ {
+ //if (!mServer.stopped())
+ //{
+ // LL_WARNS("WebSocket") << "WebSocket server is already running" << LL_ENDL;
+ // return false;
+ //}
+
+ try
+ {
+ LL_INFOS("WebSocket") << "Starting WebSocket server on port " << mPort
+ << (mLocalOnly ? " (localhost only)" : " (all interfaces)") << LL_ENDL;
+ mServer.start_accept();
+
+ // Run controlled event loop with periodic stop flag checking
+ while (!mOwner->mShouldStop && !mServer.stopped())
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WEBSOCKET("ws server run_for");
+ // Process events for up to 100ms, then check the stop flag
+ std::chrono::milliseconds timeout(100);
+ std::size_t handlers_run = mServer.get_io_service().run_for(timeout);
+
+ // If no handlers were run and the server isn't stopped,
+ // reset the io_service for the next iteration
+ if (handlers_run == 0 && !mServer.stopped() && !mOwner->mShouldStop)
+ {
+ mServer.get_io_service().restart();
+ }
+ }
+
+ LL_INFOS("WebSocket") << "WebSocket server event loop exited cleanly" << LL_ENDL;
+ return true;
+ }
+ catch (const websocketpp::exception& e)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server exception: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server std::exception: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (...)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server unknown exception" << LL_ENDL;
+ return false;
+ }
+ }
+
+ void stop()
+ {
+ if (mServer.stopped())
+ {
+ return;
+ }
+ try
+ {
+ mServer.stop_listening();
+ mServer.stop();
+ }
+ catch (const std::exception&)
+ {
+ LL_WARNS("WebSocket") << "Error stopping WebSocket server" << LL_ENDL;
+ }
+ }
+
+ /**
+ * @brief Handle new connection establishment event
+ * @param hdl WebSocket connection handle from websocketpp
+ *
+ * Called automatically by the websocketpp library when a new client connection
+ * is successfully established. This method serves as a bridge between the
+ * low-level websocketpp callback and the high-level WSServer interface.
+ */
+ void onOpen(websocketpp::connection_hdl hdl) const
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ LL_ERRS_IF(!mOwner, "WebSocket") << "mOwner should never be null. If it is, something is very wrong!" << LL_ENDL;
+
+ mOwner->handleOpenConnection(hdl);
+ }
+
+ /**
+ * @brief Handle connection closure event
+ * @param hdl WebSocket connection handle from websocketpp
+ */
+ void onClose(websocketpp::connection_hdl hdl) const
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ LL_ERRS_IF(!mOwner, "WebSocket") << "mOwner should never be null" << LL_ENDL;
+ mOwner->handleCloseConnection(hdl);
+ }
+
+ /**
+ * @brief Handle incoming message from client
+ * @param hdl WebSocket connection handle identifying the sender
+ * @param msg Shared pointer to the message object containing payload and metadata
+ *
+ * Called automatically by the websocketpp library when a complete message is
+ * received from a client. This method validates the connection exists, extracts
+ * the message payload, and forwards it to the appropriate connection handler.
+ *
+ * Currently handles text messages only.
+ */
+ void onMessage(websocketpp::connection_hdl hdl, Server_t::message_ptr msg) const
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ LL_ERRS_IF(!mOwner, "WebSocket") << "mOwner should never be null" << LL_ENDL;
+ LLWebsocketMgr::WSConnection::ptr_t connection = mOwner->getConnection(hdl);
+ if (!connection)
+ {
+ LL_WARNS("WebSocket") << "Received message for unknown connection" << LL_ENDL;
+ return;
+ }
+
+ // TODO: check the FIN bit and handle fragmented messages if needed
+ // TODO: check terminal and close codes and handle connection closure if needed
+ mOwner->handleMessage(hdl, msg->get_payload());
+ }
+
+ //-------------------------------------------
+ Server_t mServer; ///< The underlying websocketpp server instance
+ LLWebsocketMgr::WSServer* mOwner{ nullptr }; ///< Back-reference to the owning WSServer instance (guaranteed non-null)
+ U16 mPort{ 0 }; ///< TCP port number the server listens on
+ bool mLocalOnly{ true }; ///< Whether to bind to localhost only (true) or all interfaces (false)
+};
+
+//------------------------------------------------------------------------
+LLWebsocketMgr::WSServer::WSServer(std::string_view name, U16 port, bool local_only):
+ mServerName(name),
+ mImpl(std::make_unique<Server_impl>(this, port, local_only))
+{
+ mImpl->init();
+
+ // Initialize the server with the given name and host
+ LL_INFOS("WebSocket") << "Creating WebSocket server: " << name <<
+ " listening " << (mImpl->mLocalOnly ? "locally" : "ON ALL INTERFACES") <<
+ " on port " << mImpl->mPort << LL_ENDL;
+}
+
+LLWebsocketMgr::WSServer::~WSServer()
+{
+ // Ensure the server is stopped before destruction
+ stop();
+}
+
+LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSServer::connectionFactory(LLWebsocketMgr::WSServer::ptr_t server,
+ LLWebsocketMgr::connection_h handle)
+{
+ return std::make_shared<LLWebsocketMgr::WSConnection>(server, handle);
+}
+
+bool LLWebsocketMgr::WSServer::start()
+{
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+
+ LLMutexLock lock(&mThreadMutex);
+
+ // Check if already running
+ if (isRunning())
+ {
+ LL_WARNS("WebSocket") << "Server " << mServerName << " is already running" << LL_ENDL;
+ return false;
+ }
+
+ // Reset the stop flag
+ mShouldStop = false;
+
+ // Start the server thread
+ mServerThread = std::thread([this]() {
+ LL_INFOS("WebSocket") << "WebSocket server thread starting for: " << mServerName << LL_ENDL;
+
+ // Run the controlled server loop that checks the stop flag
+ // Server_impl accesses mShouldStop through the mOwner pointer
+ bool success = mImpl->start();
+
+ if (!success)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server thread failed to start for: " << mServerName << LL_ENDL;
+ }
+
+ LL_INFOS("WebSocket") << "WebSocket server thread exiting for: " << mServerName << LL_ENDL;
+ });
+
+ onStarted();
+ LL_INFOS("WebSocket") << "Started WebSocket server thread: " << mServerName << LL_ENDL;
+ return true;
+}
+
+void LLWebsocketMgr::WSServer::stop()
+{
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+
+ {
+ LLMutexLock lock(&mThreadMutex);
+
+ // Check if already stopped
+ if (!isRunning())
+ {
+ return;
+ }
+
+ LL_INFOS("WebSocket") << "Stopping WebSocket server: " << mServerName << LL_ENDL;
+
+ mShouldStop = true;
+
+ // Send close frames to all connected clients before stopping the ASIO loop
+ closeAllConnections(1001, "Server shutting down");
+
+ // Stop the websocket server (this will cause the controlled run loop to exit)
+ mImpl->stop();
+ } // Release the lock here
+
+ if (mServerThread.joinable())
+ {
+ mServerThread.join();
+ LL_INFOS("WebSocket") << "WebSocket server thread joined for: " << mServerName << LL_ENDL;
+ }
+ onStopped();
+}
+
+bool LLWebsocketMgr::WSServer::isRunning() const
+{
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+
+ // Check both the thread state, websocket server state, and the stop flag
+ return mServerThread.joinable() && !mImpl->mServer.stopped() && !mShouldStop;
+}
+
+void LLWebsocketMgr::WSServer::broadcastMessage(const std::string& message)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+ LLMutexLock lock(&mConnectionMutex);
+ for (const auto& [handle, conn] : mConnections)
+ {
+ sendMessageTo(handle, message);
+ }
+}
+
+bool LLWebsocketMgr::WSServer::sendMessageTo(const connection_h& handle, const std::string& message)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+ websocketpp::lib::error_code ec;
+ mImpl->mServer.send(handle, message, websocketpp::frame::opcode::text, ec);
+ if (ec)
+ {
+ LL_WARNS("WebSocket") << mServerName << " failed to send message: " << ec.message() << LL_ENDL;
+ return false;
+ }
+ return true;
+}
+
+bool LLWebsocketMgr::WSServer::closeConnection(const connection_h& handle, U16 code, const std::string& reason)
+{
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+
+ try
+ {
+ websocketpp::lib::error_code ec;
+ mImpl->mServer.close(handle, code, reason, ec);
+ if (ec)
+ {
+ LL_WARNS("WebSocket") << mServerName << " failed to close connection: " << ec.message() << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS("WebSocket") << mServerName << " initiated close for connection with code "
+ << code << " and reason: " << reason << LL_ENDL;
+ return true;
+ }
+ catch (const websocketpp::exception& e)
+ {
+ LL_WARNS("WebSocket") << mServerName << " exception closing connection: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS("WebSocket") << mServerName << " std::exception closing connection: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (...)
+ {
+ LL_WARNS("WebSocket") << mServerName << " unknown exception closing connection" << LL_ENDL;
+ return false;
+ }
+}
+
+LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSServer::getConnection(const connection_h& handle)
+{
+ LLMutexLock lock(&mConnectionMutex);
+ auto it = mConnections.find(handle);
+ if (it != mConnections.end())
+ {
+ return it->second;
+ }
+ return nullptr;
+}
+
+LLWebsocketMgr::connection_state_t LLWebsocketMgr::WSServer::getConnectionState(const connection_h& handle) const
+{
+ websocketpp::lib::error_code ec;
+ auto con = mImpl->mServer.get_con_from_hdl(handle, ec);
+ if (ec)
+ {
+ LL_WARNS("WebSocket") << mServerName << " failed to get connection state: " << ec.message() << LL_ENDL;
+ websocketpp::session::state::value state = websocketpp::session::state::closed;
+ return connection_closed;
+ }
+ return static_cast<connection_state_t>(con->get_state());
+}
+
+
+void LLWebsocketMgr::WSServer::handleOpenConnection(const connection_h& handle)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ WSConnection::ptr_t connection;
+ size_t size(0);
+ {
+ LLMutexLock lock(&mConnectionMutex);
+ auto it = mConnections.find(handle);
+ if (it == mConnections.end())
+ {
+ connection = connectionFactory(shared_from_this(), handle);
+ if (!connection)
+ {
+ LL_WARNS("WebSocket") << "Failed to create connection for websocket server " << mServerName << LL_ENDL;
+ return;
+ }
+ mConnections[handle] = connection;
+ }
+ else
+ {
+ connection = it->second;
+ }
+
+ if (!connection)
+ {
+ LL_WARNS("WebSocket") << mServerName << " failed to create connection object" << LL_ENDL;
+ return;
+ }
+ // Removed redundant assignment to mConnections[handle]
+ size = mConnections.size();
+ }
+
+ onConnectionOpened(connection); // TODO: consider letting the server reject the connection here
+ connection->onOpen();
+ LL_INFOS("WebSocket") << mServerName << " opened new connection, total connections: " << size << LL_ENDL;
+}
+
+void LLWebsocketMgr::WSServer::handleCloseConnection(const connection_h& handle)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ size_t size(0);
+ WSConnection::ptr_t connection;
+ {
+ LLMutexLock lock(&mConnectionMutex);
+ auto it = mConnections.find(handle);
+ if (it != mConnections.end())
+ {
+ connection = it->second;
+ mConnections.erase(it);
+ }
+ size = mConnections.size();
+ }
+ if (connection)
+ {
+ connection->onClose();
+ onConnectionClosed(connection);
+ LL_INFOS("WebSocket") << mServerName << " closed connection, total connections: " << size << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("WebSocket") << mServerName << " attempted to close unknown connection" << LL_ENDL;
+ }
+}
+
+void LLWebsocketMgr::WSServer::handleMessage(const connection_h& handle, const std::string& message)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_WEBSOCKET;
+ WSConnection::ptr_t connection = getConnection(handle);
+ if (connection)
+ {
+ connection->onMessage(message);
+ }
+ else
+ {
+ LL_WARNS("WebSocket") << mServerName << " received message for unknown connection" << LL_ENDL;
+ }
+}
+
+//------------------------------------------------------------------------
+bool LLWebsocketMgr::WSConnection::sendMessage(const std::string& message) const
+{
+ if (mOwningServer.expired())
+ {
+ LL_WARNS("WebSocket") << "Attempted to send message on connection with null server reference" << LL_ENDL;
+ return false;
+ }
+ return mOwningServer.lock()->sendMessageTo(mConnectionHandle, message);
+}
+
+bool LLWebsocketMgr::WSConnection::sendMessage(const boost::json::value& json) const
+{
+ std::string message = boost::json::serialize(json);
+ return sendMessage(message);
+}
+
+bool LLWebsocketMgr::WSConnection::sendMessage(const LLSD& data) const
+{
+ return sendMessage(LlsdToJson(data));
+}
+
+void LLWebsocketMgr::WSConnection::closeConnection(U16 code, const std::string& reason)
+{
+ if (mOwningServer.expired())
+ {
+ LL_WARNS("WebSocket") << "Attempted to close connection with null server reference" << LL_ENDL;
+ return;
+ }
+
+ LL_INFOS("WebSocket") << "WSConnection closing connection with code " << code
+ << " and reason: " << (reason.empty() ? "(no reason)" : reason) << LL_ENDL;
+
+ if (!mOwningServer.lock()->closeConnection(mConnectionHandle, code, reason))
+ {
+ LL_WARNS("WebSocket") << "Failed to close connection through server" << LL_ENDL;
+ }
+}
+
+bool LLWebsocketMgr::WSConnection::isConnected() const
+{
+ if (mOwningServer.expired())
+ {
+ return false;
+ }
+
+ LLWebsocketMgr::WSServer::ptr_t server = mOwningServer.lock();
+ if (!server)
+ {
+ return false;
+ }
+ return server->getConnectionState(mConnectionHandle) == connection_open;
+}
+
+LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSConnection::getSelfPtr()
+{
+ auto server = mOwningServer.lock();
+ if (!server) return nullptr;
+ return server->getConnection(mConnectionHandle);
+}
+
+void LLWebsocketMgr::WSServer::closeAllConnections(U16 code, const std::string& reason)
+{
+ std::vector<connection_h> handles;
+ {
+ LLMutexLock lock(&mConnectionMutex);
+ for (const auto& [handle, conn] : mConnections)
+ {
+ handles.push_back(handle);
+ }
+ }
+ for (const auto& handle : handles)
+ {
+ closeConnection(handle, code, reason);
+ }
+}