Coverage Report

Created: 2026-07-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WWebSocketConnection.h
Line
Count
Source
1
#ifndef WT_WWEBSOCKETCONNECTION_H_
2
#define WT_WWEBSOCKETCONNECTION_H_
3
4
#include "Wt/AsioWrapper/asio.hpp"
5
#ifdef WT_WITH_SSL
6
#include "Wt/AsioWrapper/ssl.hpp"
7
#endif
8
#include "Wt/AsioWrapper/system_error.hpp"
9
10
#include "Wt/WObject.h"
11
#include "Wt/WIOService.h"
12
#include "Wt/WSignal.h"
13
#include "Wt/WStringStream.h"
14
15
#include <functional>
16
#include <mutex>
17
18
namespace Wt {
19
20
class WebSocketHandlerResource;
21
class WWebSocketResource;
22
23
using Buffer = std::array<char, 8192>;
24
using Socket = AsioWrapper::asio::ip::tcp::socket;
25
#ifdef WT_WITH_SSL
26
using SSLSocket = AsioWrapper::asio::ssl::stream<AsioWrapper::asio::ip::tcp::socket>;
27
#endif
28
29
/*! \class OpCode Wt/WWebSocketConnection.h
30
 *  \brief Indicates the type of the frame/message.
31
 *
32
 * This enumeration contains all possible types for a WebSocket frame.
33
 */
34
enum class OpCode
35
{
36
  /*! Indicates that the content is the same as a previous frame, and
37
   * that its data needs to be appended to the previous frame.
38
   */
39
  Continuation = 0,
40
  //! Indicates a text message. The data of the frame is a string.
41
  Text = 1,
42
  //! Indicates a binary message. The data of the frame is binary.
43
  Binary = 2,
44
  /*! Indicates a close message. The data of the frame is an optional
45
   * string.
46
   */
47
  Close = 8,
48
  //! Indicates a ping message.
49
  Ping = 9,
50
  //! Indicates a pong message.
51
  Pong = 10,
52
};
53
54
/*! \class CloseCode Wt/WWebSocketConnection.h
55
 *  \brief Indicates the generic code for the close message.
56
 *
57
 * This contains all possible codes for a close message.
58
 */
59
enum class CloseCode
60
{
61
  Normal = 1000, //!< The normal code, this is the most-often used code.
62
  GoingAway = 1001, //!< Indicates that the server is going down.
63
  ProtocolError = 1002, //<! Termination due to a protocol error.
64
  UnexpectedDataType = 1003, //<! The data type cannot be handled.
65
  Reserved = 1004, //<! Reserved, not to be used.
66
  NoStatusCode = 1005, //!< Reserved, to be used by applications.
67
  AbnormalClose = 1006, //!< Reserved, to be used by applications.
68
  IncorrectData = 1007, //!< Received data is not consistent with type.
69
  PolicyError = 1008, //!< Generic code for any policy violations.
70
  MessageTooLarge = 1009, //!< Termination when received message is too big.
71
  FailedExtensionNegotiation = 1010, //!< Termination by client due to expected extension.
72
  UnexpectedCondition = 1011, //!< Termination by server due to unexpected condition.
73
  TLSFailure = 1015 //!< Reserved, to be used by applications.
74
};
75
76
struct WebSocketFrameHeader;
77
78
enum class ReadingState
79
{
80
  ReadingHeader = 0,
81
  ReadingData = 1,
82
  ClosingSocket = 2,
83
  SkipData = 3,
84
};
85
86
class WT_API WebSocketConnection : public std::enable_shared_from_this<WebSocketConnection>
87
{
88
public:
89
  WebSocketConnection(AsioWrapper::asio::io_service& ioService);
90
91
  virtual ~WebSocketConnection();
92
93
  virtual Socket& socket() = 0;
94
95
  virtual void doClose() = 0;
96
97
  void startReading();
98
  // Performs an async write to the socket, inside the write-done loop.
99
  bool doAsyncWrite(OpCode type, const std::vector<char>& frameHeader, const std::vector<char>& data = {});
100
  // Performs an async write to the socket, outside the write-done loop.
101
  // This will schedule the write to happen on the strand, which will execute ones the socket is no longer
102
  // busy writing (or "immediately" if it isn't busy. This will not trigger the callback (hasDataWrittenCallback_)
103
  // so the WWebsocketResource isn't aware of this frame being sent, since it will not be notified.
104
  void doControlFrameWrite(const std::vector<char>& frameHeader, OpCode opcode);
105
106
  void setDataReadCallback(const std::function<void()>& callback);
107
  void setDataWrittenCallback(const std::function<void(const AsioWrapper::error_code&, std::size_t)>& callback);
108
109
0
  WebSocketFrameHeader* header() const { return header_.get(); }
110
0
  const WStringStream& dataBuffer() const { return dataBuffer_; }
111
112
  bool isOpen();
113
114
  void setMaximumReceivedFrameSize(std::size_t frameSize);
115
  void setMaximumReceivedMessageSize(std::size_t messageSize);
116
117
0
  bool skipMessage() const { return skipMessage_; }
118
119
protected:
120
  AsioWrapper::asio::io_service& ioService_;
121
  AsioWrapper::asio::io_service::strand strand_;
122
123
  void handleAsyncRead(const AsioWrapper::error_code& e, std::size_t bytes_transferred);
124
  void handleAsyncWritten(OpCode type, const AsioWrapper::error_code& e, std::size_t bytes_transferred);
125
126
  Buffer readBuffer_;
127
  Buffer::iterator readBufferPtr_; // first free byte of readBuffer_ when async read was started
128
129
  std::size_t skipDataSize_;
130
  std::size_t frameSize_;
131
  std::size_t messageSize_;
132
133
  virtual void doSocketRead(char* buffer, size_t size) = 0;
134
  // Performs an async write to the socket, inside the write-done loop.
135
  virtual void doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type) = 0;
136
137
private:
138
  // Buffer & parsing
139
  ReadingState readingState_;
140
  bool skipMessage_;
141
  std::unique_ptr<WebSocketFrameHeader> header_;
142
  WStringStream dataBuffer_;
143
144
  // Continuation skipping & size
145
  bool isContinuation_;
146
  std::size_t continuationSize_;
147
148
  // Socket state, part of the regular flow (write - done).
149
  bool isWriting_;
150
151
  // Socket state, part of the control system, outside the normal flow.
152
  bool isWritingToSocket_;
153
  // Socket writing mutex, guarding (control) writing state, which can
154
  // be set by sendMessage or ping-pong frames.
155
  // It guards:
156
  //  - isWritingToSocket_
157
  //  - hasPendingDataWrite_
158
  //  - hasPendingPingWrite_
159
  //  - hasPendingPingWrite_
160
  //  - pendingWriteDataType_
161
  //  - pendingWritePingBuffer_
162
  //  - pendingWritePongBuffer_
163
  //  - pendingWriteDataBuffer_
164
  std::mutex writingMutex_;
165
166
  // Writing buffer. Necessary for delayed frames, so that data isn't lost,
167
  // since `const_buffer` does NOT own its underlying data.
168
  WStringStream writeBuffer_;
169
  // Temporary writing storage, when sending collides with other socket
170
  // operation. The type will indicate the delayed request type.
171
  // In case of Ping or Pong, the respective buffer will be used.
172
  // In case of other frames, the data buffer will be used.
173
  bool hasPendingDataWrite_;
174
  bool hasPendingPingWrite_;
175
  bool hasPendingPongWrite_;
176
  OpCode pendingWriteDataType_;
177
  std::vector<AsioWrapper::asio::const_buffer> pendingWritePingBuffer_;
178
  std::vector<AsioWrapper::asio::const_buffer> pendingWritePongBuffer_;
179
  std::vector<AsioWrapper::asio::const_buffer> pendingWriteDataBuffer_;
180
181
  std::function<void()> hasDataReadCallback_;
182
  std::function<void(const AsioWrapper::error_code&, std::size_t)> hasDataWrittenCallback_;
183
184
  void doAsyncRead(char* buffer, size_t size);
185
  std::size_t parseBuffer(const char* begin, const char* end);
186
  void doEmitAndCleanBuffers();
187
};
188
189
class WT_API WebSocketTcpConnection final : public WebSocketConnection
190
{
191
public:
192
  explicit WebSocketTcpConnection(AsioWrapper::asio::io_service& ioService, std::unique_ptr<Socket> socket);
193
194
  ~WebSocketTcpConnection() final;
195
196
  Socket& socket() final;
197
198
  void doClose() final;
199
200
protected:
201
  void doSocketRead(char* input, size_t offset) final;
202
  void doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type) final;
