/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_ |