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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
/**
* @file llwebsocketmgr.h
* @brief WebSocket manager singleton for managing WebSocket servers and connections
*
* $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$
*/
#pragma once
#include "llsingleton.h"
#include "llsd.h"
#include "lluuid.h"
#include "llhost.h"
#include "llmutex.h"
#include <memory>
#include <map>
#include <vector>
#include <functional>
#include <thread>
#include <atomic>
#include <boost/json.hpp>
#include <websocketpp/common/connection_hdl.hpp>
struct Server_impl;
/**
* @class LLWebsocketMgr
* @brief Singleton manager for WebSocket connections and servers
*
* This class provides a high-level interface for managing WebSocket connections
* and servers using websocketpp library. It handles both client and server
* connections, provides thread-safe operations, and integrates with the
* existing Linden Lab infrastructure.
*/
class LLWebsocketMgr: public LLSingleton<LLWebsocketMgr>
{
LLSINGLETON(LLWebsocketMgr) = default;
virtual ~LLWebsocketMgr() = default;
LOG_CLASS(LLWebsocketMgr);
public:
using connection_h = websocketpp::connection_hdl;
class WSServer;
enum connection_state_t
{ // must map to websocketpp::session::state
connection_connecting = 0,
connection_open = 1,
connection_closing = 2,
connection_closed = 3
};
class WSConnection
{
friend class LLWebsocketMgr;
public:
using ptr_t = std::shared_ptr<WSConnection>;
/**
* @brief Constructor for WSConnection
* @param server Shared pointer to the parent WSServer
* @param handle WebSocket connection handle from websocketpp
*/
WSConnection(const std::shared_ptr<WSServer> &server, const connection_h& handle):
mConnectionHandle(handle),
mOwningServer(server)
{}
virtual ~WSConnection() = default;
/**
* Override this method in derived classes to handle connection establishment.
* This is called after the WebSocket handshake is complete and the connection
* is ready to send/receive messages.
*/
virtual void onOpen() {}
/**
* Override this method in derived classes to handle connection closure.
* This is called when the connection has been terminated, either normally
* or due to an error condition.
*/
virtual void onClose() {}
/**
* @brief Called when a message is received
* @param message The received message as a string
*
* Override this method in derived classes to handle incoming messages.
* Currently only text messages are supported.
*/
virtual void onMessage(const std::string& message) {}
/**
* @brief Send a message to the connected client
* @param message The message string to send
* @return true if the message was queued successfully, false on error
*
* Sends a text message to the remote endpoint. The message is queued
* asynchronously and may not be sent immediately.
*/
bool sendMessage(const std::string& message) const;
bool sendMessage(const boost::json::value& json) const;
bool sendMessage(const LLSD& data) const;
/**
* @brief Close the WebSocket connection gracefully
* @param code Optional close code (default: normal closure)
* @param reason Optional reason string (default: empty)
*
* Initiates a graceful WebSocket close handshake. The connection will
* send a close frame with the specified code and reason, then wait for
* the remote endpoint to respond with its own close frame before
* actually closing the underlying TCP connection.
*
* Common close codes:
* - 1000: Normal closure (default)
* - 1001: Going away (server shutting down, page navigating away)
* - 1002: Protocol error
* - 1003: Unsupported data type
* - 1008: Policy violation
* - 1009: Message too big
*
* @note After calling this method, no further messages should be sent
* @note The onClose() callback will be invoked when the close handshake completes
*/
void closeConnection(U16 code = 1000, const std::string& reason = std::string());
bool isConnected() const;
protected:
/// Returns a shared_ptr to this connection, retrieved from the owning server.
/// Valid only while the connection is open and registered with the server.
ptr_t getSelfPtr();
connection_h mConnectionHandle;
std::weak_ptr<WSServer> mOwningServer; // Back-reference to the server this connection belongs to
};
/**
* @class WSServer
* @brief Base class for WebSocket servers with customizable connection handling
*
* WSServer provides a high-level abstraction over websocketpp servers, handling
* threading, connection management, and event dispatching. Derive from this class
* to create custom WebSocket servers with application-specific logic.
*
* ## Basic Usage
*
* @code
* class MyServer : public LLWebsocketMgr::WSServer
* {
* public:
* MyServer(const std::string& name, U16 port)
* : WSServer(name, port, false) // Listen on all interfaces
* {}
*
* void onConnectionOpened(const WSConnection::ptr_t& connection) override
* {
* LL_INFOS("MyServer") << "New client connected" << LL_ENDL;
* // Send welcome message
* connection->sendMessage("Welcome to the server!");
* }
*
* void onConnectionClosed(const WSConnection::ptr_t& connection) override
* {
* LL_INFOS("MyServer") << "Client disconnected" << LL_ENDL;
* }
*
* protected:
* // Use custom connection class
* WSConnection::ptr_t connectionFactory(WSServer::ptr_t server, connection_h handle) override
* {
* return std::make_shared<MyConnection>(server, handle);
* }
* };
* @endcode
*
* ## Connection Management
*
* The server automatically manages connection lifetimes and provides several ways
* to interact with connections:
*
* - `broadcastMessage()` - Send message to all connected clients
* - `sendMessageTo()` - Send message to specific connection
* - `closeConnection()` - Close specific connection with code/reason
* - `getConnection()` - Get connection object by handle
*
* ## Thread Safety
*
* All public methods are thread-safe and can be called from any thread. The server
* runs its own background thread for handling WebSocket events, while connection
* callbacks are also executed on this background thread.
*/
class WSServer: public std::enable_shared_from_this<WSServer>
{
friend struct Server_impl;
friend class WSConnection;
friend class LLWebsocketMgr;
public:
using ptr_t = std::shared_ptr<WSServer>;
WSServer(std::string_view name, U16 port, bool local_only = true);
virtual ~WSServer();
virtual void onStarted() {}
virtual void onStopped() {}
virtual void onConnectionOpened(const WSConnection::ptr_t& connection) { }
virtual void onConnectionClosed(const WSConnection::ptr_t& connection) { }
bool isRunning() const;
size_t getConnectionCount() const
{
LLMutexLock lock(&mConnectionMutex);
return mConnections.size();
}
void broadcastMessage(const std::string& message);
virtual bool update() { return true; }
connection_state_t getConnectionState(const connection_h& handle) const;
protected:
virtual WSConnection::ptr_t connectionFactory(WSServer::ptr_t server, connection_h handle);
bool start();
void stop();
bool sendMessageTo(const connection_h& handle, const std::string& message);
/**
* @brief Close a specific connection gracefully
* @param handle The connection handle to close
* @param code Close code (default: normal closure)
* @param reason Close reason string (default: empty)
* @return true if close was initiated successfully, false on error
*
* Internal method used by WSConnection to close individual connections.
* This method is thread-safe and can be called from any thread.
*/
bool closeConnection(const connection_h& handle, U16 code = 1000, const std::string& reason = std::string());
void closeAllConnections(U16 code = 1001, const std::string& reason = "Server shutting down");
private:
using connection_map_t = std::map<connection_h, WSConnection::ptr_t, std::owner_less<connection_h> >;
WSConnection::ptr_t getConnection(const connection_h& handle);
void handleOpenConnection(const connection_h& handle);
void handleCloseConnection(const connection_h& handle);
void handleMessage(const connection_h& handle, const std::string& message);
std::string mServerName;
std::unique_ptr<Server_impl> mImpl;
connection_map_t mConnections;
mutable LLMutex mConnectionMutex;
// Threading support
std::thread mServerThread; ///< Thread running the ASIO event loop
std::atomic<bool> mShouldStop{ false }; ///< Thread-safe stop flag
mutable LLMutex mThreadMutex; ///< Mutex for thread synchronization
};
// Server and Connection Management
WSServer::ptr_t findServerByName(const std::string &name) const;
bool addServer(const WSServer::ptr_t& server);
bool removeServer(const std::string &name);
bool startServer(const std::string &name) const;
void stopServer(const std::string &name) const;
void update();
protected:
void initSingleton() override;
void cleanupSingleton() override;
private:
using server_map_t = std::map<std::string, WSServer::ptr_t>;
void stopAllServers();
server_map_t mServers;
};
|