203
204
private:
205
  std::unique_ptr<Socket> socket_;
206
};
207
208
#ifdef WT_WITH_SSL
209
class WT_API WebSocketSslConnection final : public WebSocketConnection
210
{
211
public:
212
  explicit WebSocketSslConnection(AsioWrapper::asio::io_service& ioService, std::unique_ptr<SSLSocket> socket);
213
214
  ~WebSocketSslConnection() final;
215
216
  Socket& socket() final;
217
218
  void doClose() final;
219
220
protected:
221
  void doSocketRead(char* input, size_t offset) final;
222
  void doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type) final;
223
224
private:
225
  std::unique_ptr<SSLSocket> socket_;
226
227
  void stopTcpSocket(const AsioWrapper::error_code& e);
228
};
229
#endif
230
231
/*! \class WWebSocketConnection Wt/WWebSocketConnection.h
232
 *  \brief A single connection to a WWebSocketResource.
233
 *
234
 * When a connection is set-up to a WWebSocketResource, a new connection
235
 * is created, which manages the underlying TCP or SSL stream.
236
 *
237
 * Upon its creation, it will inherit the setting currently found on the
238
 * WWebSocketResource. Like the maximum frame and message sizes, the
239
 * settings for the ping-pong system and whether it takes the application
240
 * update lock.
241
 *
242
 * The connection can be used to listen to incoming requests, or send out
243
 * messages after the WebSocket set-up has occurred. It also offers
244
 * methods to manage the connection, like closing it gracefully.
245
 *
246
 * Each of the methods that either read an incoming message, or respond
247
 * to a message are made virtual so that developers can change the
248
 * implementation slightly, if they desire. This will be limited to being
249
 * able to log, track, or manipulate the data, but not change the way the
250
 * framework will create frames or messages.
251
 *
252
 * Most socket, and data management is still handled by the framework.
253
 * That is, everything to do with any of the above mentioned methods,
254
 * copied from WWebSocketResource, is handled by the framework. A
255
 * developer will thus never be able to receive a message or frame
256
 * greater than the imposed limit, unless they increase it, or disable
257
 * it. If the setTakesUpdateLock() is set to \p true, this lock
258
 * acquisition will always take place. These settings can always be
259
 * changed after the connection has been set up.
260
 */
261
class WT_API WWebSocketConnection : public WObject
262
{
263
public:
264
  //! Constructor, pointing to its resource and the IO service.
265
  WWebSocketConnection(WWebSocketResource* resource, AsioWrapper::asio::io_service& ioService);
266
267
  //! Destructor.
268
  virtual ~WWebSocketConnection();
269
270
  /*! \brief Handles an incoming text message.
271
   *
272
   * A single message (which can consist of multiple frames) has been
273
   * received, of the text type. The \p text parameter contains the data
274
   * that was present in the message.
275
   */
276
  virtual void handleMessage(const std::string& text);
277
278
  /*! \brief Handles an incoming binary message.
279
   *
280
   * A single message (which can consist of multiple frames) has been
281
   * received, of the binary type. The \p buffer parameter contains the
282
   * data that was present in the message.
283
   */
284
  virtual void handleMessage(const std::vector<char>& buffer);
285
286
  /*! \brief Sends out a text message.
287
   *
288
   * This will create a single text message that is to be sent to the
289
   * client. The \p text data will be added to the message. The message
290
   * is sent out as a single frame. This method can only be called in a
291
   * sequential manner. Meaning that after each send event, a done() will
292
   * be called, and no other send can be executed before the done()
293
   * signal has been emitted. If this is called before done() fired, and
294
   * thus when the previous message is still being written, the function
295
   * will return \p false, and a warning will be logged.
296
   *
297
   * Upon success, this will return \p true. This does not indicate that
298
   * the message has been actually sent, but that it has been successfully
299
   * queued. When the message has been written to the stream, done() will
300
   * be called. When done() is fired, the queued message has been
301
   * handled. This can mean that is has been successfully written, or
302
   * that and error has occurred. If an error is attached to the done()
303
   * signal, an error has occurred during sending, and the stream is
304
   * potentially useless, depending on the type of error.
305
   */
306
  virtual bool sendMessage(const std::string& text);
307
308
  /*! \brief Sends out a binary message.
309
   *
310
   * This will create a single binary message that is to be sent to the
311
   * client. The \p buffer data will be added to the message. The message
312
   * is sent out as a single frame. This method can only be called in a
313
   * sequantial manner. Meaning that after each send event, a done() will
314
   * be called, and no other send can be executed before the done()
315
   * signal has been emitted. If this is called before done() fired, and
316
   * thus when the previous message is still being written, the function
317
   * will return \p false, and a warning will be logged.
318
   *
319
   * Upon success, this will return \p true. This does not indicate that
320
   * the message has been actually sent, but that it has been successfully
321
   * queued. When the message has been written to the stream, done() will
322
   * be called. When done() is fired, the queued message has been
323
   * handled. This can mean that is has been successfully written, or
324
   * that and error has occurred. If an error is attached to the done()
325
   * signal, an error has occurred during sending, and the stream is
326
   * potentially useless, depending on the type of error.
327
   */
328
  virtual bool sendMessage(const std::vector<char>& buffer);
329
330
  /*! \brief Send the close signal to the client.
331
   *
332
   * This will close the connection in a graceful manner. After the
333
   * closing frame is sent out, the connection waits for it to be
334
   * acknowledged by the client. Once this has been received, the
335
   * connection will be actually terminated.
336
   *
337
   * When sending this message, the \p code will indicate a generic
338
   * cause for termination. Generally CloseCode::Normal will be most
339
   * often used. An optional \p reason can be provided to the client
340
   * as an additional specification to the generic code.
341
   *
342
   * This method is also considered a sending event, meaning that it
343
   * also blocks an additional writing events, until done() has been
344
   * emitted.
345
   */
346
  virtual bool close(CloseCode code, const std::string& reason = "");
347
348
  /*! \brief Acknowledges a received close frame.
349
   *
350
   * A client has sent out a close frame, to which the server responds
351
   * with an identical message. It sends out a frame with the same code
352
   * and optionally the same \p reason.
353
   *
354
   * Since this is a final response, it does not fall within the normal
355
   * sending/done() logic. It does still write a frame to the client, but
356
   * the response here is not important. The done() signal will be fired,
357
   * and used internally, eventually firing the closed() signal. Which
358
   * is used to indicate that the stream will be closed down and is to be
359
   * considered unusable from now on.
360
   */
361
  virtual void acknowledgeClose(const std::string & reason = "");
362
363
  /*! \brief Sends out a ping message.
364
   *
365
   * This sends out a ping message to the connected client. This will
366
   * happen every \p x seconds where \p x is set by setPingTimeout().
367
   */
368
  virtual bool sendPing();
369
370
  /*! \brief Acknowlegdes a received ping message.
371
   *
372
   * The client has sent out a ping message. The server now needs to
373
   * respond with a pong.
374
   */
375
  virtual void acknowledgePing();
376
377
  /*! \brief Handles an incoming pong message.
378
   *
379
   * A pong message has been sent by the client, meaning that earlier
380
   * the server has sent out a ping message with sendPing().
381
   *
382
   * This ensures that the ping-pong mechanism is complete. When this
383
   * method is called, this indicates a full cycle of the ping-pong
384
   * logic. This will set up the timer (if it is enabled) again.
385
   */
386
  virtual void handlePong();
387
388
  /*! \brief Handles an incoming bad frame.
389
   *
390
   * A frame was received that was not expected. Currently this is only
391
   * used for when a continuation frame is caught, but no initial frame
392
   * to which the continuation should apply, was received first.
393
   */
394
  virtual void handleError();
395
396
  /*! \brief Sets the maximum received frame and message size.
397
   *
398
   * \sa WWebSocketResource::setMaximumReceivedSize
399
   */
400
  void setMaximumReceivedSize(std::size_t frameSize, std::size_t messageSize);
401
402
  /*! \brief Sets the application update lock needs to be taken on
403
   * sending or receiving messages.
404
   *
405
   * \sa WWebSocketResource::setTakesUpdateLock
406
   */
407
  void setTakesUpdateLock(bool takesUpdateLock);
408
409
  /*! \brief Sets the ping-pong configuration.
410
   *
411
   * \sa WWebSocketResource::setPingTimeout
412
   */
413
  void setPingTimeout(int pingInterval, int pingTimeout);
414
415
  /*! \brief Signal indicating a sending event has been completed.
416
   *
417
   * The error code it returns either does not exists, indicating a
418
   * successful write. Or it can exist, and will then have a value.
419
   * The number of the value signifies which type of error has occurred.
420
   * This can leave the underlying stream useless.
421
   */
422
0
  Signal<AsioWrapper::error_code>& done() { return done_; }
423
424
  /*! \brief Signal indicating the connection has been closed.
425
   *
426
   * The error code it returns either does not exists, indicating a
427
   * successful close. Or it can exist, and will then have a value.
428
   * The number of the value signifies which type of error has occurred.
429
   * Even if an error occurs the underlying stream should never be used
430
   * again, since it is very likely the client has already closed the
431
   * stream from their end.
432
   *
433
   * The string returned can hold an optional message, which specifies
434
   * why the connection was closed.
435
   */
436
0
  Signal<AsioWrapper::error_code, const std::string&>& closed() { return closed_; }
437
438
private:
439
  WWebSocketResource* resource_;
440
  std::shared_ptr<WebSocketConnection> socketConnection_;
441
442
  Signal<AsioWrapper::error_code> done_;
443
  Signal<AsioWrapper::error_code, const std::string&> closed_;
444
445
  void setSocket(const std::shared_ptr<WebSocketConnection>& connection);
446
447
  // Send an empty frame INSIDE the write-done loop (blocking async write & done() signal)
448
  bool sendEmptyFrame(OpCode opcode);
449
  // Send an empty frame OUTSIDE the write-done loop (blocking async write & done() signal)
450
  bool sendControlFrame(OpCode opcode);
451
  bool sendDataFrame(const std::vector<char>& buffer, OpCode opcode);
452
453
  void writeFrame(const AsioWrapper::error_code& e, std::size_t bytes_transferred);
454
  void receiveFrame();
455
456
  void startPingTimer();
457
  void doSendPing(const AsioWrapper::error_code& e);
458
  void missingPong(const AsioWrapper::error_code& e);
459
  void closeSocket(const AsioWrapper::error_code& e, const std::string& reason);
460
461
  WApplication* app();
462
463
  bool wantsToClose_;
464
465
  std::size_t frameSize_;
466
  std::size_t messageSize_;
467
468
  bool takesUpdateLock_;
469
470
  int pingInterval_;
471
  int pingTimeout_;
472
473
  AsioWrapper::asio::steady_timer pingSignalTimer_;
474
  AsioWrapper::asio::steady_timer pongTimeoutTimer_;
475
476
  WStringStream continuationBuffer_;
477
  OpCode continuationOpCode_;
478
479
  friend class WebSocketHandlerResource;
480
};
481
}
482
#endif // WT_WWEBSOCKETCONNECTION_H_