/src/wt/src/Wt/WWebSocketConnection.C
Line | Count | Source |
1 | | #include "Wt/WWebSocketConnection.h" |
2 | | |
3 | | #include "Wt/AsioWrapper/namespace.hpp" |
4 | | #include "Wt/AsioWrapper/system_error.hpp" |
5 | | |
6 | | #include "Wt/WApplication.h" |
7 | | #include "Wt/WLogger.h" |
8 | | #include "Wt/WString.h" |
9 | | #include "Wt/WWebSocketResource.h" |
10 | | |
11 | | #include <chrono> |
12 | | #include <system_error> |
13 | | |
14 | | namespace Wt { |
15 | | LOGGER("WWebSocketConnection"); |
16 | | |
17 | | namespace asio = AsioWrapper::asio; |
18 | | |
19 | | std::string OpCodeToString(OpCode code) |
20 | 0 | { |
21 | 0 | switch(code) { |
22 | 0 | case OpCode::Continuation: |
23 | 0 | return "Continuation"; |
24 | 0 | case OpCode::Text: |
25 | 0 | return "Text"; |
26 | 0 | case OpCode::Binary: |
27 | 0 | return "Binary"; |
28 | 0 | case OpCode::Close: |
29 | 0 | return "Close"; |
30 | 0 | case OpCode::Ping: |
31 | 0 | return "Ping"; |
32 | 0 | case OpCode::Pong: |
33 | 0 | return "Pong"; |
34 | 0 | } |
35 | 0 | return ""; |
36 | 0 | } |
37 | | |
38 | | struct WebSocketFrameHeader |
39 | | { |
40 | | public: |
41 | | bool isFinished; |
42 | | bool reserved1; |
43 | | bool reserved2; |
44 | | bool reserved3; |
45 | | OpCode opCode; |
46 | | bool isMasked; |
47 | | |
48 | | bool readFrameHeader(const char* begin, std::size_t maxLength); |
49 | | std::vector<char> generateFrameHeader(std::size_t dataSize, const std::vector<char>& mask = {}) const; |
50 | | |
51 | | int getHeaderSize() const; |
52 | | |
53 | | std::string toString() const; |
54 | | |
55 | 0 | unsigned char lengthCode() const { return lengthCode_; } |
56 | 0 | std::size_t length() const { return length_; } |
57 | 0 | unsigned char* mask() { return mask_; } |
58 | | |
59 | | private: |
60 | | unsigned char lengthCode_; |
61 | | uint64_t length_; |
62 | | unsigned char mask_[4]; |
63 | | }; |
64 | | |
65 | | bool WebSocketFrameHeader::readFrameHeader(const char* begin, std::size_t maxLength) |
66 | 0 | { |
67 | | /* FRAME: |
68 | | 0 1 2 |
69 | | 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 |
70 | | F R R R OPCODE M PAYLOAD LENGTH EXTENDED LENGTH |
71 | | I S S S A |
72 | | N V V V S |
73 | | 1 2 3 K |
74 | | |
75 | | 3/5/11 ... 5/9/15 |
76 | | 0 1 2 3 4 5 6 7 ... 0 1 2 3 4 5 6 7 |
77 | | MASKING KEY |
78 | | */ |
79 | 0 | if (maxLength < 2) { |
80 | 0 | return false; |
81 | 0 | } |
82 | | |
83 | | // First byte |
84 | 0 | unsigned char codes = begin[0]; |
85 | | // Second byte |
86 | 0 | unsigned char maskAndLength = begin[1]; |
87 | | |
88 | | // Byte 0 bit 0 |
89 | 0 | isFinished = codes & 0x80; |
90 | | // Byte 0 bit 1 |
91 | 0 | reserved1 = codes & 0x40; |
92 | | // Byte 0 bit 2 |
93 | 0 | reserved2 = codes & 0x20; |
94 | | // Byte 0 bit 3 |
95 | 0 | reserved3 = codes & 0x10; |
96 | | // Byte 0 bit 4-7 |
97 | 0 | opCode = static_cast<OpCode>(codes & 0xF); |
98 | | // Byte 1 bit 0 |
99 | 0 | isMasked = maskAndLength & 0x80; |
100 | | // Byte 7 bit 1-7 |
101 | 0 | lengthCode_ = maskAndLength & 0x7F; |
102 | |
|
103 | 0 | if (lengthCode_ == 126) { |
104 | 0 | if (maxLength < 4) { |
105 | 0 | return false; |
106 | 0 | } |
107 | | |
108 | | // Read next 2 bytes |
109 | 0 | unsigned char length1 = begin[2]; |
110 | 0 | unsigned char length2 = begin[3]; |
111 | 0 | length_ = (length1 << 8) | length2; |
112 | 0 | } else if (lengthCode_ == 127) { |
113 | | // Read next 8 bytes (where most significant must be 0) |
114 | | // assert length & (1 << 64) == 0 |
115 | 0 | if (maxLength < 10) { |
116 | 0 | return false; |
117 | 0 | } |
118 | | |
119 | 0 | unsigned char length1 = begin[2]; |
120 | 0 | unsigned char length2 = begin[3]; |
121 | 0 | unsigned char length3 = begin[4]; |
122 | 0 | unsigned char length4 = begin[5]; |
123 | 0 | unsigned char length5 = begin[6]; |
124 | 0 | unsigned char length6 = begin[7]; |
125 | 0 | unsigned char length7 = begin[8]; |
126 | 0 | unsigned char length8 = begin[9]; |
127 | 0 | length_ = (static_cast<uint64_t>(length1) << 56) | (static_cast<uint64_t>(length2) << 48) |
128 | 0 | | (static_cast<uint64_t>(length3) << 40) | (static_cast<uint64_t>(length4) << 32) |
129 | 0 | | (static_cast<uint64_t>(length5) << 24) | (static_cast<uint64_t>(length6) << 16) |
130 | 0 | | (static_cast<uint64_t>(length7) << 8) | static_cast<uint64_t>(length8); |
131 | 0 | } else { |
132 | 0 | length_ = lengthCode_; |
133 | 0 | } |
134 | | |
135 | | // Normally starts at offset 2 |
136 | 0 | unsigned int maskOffset = 2; |
137 | 0 | if (lengthCode_ == 126) { |
138 | | // Additional 2 bytes for medium payload length |
139 | 0 | maskOffset += 2; |
140 | 0 | } else if (lengthCode_ == 127) { |
141 | | // Additional 8 bytes for long payload length |
142 | 0 | maskOffset += 8; |
143 | 0 | } |
144 | |
|
145 | 0 | if (maxLength < maskOffset + 4) { |
146 | 0 | return false; |
147 | 0 | } |
148 | | |
149 | 0 | if (isMasked) { |
150 | | // Third to sixth byte if (length <= 125) |
151 | | // Fifth to eighth byte if (length == 126) |
152 | | // Eleventh to fourteenth byte if (length == 127) |
153 | 0 | mask_[0] = begin[maskOffset]; |
154 | 0 | mask_[1] = begin[maskOffset + 1]; |
155 | 0 | mask_[2] = begin[maskOffset + 2]; |
156 | 0 | mask_[3] = begin[maskOffset + 3]; |
157 | 0 | } |
158 | |
|
159 | 0 | return true; |
160 | 0 | } |
161 | | |
162 | | std::vector<char> WebSocketFrameHeader::generateFrameHeader(std::size_t dataLength, const std::vector<char>& mask) const |
163 | 0 | { |
164 | 0 | char FIN = static_cast<char>(isFinished); |
165 | 0 | char RSV1 = static_cast<char>(reserved1); |
166 | 0 | char RSV2 = static_cast<char>(reserved2); |
167 | 0 | char RSV3 = static_cast<char>(reserved3); |
168 | 0 | char code = static_cast<char>(opCode); |
169 | |
|
170 | 0 | char frameByte = (FIN << 7) + (RSV1 << 6) + (RSV2 << 5) + (RSV3 << 4) + code; |
171 | |
|
172 | 0 | std::vector<char> frameHeader; |
173 | |
|
174 | 0 | frameHeader.push_back(frameByte); |
175 | | |
176 | | // Masking & length |
177 | | // MASKING must NOT be done by the server |
178 | 0 | char MASK = isMasked; |
179 | 0 | char length = 0; |
180 | |
|
181 | 0 | if (dataLength < 126) { |
182 | 0 | length = dataLength; |
183 | 0 | } else if (dataLength < 65536) { |
184 | 0 | length = 126; |
185 | 0 | } else { |
186 | 0 | length = 127; |
187 | 0 | } |
188 | |
|
189 | 0 | char maskAndLength = (MASK << 7) + length; |
190 | |
|
191 | 0 | frameHeader.push_back(maskAndLength); |
192 | |
|
193 | 0 | if (length == 126) { |
194 | | // Optional extended length (2 byte) |
195 | 0 | unsigned int l = dataLength; |
196 | 0 | char l1 = (l >> 8) & 0xFF; |
197 | 0 | char l2 = l & 0xFF; |
198 | 0 | frameHeader.push_back(l1); |
199 | 0 | frameHeader.push_back(l2); |
200 | 0 | } else if (length == 127) { |
201 | | // Optional extended length (8 byte) |
202 | 0 | std::size_t l = dataLength; |
203 | 0 | char l1 = (l >> 56) & 0xFF; |
204 | 0 | char l2 = (l >> 48) & 0xFF; |
205 | 0 | char l3 = (l >> 40) & 0xFF; |
206 | 0 | char l4 = (l >> 32) & 0xFF; |
207 | 0 | char l5 = (l >> 24) & 0xFF; |
208 | 0 | char l6 = (l >> 16) & 0xFF; |
209 | 0 | char l7 = (l >> 8) & 0xFF; |
210 | 0 | char l8 = l & 0xFF; |
211 | 0 | frameHeader.push_back(l1); |
212 | 0 | frameHeader.push_back(l2); |
213 | 0 | frameHeader.push_back(l3); |
214 | 0 | frameHeader.push_back(l4); |
215 | 0 | frameHeader.push_back(l5); |
216 | 0 | frameHeader.push_back(l6); |
217 | 0 | frameHeader.push_back(l7); |
218 | 0 | frameHeader.push_back(l8); |
219 | 0 | } |
220 | |
|
221 | 0 | if (isMasked && mask.size() == 4) { |
222 | | // LOG |
223 | 0 | frameHeader.push_back(mask[0]); |
224 | 0 | frameHeader.push_back(mask[1]); |
225 | 0 | frameHeader.push_back(mask[2]); |
226 | 0 | frameHeader.push_back(mask[3]); |
227 | 0 | } |
228 | |
|
229 | 0 | return frameHeader; |
230 | 0 | } |
231 | | |
232 | | int WebSocketFrameHeader::getHeaderSize() const |
233 | 0 | { |
234 | | // Minimum size is 2 |
235 | 0 | int size = 2; |
236 | | |
237 | | // Length is 2 bytes |
238 | 0 | if (lengthCode_ == 126) { |
239 | 0 | size += 2; |
240 | | // Length it 8 bytes |
241 | 0 | } else if (lengthCode_ == 127) { |
242 | 0 | size += 8; |
243 | 0 | } |
244 | | |
245 | | // Masking is 4 bytes |
246 | 0 | if (isMasked) { |
247 | 0 | size += 4; |
248 | 0 | } |
249 | |
|
250 | 0 | return size; |
251 | 0 | } |
252 | | |
253 | | std::string WebSocketFrameHeader::toString() const |
254 | 0 | { |
255 | 0 | std::string maskValues; |
256 | 0 | if (isMasked) { |
257 | 0 | maskValues = std::to_string(mask_[0]) + " " + std::to_string(mask_[1]) + " " |
258 | 0 | + std::to_string(mask_[2]) + " " + std::to_string(mask_[3]); |
259 | 0 | } |
260 | 0 | WString result = WString("WebSocket Header with\nFinished: {1}\n" |
261 | 0 | "RSV1: {2}\nRSV2: {3}\nRSV3: {4}\n" |
262 | 0 | "OpCode: {5} ({6})\nMasking: {7}\n" |
263 | 0 | "Mask: {8}\nPayload Length: {9}") |
264 | 0 | .arg(isFinished) |
265 | 0 | .arg(reserved1) |
266 | 0 | .arg(reserved2) |
267 | 0 | .arg(reserved3) |
268 | 0 | .arg(static_cast<int>(opCode)) |
269 | 0 | .arg(OpCodeToString(opCode)) |
270 | 0 | .arg(isMasked) |
271 | 0 | .arg(maskValues) |
272 | 0 | .arg(length_); |
273 | 0 | return result.toUTF8(); |
274 | 0 | } |
275 | | |
276 | | WebSocketConnection::WebSocketConnection(AsioWrapper::asio::io_service& ioService) |
277 | 0 | : ioService_(ioService), |
278 | 0 | strand_(ioService), |
279 | 0 | readBuffer_(), |
280 | 0 | readBufferPtr_(readBuffer_.begin()), |
281 | 0 | skipDataSize_(0), |
282 | 0 | readingState_(ReadingState::ReadingHeader), |
283 | 0 | skipMessage_(false), |
284 | 0 | header_(new WebSocketFrameHeader()), |
285 | 0 | isContinuation_(false), |
286 | 0 | continuationSize_(0), |
287 | 0 | isWriting_(false), |
288 | 0 | isWritingToSocket_(false), |
289 | 0 | hasPendingDataWrite_(false) |
290 | 0 | { |
291 | 0 | } |
292 | | |
293 | | WebSocketConnection::~WebSocketConnection() |
294 | 0 | { |
295 | 0 | } |
296 | | |
297 | | void WebSocketConnection::startReading() |
298 | 0 | { |
299 | 0 | doAsyncRead(&(*readBufferPtr_), readBuffer_.end() - readBufferPtr_); |
300 | 0 | } |
301 | | |
302 | | void WebSocketConnection::doAsyncRead(char* buffer, size_t size) |
303 | 0 | { |
304 | 0 | if (readingState_ == ReadingState::ClosingSocket) { |
305 | 0 | LOG_WARN("doAsyncRead() was called after a closing signal has been sent or received."); |
306 | 0 | return; |
307 | 0 | } |
308 | | |
309 | 0 | doSocketRead(buffer, size); |
310 | 0 | } |
311 | | |
312 | | // Returns amount of bytes consumed; 0 if more data is needed before |
313 | | // more can be consumed. |
314 | | std::size_t WebSocketConnection::parseBuffer(const char* begin, const char* end) |
315 | 0 | { |
316 | 0 | const char *current = begin; |
317 | |
|
318 | 0 | if (readingState_ == ReadingState::ReadingHeader) { |
319 | 0 | auto isCompleteHeader = header_->readFrameHeader(begin, end - begin); |
320 | |
|
321 | 0 | if (!isCompleteHeader) { |
322 | | // By returning 0; indicate that we need more data |
323 | 0 | return 0; |
324 | 0 | } else { |
325 | 0 | current += header_->getHeaderSize(); |
326 | 0 | readingState_ = ReadingState::ReadingData; |
327 | |
|
328 | 0 | if (header_->length() > frameSize_) { |
329 | 0 | LOG_ERROR("parseBuffer: A frame was received that exceeded the limit (" << header_->length() << " > " << frameSize_ << " (bytes))"); |
330 | 0 | readingState_ = ReadingState::SkipData; |
331 | 0 | if (!header_->isFinished) { |
332 | 0 | LOG_ERROR("parseBuffer: The received frame will be followed by a continuation that should be skipped as well."); |
333 | 0 | skipMessage_ = true; |
334 | 0 | } |
335 | 0 | } else if (skipMessage_) { |
336 | 0 | readingState_ = ReadingState::SkipData; |
337 | 0 | LOG_ERROR("parseBuffer: A continuation frame was received that is part of a skipped message."); |
338 | 0 | } else if (isContinuation_ && header_->length() + continuationSize_ > messageSize_) { |
339 | 0 | readingState_ = ReadingState::SkipData; |
340 | 0 | LOG_ERROR("parseBuffer: A continuation frame was received that made the whole message exceed the limit (" << header_->length() + continuationSize_ << " > " << messageSize_ << " (bytes))"); |
341 | 0 | if (!header_->isFinished) { |
342 | 0 | LOG_ERROR("parseBuffer: The received frame will be followed by a continuation that should be skipped as well."); |
343 | 0 | skipMessage_ = true; |
344 | 0 | } |
345 | 0 | } |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | 0 | if (readingState_ == ReadingState::ReadingData) { |
350 | 0 | std::size_t bytes_to_add = std::min(header_->length() - dataBuffer_.length(), (size_t)(end - current)); |
351 | 0 | dataBuffer_.append(current, bytes_to_add); |
352 | 0 | if (dataBuffer_.length() == header_->length()) { |
353 | | // received a full frame |
354 | 0 | doEmitAndCleanBuffers(); |
355 | 0 | } |
356 | 0 | current += bytes_to_add; |
357 | 0 | } |
358 | | |
359 | | // Case where data is skipped due to size limitation. |
360 | 0 | if (readingState_ == ReadingState::SkipData) { |
361 | 0 | std::size_t bytes_to_add = std::min(header_->length() - skipDataSize_, (size_t)(end - current)); |
362 | 0 | skipDataSize_ += bytes_to_add; |
363 | 0 | if (skipDataSize_ == header_->length()) { |
364 | | // received a full skipped frame |
365 | 0 | doEmitAndCleanBuffers(); |
366 | 0 | } |
367 | |
|
368 | 0 | current += bytes_to_add; |
369 | 0 | } |
370 | |
|
371 | 0 | return current - begin; |
372 | 0 | } |
373 | | |
374 | | void WebSocketConnection::doEmitAndCleanBuffers() |
375 | 0 | { |
376 | | // Indicates that a message or frame falls within the defined limited size. |
377 | 0 | if (readingState_ != ReadingState::SkipData) { |
378 | 0 | hasDataReadCallback_(); |
379 | 0 | } else { |
380 | 0 | LOG_INFO("doEmitAndCleanBuffers: not calling read callback due to frame or message size being exceeded."); |
381 | 0 | } |
382 | |
|
383 | 0 | readingState_ = ReadingState::ReadingHeader; |
384 | | |
385 | | // If the header marks a complete message, no longer skip any frames, if before |
386 | | // they were skipped due to the message limit. |
387 | 0 | if (header_->isFinished) { |
388 | 0 | skipMessage_ = false; |
389 | 0 | isContinuation_ = false; |
390 | 0 | continuationSize_ = 0; |
391 | 0 | } else { |
392 | 0 | isContinuation_ = true; |
393 | 0 | continuationSize_ += header_->length(); |
394 | 0 | } |
395 | | |
396 | | // If closing received, mark as wanting to close, to avoid further listening, |
397 | | // and cancel active async (read) operations. |
398 | 0 | if (header_->opCode == OpCode::Close) { |
399 | 0 | readingState_ = ReadingState::ClosingSocket; |
400 | 0 | } |
401 | |
|
402 | 0 | dataBuffer_.clear(); |
403 | 0 | skipDataSize_ = 0; |
404 | 0 | } |
405 | | |
406 | | void WebSocketConnection::handleAsyncRead(const AsioWrapper::error_code& e, std::size_t bytes_transferred) |
407 | 0 | { |
408 | 0 | if (e) { |
409 | | // Normal shutdown |
410 | 0 | if (e == AsioWrapper::asio::error::operation_aborted) { |
411 | 0 | dataBuffer_.clear(); |
412 | 0 | return; |
413 | 0 | } |
414 | | |
415 | 0 | LOG_ERROR("handleAsyncRead: error encountered " << e.value()); |
416 | |
|
417 | 0 | dataBuffer_.clear(); |
418 | |
|
419 | 0 | doClose(); |
420 | 0 | return; |
421 | 0 | } |
422 | | |
423 | 0 | LOG_DEBUG("handleAsyncRead: incoming frame of size " << bytes_transferred << " bytes"); |
424 | | |
425 | | // parse, starting at the beginning of the read buffer |
426 | 0 | const char *current = &(*readBuffer_.begin()); |
427 | | // when calculating end, start after bytes that were still in buffer |
428 | 0 | const char *end = &(*readBufferPtr_) + bytes_transferred; |
429 | | |
430 | | // parse the input buffer until we don't make progress anymore |
431 | 0 | std::size_t parsedBytes = parseBuffer(current, end); |
432 | 0 | while (parsedBytes != 0) { |
433 | 0 | current += parsedBytes; |
434 | 0 | parsedBytes = parseBuffer(current, end); |
435 | 0 | } |
436 | | // move remainder of rx buffer back at the start. Buffer should be empty, |
437 | | // unless a partial header was received |
438 | 0 | std::size_t restSize = end - current; |
439 | 0 | std::memmove(&(*readBuffer_.begin()), current, restSize); |
440 | 0 | readBufferPtr_ = readBuffer_.begin() + restSize; |
441 | |
|
442 | 0 | if (readingState_ != ReadingState::ClosingSocket) { |
443 | 0 | doAsyncRead(&(*readBufferPtr_), readBuffer_.end() - readBufferPtr_); |
444 | 0 | } |
445 | 0 | } |
446 | | |
447 | | bool WebSocketConnection::doAsyncWrite(OpCode type, const std::vector<char>& frameHeader, const std::vector<char>& data) |
448 | 0 | { |
449 | | // Check for the write-done loop, developer driven |
450 | 0 | if (isWriting_) { |
451 | 0 | LOG_WARN("The connection is already being used to send data over. Wait for the done() signal to fire."); |
452 | 0 | return false; |
453 | 0 | } |
454 | | |
455 | | // Concat header and data to stream |
456 | 0 | writeBuffer_.append(frameHeader.data(), frameHeader.size()); |
457 | 0 | writeBuffer_.append(data.data(), data.size()); |
458 | | |
459 | | // Put data into asio::buffer actual write buffer |
460 | 0 | using namespace AsioWrapper::asio; |
461 | |
|
462 | 0 | std::vector<const_buffer> buffer; |
463 | 0 | writeBuffer_.asioBuffers(buffer); |
464 | | |
465 | | // Check for functionality outside write-done loop (ping/pong), framework driven |
466 | 0 | std::unique_lock<std::mutex> lock(writingMutex_); |
467 | 0 | if (isWritingToSocket_) { |
468 | 0 | LOG_DEBUG("doAsyncWrite: The connection is being written to (Ping/Pong), the frame will be delayed."); |
469 | 0 | hasPendingDataWrite_ = true; |
470 | 0 | pendingWriteDataType_ = type; |
471 | 0 | pendingWriteDataBuffer_ = buffer; |
472 | 0 | return true; |
473 | 0 | } |
474 | | |
475 | 0 | isWriting_ = true; |
476 | 0 | isWritingToSocket_ = true; |
477 | |
|
478 | 0 | doSocketWrite(buffer, type); |
479 | |
|
480 | 0 | return true; |
481 | 0 | } |
482 | | |
483 | | void WebSocketConnection::doControlFrameWrite(const std::vector<char>& frameHeader, OpCode opcode) |
484 | 0 | { |
485 | | // Put data into asio::buffer actual write buffer |
486 | 0 | writeBuffer_.append(frameHeader.data(), frameHeader.size()); |
487 | |
|
488 | 0 | using namespace AsioWrapper::asio; |
489 | |
|
490 | 0 | std::vector<const_buffer> buffer; |
491 | 0 | writeBuffer_.asioBuffers(buffer); |
492 | | |
493 | | // Check for functionality outside write-done loop (ping/pong), framework driven |
494 | 0 | std::unique_lock<std::mutex> lock(writingMutex_); |
495 | 0 | if (isWritingToSocket_) { |
496 | 0 | if (opcode == OpCode::Ping) { |
497 | 0 | LOG_DEBUG("doControlFrameWrite: Delay the sending of the control (Ping) frame, since the socket is busy"); |
498 | 0 | hasPendingPingWrite_ = true; |
499 | 0 | pendingWritePingBuffer_ = buffer; |
500 | 0 | } |
501 | 0 | if (opcode == OpCode::Pong) { |
502 | 0 | LOG_DEBUG("doControlFrameWrite: Delay the sending of the control (Pong) frame, since the socket is busy"); |
503 | 0 | hasPendingPongWrite_ = true; |
504 | 0 | pendingWritePongBuffer_ = buffer; |
505 | 0 | } |
506 | 0 | return; |
507 | 0 | } |
508 | | |
509 | 0 | isWritingToSocket_ = true; |
510 | |
|
511 | 0 | doSocketWrite(buffer, opcode); |
512 | 0 | } |
513 | | |
514 | | void WebSocketConnection::handleAsyncWritten(OpCode type, const AsioWrapper::error_code& e, std::size_t bytes_transferred) |
515 | 0 | { |
516 | 0 | if (e) { |
517 | 0 | LOG_ERROR("handleAsyncWritten: error encountered " << e.value()); |
518 | 0 | return; |
519 | 0 | } |
520 | | |
521 | | // Check for functionality outside write-done loop (ping/pong), framework driven |
522 | 0 | std::unique_lock<std::mutex> lock(writingMutex_); |
523 | | |
524 | | // Only perform a singular write of a delayed frame per handler. |
525 | 0 | if (hasPendingPingWrite_) { |
526 | | // A control write (ping) event took place while the socket was busy sending the data frame. |
527 | 0 | LOG_DEBUG("handleAsyncWritten: Handling a delayed Ping frame"); |
528 | 0 | doSocketWrite(pendingWritePingBuffer_, OpCode::Ping); |
529 | 0 | hasPendingPingWrite_ = false; |
530 | 0 | pendingWritePingBuffer_.clear(); |
531 | 0 | } else if (hasPendingPongWrite_) { |
532 | | // A control write (pong) event took place while the socket was busy sending the data frame. |
533 | 0 | LOG_DEBUG("handleAsyncWritten: Handling a delayed Pong frame"); |
534 | 0 | doSocketWrite(pendingWritePongBuffer_, OpCode::Pong); |
535 | 0 | hasPendingPongWrite_ = false; |
536 | 0 | pendingWritePongBuffer_.clear(); |
537 | 0 | }else if (hasPendingDataWrite_) { |
538 | 0 | LOG_DEBUG("handleAsyncControl: Handling a delayed data frame after: " << OpCodeToString(type)); |
539 | 0 | doSocketWrite(pendingWriteDataBuffer_, pendingWriteDataType_); |
540 | 0 | hasPendingDataWrite_ = false; |
541 | 0 | pendingWriteDataBuffer_.clear(); |
542 | 0 | } |
543 | |
|
544 | 0 | isWriting_= false; |
545 | 0 | isWritingToSocket_ = false; |
546 | 0 | writeBuffer_.clear(); |
547 | |
|
548 | 0 | LOG_DEBUG("handleAsyncWritten: Outgoing frame of size: " << bytes_transferred << " bytes has been written"); |
549 | |
|
550 | 0 | if (type == OpCode::Binary || type == OpCode::Text || type == OpCode::Close) { |
551 | 0 | hasDataWrittenCallback_(e, bytes_transferred); |
552 | 0 | } |
553 | 0 | } |
554 | | |
555 | | void WebSocketConnection::setDataReadCallback(const std::function<void()>& callback) |
556 | 0 | { |
557 | 0 | hasDataReadCallback_ = callback; |
558 | 0 | } |
559 | | |
560 | | void WebSocketConnection::setDataWrittenCallback(const std::function<void(const AsioWrapper::error_code&, std::size_t)>& callback) |
561 | 0 | { |
562 | 0 | hasDataWrittenCallback_ = callback; |
563 | 0 | } |
564 | | |
565 | | bool WebSocketConnection::isOpen() |
566 | 0 | { |
567 | 0 | return socket().is_open(); |
568 | 0 | } |
569 | | |
570 | | void WebSocketConnection::setMaximumReceivedFrameSize(std::size_t frameSize) |
571 | 0 | { |
572 | 0 | frameSize_ = frameSize; |
573 | 0 | } |
574 | | |
575 | | void WebSocketConnection::setMaximumReceivedMessageSize(std::size_t messageSize) |
576 | 0 | { |
577 | 0 | messageSize_ = messageSize; |
578 | 0 | } |
579 | | |
580 | | WebSocketTcpConnection::WebSocketTcpConnection(AsioWrapper::asio::io_service& ioService, std::unique_ptr<Socket> socket) |
581 | 0 | : WebSocketConnection(ioService), |
582 | 0 | socket_(std::move(socket)) |
583 | 0 | { |
584 | 0 | } |
585 | | |
586 | | WebSocketTcpConnection::~WebSocketTcpConnection() |
587 | 0 | { |
588 | 0 | if (isOpen()) { |
589 | 0 | doClose(); |
590 | 0 | } |
591 | 0 | } |
592 | | |
593 | | void WebSocketTcpConnection::doClose() |
594 | 0 | { |
595 | 0 | try { |
596 | 0 | AsioWrapper::error_code ignored_ec; |
597 | 0 | auto handle = socket().native_handle(); |
598 | 0 | socket().cancel(); |
599 | 0 | socket().shutdown(AsioWrapper::asio::ip::tcp::socket::shutdown_both, ignored_ec); |
600 | 0 | socket().close(); |
601 | 0 | LOG_INFO("Socket " << handle << " was shut down correctly."); |
602 | 0 | } catch (AsioWrapper::system_error& e) { |
603 | 0 | LOG_DEBUG("Socket " << socket().native_handle() << " encountered an error while shutting down: " << e.what()); |
604 | 0 | } |
605 | 0 | } |
606 | | |
607 | | void WebSocketTcpConnection::doSocketRead(char* buffer, size_t size) |
608 | 0 | { |
609 | 0 | auto ptr = std::static_pointer_cast<WebSocketTcpConnection>(shared_from_this()); |
610 | |
|
611 | 0 | socket_->async_read_some(AsioWrapper::asio::buffer(buffer, size), |
612 | 0 | [ptr](const AsioWrapper::error_code& e, std::size_t bytes_transferred) { |
613 | 0 | asio::dispatch(ptr->strand_, std::bind(&WebSocketTcpConnection::handleAsyncRead, ptr, e, bytes_transferred)); |
614 | 0 | }); |
615 | 0 | } |
616 | | |
617 | | void WebSocketTcpConnection::doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type) |
618 | 0 | { |
619 | 0 | auto ptr = std::static_pointer_cast<WebSocketTcpConnection>(shared_from_this()); |
620 | 0 | async_write(socket(), buffer, |
621 | 0 | [ptr, type](const AsioWrapper::error_code& e, std::size_t bytes_transferred) { |
622 | 0 | asio::dispatch(ptr->strand_, std::bind(&WebSocketTcpConnection::handleAsyncWritten, ptr, type, e, bytes_transferred)); |
623 | 0 | }); |
624 | 0 | } |
625 | | |
626 | | Socket& WebSocketTcpConnection::socket() |
627 | 0 | { |
628 | 0 | return *socket_.get(); |
629 | 0 | } |
630 | | |
631 | | #ifdef WT_WITH_SSL |
632 | | WebSocketSslConnection::WebSocketSslConnection(AsioWrapper::asio::io_service& ioService, std::unique_ptr<SSLSocket> socket) |
633 | | : WebSocketConnection(ioService), |
634 | | socket_(std::move(socket)) |
635 | | { |
636 | | } |
637 | | |
638 | | WebSocketSslConnection::~WebSocketSslConnection() |
639 | | { |
640 | | if (isOpen()) { |
641 | | doClose(); |
642 | | } |
643 | | } |
644 | | |
645 | | void WebSocketSslConnection::doClose() |
646 | | { |
647 | | // Non-static pointer |
648 | | std::shared_ptr<WebSocketSslConnection> ptr = std::static_pointer_cast<WebSocketSslConnection>(shared_from_this()); |
649 | | |
650 | | socket_->async_shutdown([ptr](const AsioWrapper::error_code& e) { |
651 | | asio::dispatch(ptr->strand_, std::bind(&WebSocketSslConnection::stopTcpSocket, ptr, e)); |
652 | | }); |
653 | | } |
654 | | |
655 | | void WebSocketSslConnection::stopTcpSocket(const AsioWrapper::error_code& e) |
656 | | { |
657 | | if (e) { |
658 | | LOG_ERROR("doClose: SSL socket shutdown failed " << e.value()); |
659 | | } |
660 | | |
661 | | try { |
662 | | AsioWrapper::error_code ignored_ec; |
663 | | auto handle = socket().native_handle(); |
664 | | socket().cancel(); |
665 | | socket().shutdown(AsioWrapper::asio::ip::tcp::socket::shutdown_both, ignored_ec); |
666 | | socket().close(); |
667 | | LOG_INFO("Socket " << handle << " was shut down correctly."); |
668 | | } catch (AsioWrapper::system_error& e) { |
669 | | LOG_DEBUG("Socket " << socket().native_handle() << " encountered an error while shutting down: " << e.what()); |
670 | | } |
671 | | |
672 | | } |
673 | | |
674 | | void WebSocketSslConnection::doSocketRead(char* buffer, size_t size) |
675 | | { |
676 | | auto ptr = std::static_pointer_cast<WebSocketSslConnection>(shared_from_this()); |
677 | | socket_->async_read_some(AsioWrapper::asio::buffer(buffer, size), |
678 | | [ptr](const AsioWrapper::error_code& e, std::size_t bytes_transferred) { |
679 | | asio::dispatch(ptr->strand_, std::bind(&WebSocketSslConnection::handleAsyncRead, ptr, e, bytes_transferred)); |
680 | | }); |
681 | | } |
682 | | |
683 | | void WebSocketSslConnection::doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type) |
684 | | { |
685 | | auto ptr = std::static_pointer_cast<WebSocketSslConnection>(shared_from_this()); |
686 | | async_write(*socket_, buffer, |
687 | | [ptr, type](const AsioWrapper::error_code& e, std::size_t bytes_transferred) { |
688 | | asio::dispatch(ptr->strand_, std::bind(&WebSocketSslConnection::handleAsyncWritten, ptr, type, e, bytes_transferred)); |
689 | | }); |
690 | | } |
691 | | |
692 | | Socket& WebSocketSslConnection::socket() |
693 | | { |
694 | | return socket_->next_layer(); |
695 | | } |
696 | | #endif |
697 | | |
698 | | WWebSocketConnection::WWebSocketConnection(WWebSocketResource* resource, AsioWrapper::asio::io_service& ioService) |
699 | 0 | : resource_(resource), |
700 | 0 | wantsToClose_(false), |
701 | 0 | frameSize_(0), |
702 | 0 | messageSize_(0), |
703 | 0 | takesUpdateLock_(true), |
704 | 0 | pingInterval_(180), |
705 | 0 | pingTimeout_(360), |
706 | 0 | pingSignalTimer_(ioService), |
707 | 0 | pongTimeoutTimer_(ioService) |
708 | 0 | { |
709 | 0 | LOG_DEBUG("New connection to track on endpoint: " << resource->url()); |
710 | 0 | } |
711 | | |
712 | | WWebSocketConnection::~WWebSocketConnection() |
713 | 0 | { |
714 | 0 | AsioWrapper::error_code ignored_ec; |
715 | 0 | closeSocket(ignored_ec, ""); |
716 | 0 | } |
717 | | |
718 | | void WWebSocketConnection::setSocket(const std::shared_ptr<WebSocketConnection>& connection) |
719 | 0 | { |
720 | 0 | socketConnection_ = connection; |
721 | 0 | socketConnection_->setMaximumReceivedFrameSize(frameSize_); |
722 | 0 | socketConnection_->setMaximumReceivedMessageSize(messageSize_); |
723 | |
|
724 | 0 | socketConnection_->startReading(); |
725 | | // Set up listeners to socket changes (written & read) |
726 | 0 | socketConnection_->setDataWrittenCallback(std::bind(&WWebSocketConnection::writeFrame, this, std::placeholders::_1, std::placeholders::_2)); |
727 | 0 | socketConnection_->setDataReadCallback(std::bind(&WWebSocketConnection::receiveFrame, this)); |
728 | 0 | } |
729 | | |
730 | | void WWebSocketConnection::handleMessage(const std::string& text) |
731 | 0 | { |
732 | 0 | LOG_INFO("handleMessage(text): text of length: " << std::to_string(text.size())); |
733 | 0 | } |
734 | | |
735 | | void WWebSocketConnection::handleMessage(const std::vector<char>& buffer) |
736 | 0 | { |
737 | 0 | LOG_INFO("handleMessage(binary): array of length: " + std::to_string(buffer.size())); |
738 | 0 | } |
739 | | |
740 | | bool WWebSocketConnection::sendMessage(const std::string& text) |
741 | 0 | { |
742 | 0 | std::vector<char> buff; |
743 | 0 | std::copy(text.begin(), text.end(), std::back_inserter(buff)); |
744 | 0 | return sendDataFrame(buff, OpCode::Text); |
745 | 0 | } |
746 | | |
747 | | bool WWebSocketConnection::sendMessage(const std::vector<char>& buffer) |
748 | 0 | { |
749 | 0 | return sendDataFrame(buffer, OpCode::Binary); |
750 | 0 | } |
751 | | |
752 | | bool WWebSocketConnection::close(WT_MAYBE_UNUSED CloseCode code, const std::string& reason) |
753 | 0 | { |
754 | 0 | wantsToClose_ = true; |
755 | |
|
756 | 0 | if (reason.empty()) { |
757 | 0 | return sendEmptyFrame(OpCode::Close); |
758 | 0 | } |
759 | | |
760 | 0 | std::vector<char> buff; |
761 | 0 | std::copy(reason.begin(), reason.end(), std::back_inserter(buff)); |
762 | 0 | return sendDataFrame(buff, OpCode::Close); |
763 | 0 | } |
764 | | |
765 | | bool WWebSocketConnection::sendDataFrame(const std::vector<char>& data, OpCode opcode) |
766 | 0 | { |
767 | 0 | WebSocketFrameHeader header; |
768 | 0 | header.isFinished = true; |
769 | 0 | header.reserved1 = false; |
770 | 0 | header.reserved2 = false; |
771 | 0 | header.reserved3 = false; |
772 | 0 | header.opCode = opcode; |
773 | 0 | header.isMasked = false; |
774 | |
|
775 | 0 | std::vector<char> frameHeader = header.generateFrameHeader(data.size());; |
776 | |
|
777 | 0 | LOG_DEBUG("sendDataFrame: writing data frame with a header of " << frameHeader.size() << " bytes and data of " << data.size() << " bytes"); |
778 | |
|
779 | 0 | return socketConnection_->doAsyncWrite(opcode, frameHeader, data); |
780 | 0 | } |
781 | | |
782 | | bool WWebSocketConnection::sendEmptyFrame(OpCode opcode) |
783 | 0 | { |
784 | 0 | if (opcode == OpCode::Ping || opcode == OpCode::Pong) { |
785 | 0 | LOG_WARN("sendEmptyFrame: sending a ping or pong frame inside the write/done loop. This will trigger the done() signal upon successfully writing."); |
786 | 0 | } |
787 | |
|
788 | 0 | WebSocketFrameHeader header; |
789 | 0 | header.isFinished = true; |
790 | 0 | header.reserved1 = false; |
791 | 0 | header.reserved2 = false; |
792 | 0 | header.reserved3 = false; |
793 | 0 | header.opCode = opcode; |
794 | 0 | header.isMasked = false; |
795 | |
|
796 | 0 | std::vector<char> frameHeader = header.generateFrameHeader(0); |
797 | |
|
798 | 0 | LOG_DEBUG("sendEmptyFrame: writing a control frame with a header of " << frameHeader.size() << " bytes and OpCode: " << OpCodeToString(header.opCode)); |
799 | |
|
800 | 0 | return socketConnection_->doAsyncWrite(opcode, frameHeader); |
801 | 0 | } |
802 | | |
803 | | bool WWebSocketConnection::sendControlFrame(OpCode opcode) |
804 | 0 | { |
805 | 0 | if (opcode == OpCode::Close) { |
806 | 0 | LOG_WARN("sendControlFrame: sending a close frame outside the write/done loop. This will NOT trigger the done() or closed() signals upon successfully writing."); |
807 | 0 | } |
808 | |
|
809 | 0 | WebSocketFrameHeader header; |
810 | 0 | header.isFinished = true; |
811 | 0 | header.reserved1 = false; |
812 | 0 | header.reserved2 = false; |
813 | 0 | header.reserved3 = false; |
814 | 0 | header.opCode = opcode; |
815 | 0 | header.isMasked = false; |
816 | |
|
817 | 0 | std::vector<char> frameHeader = header.generateFrameHeader(0); |
818 | |
|
819 | 0 | LOG_DEBUG("sendControlFrame: writing a control frame with a header of " << frameHeader.size() << " bytes and OpCode: " << OpCodeToString(header.opCode)); |
820 | |
|
821 | 0 | socketConnection_->doControlFrameWrite(frameHeader, opcode); |
822 | 0 | return true; |
823 | 0 | } |
824 | | |
825 | | void WWebSocketConnection::writeFrame(const AsioWrapper::error_code& e, std::size_t bytes_transferred) |
826 | 0 | { |
827 | 0 | LOG_DEBUG("writeFrame: written data of size: " << bytes_transferred << " bytes"); |
828 | |
|
829 | 0 | std::unique_ptr<WApplication::UpdateLock> updateLock; |
830 | 0 | if (takesUpdateLock_ && app()) { |
831 | 0 | updateLock.reset(new Wt::WApplication::UpdateLock(app())); |
832 | 0 | if (!*updateLock) { |
833 | 0 | LOG_ERROR("writeFrame: Cannot take the update lock"); |
834 | 0 | return; |
835 | 0 | } |
836 | 0 | } |
837 | | |
838 | 0 | done_.emit(e); |
839 | 0 | updateLock.reset(nullptr); |
840 | 0 | } |
841 | | |
842 | | void WWebSocketConnection::receiveFrame() |
843 | 0 | { |
844 | 0 | WebSocketFrameHeader* header = socketConnection_->header(); |
845 | |
|
846 | 0 | std::string data; |
847 | 0 | std::string rawData = socketConnection_->dataBuffer().str(); |
848 | 0 | std::size_t dataSize = rawData.size(); |
849 | 0 | data.reserve(dataSize); |
850 | |
|
851 | 0 | if (header->isMasked) { |
852 | 0 | for (unsigned int i = 0; i < dataSize; ++i) { |
853 | 0 | data += (rawData[i] ^ header->mask()[(i) % 4]); |
854 | 0 | } |
855 | 0 | } else { |
856 | 0 | data += rawData; |
857 | 0 | } |
858 | |
|
859 | 0 | if (socketConnection_->skipMessage()) { |
860 | 0 | LOG_DEBUG("receiveFrame: Received a finished frame or message, that exceeded the received frame limit (" << header->length() << " > " << frameSize_ << " (bytes))"); |
861 | 0 | return; |
862 | 0 | } |
863 | | |
864 | | // Continuation frame case |
865 | 0 | if (!header->isFinished) { |
866 | | // Remember current unmasked data for when frame finishes. |
867 | 0 | continuationBuffer_.append(data.c_str(), data.size()); |
868 | 0 | if (header->opCode != OpCode::Continuation) { |
869 | 0 | continuationOpCode_ = header->opCode; |
870 | 0 | } |
871 | 0 | LOG_DEBUG("Received a non-finished frame, and expect a continuation. Current OpCode: " << OpCodeToString(continuationOpCode_) << " and data size: " << data.size()); |
872 | 0 | return; |
873 | 0 | } else if (header->isFinished && header->opCode == OpCode::Continuation) { |
874 | 0 | header->opCode = continuationOpCode_; |
875 | 0 | continuationBuffer_.append(data.c_str(), data.size()); |
876 | |
|
877 | 0 | if (continuationBuffer_.length() > messageSize_) { |
878 | 0 | LOG_ERROR("receiveFrame: A message was received that exceeded the limit (" << continuationBuffer_.length() << " > " << messageSize_ << " (bytes))"); |
879 | 0 | continuationBuffer_.clear(); |
880 | 0 | return; |
881 | 0 | } |
882 | | |
883 | 0 | data = continuationBuffer_.str(); |
884 | 0 | LOG_DEBUG("Received a finished frame, after a continuation. Current OpCode: " << OpCodeToString(continuationOpCode_) << " and data size: " << data.size()); |
885 | 0 | continuationBuffer_.clear(); |
886 | 0 | } |
887 | | |
888 | | // Handle all non-update locked functionality: |
889 | 0 | if (header->opCode == OpCode::Ping) { |
890 | 0 | acknowledgePing(); |
891 | 0 | return; |
892 | 0 | } else if (header->opCode == OpCode::Pong) { |
893 | 0 | handlePong(); |
894 | 0 | return; |
895 | 0 | } |
896 | | |
897 | 0 | std::unique_ptr<WApplication::UpdateLock> updateLock; |
898 | 0 | if (takesUpdateLock_ && app()) { |
899 | 0 | updateLock.reset(new Wt::WApplication::UpdateLock(app())); |
900 | 0 | if (!*updateLock) { |
901 | 0 | LOG_ERROR("receiveFrame: Cannot take the update lock"); |
902 | 0 | return; |
903 | 0 | } |
904 | 0 | } |
905 | | |
906 | 0 | if (header->opCode == OpCode::Close) { |
907 | 0 | if (wantsToClose_) { |
908 | 0 | AsioWrapper::error_code ignored_ec; |
909 | 0 | closeSocket(ignored_ec, data); |
910 | 0 | } else { |
911 | 0 | acknowledgeClose(data); |
912 | 0 | } |
913 | 0 | } else if (header->opCode == OpCode::Text) { |
914 | 0 | handleMessage(data); |
915 | 0 | } else if (header->opCode == OpCode::Binary) { |
916 | 0 | std::vector<char> binaryData(data.begin(), data.end()); |
917 | 0 | handleMessage(binaryData); |
918 | 0 | } else if (header->opCode == OpCode::Continuation) { |
919 | 0 | handleError(); |
920 | 0 | } |
921 | |
|
922 | 0 | updateLock.reset(nullptr); |
923 | 0 | } |
924 | | |
925 | | void WWebSocketConnection::acknowledgeClose(const std::string& reason) |
926 | 0 | { |
927 | 0 | std::vector<char> buff; |
928 | 0 | std::copy(reason.begin(), reason.end(), std::back_inserter(buff)); |
929 | 0 | sendDataFrame(buff, OpCode::Close); |
930 | 0 | done_.connect(this, std::bind(&WWebSocketConnection::closeSocket, this, std::placeholders::_1, reason)); |
931 | 0 | } |
932 | | |
933 | | void WWebSocketConnection::doSendPing(const AsioWrapper::error_code& e) |
934 | 0 | { |
935 | 0 | if (e) { |
936 | | // Normal shutdown |
937 | 0 | if (e == AsioWrapper::asio::error::operation_aborted) { |
938 | 0 | return; |
939 | 0 | } |
940 | 0 | LOG_ERROR("doSendPing: encountered an error " << e.value()); |
941 | 0 | return; |
942 | 0 | } |
943 | | |
944 | 0 | sendPing(); |
945 | 0 | } |
946 | | |
947 | | bool WWebSocketConnection::sendPing() |
948 | 0 | { |
949 | 0 | if (pingTimeout_ > 0) { |
950 | 0 | pongTimeoutTimer_.expires_after(std::chrono::seconds(pingTimeout_)); |
951 | 0 | pongTimeoutTimer_.async_wait(std::bind(&WWebSocketConnection::missingPong, this, std::placeholders::_1)); |
952 | 0 | } |
953 | |
|
954 | 0 | return sendControlFrame(OpCode::Ping); |
955 | 0 | } |
956 | | |
957 | | void WWebSocketConnection::acknowledgePing() |
958 | 0 | { |
959 | 0 | sendControlFrame(OpCode::Pong); |
960 | 0 | startPingTimer(); |
961 | 0 | } |
962 | | |
963 | | void WWebSocketConnection::handleError() |
964 | 0 | { |
965 | 0 | LOG_ERROR("handleError: A had request was caught, currently specifically used for a bad OpCode."); |
966 | 0 | } |
967 | | |
968 | | void WWebSocketConnection::handlePong() |
969 | 0 | { |
970 | 0 | LOG_DEBUG("handlePong: a Pong frame was received."); |
971 | 0 | startPingTimer(); |
972 | 0 | } |
973 | | |
974 | | void WWebSocketConnection::missingPong(const AsioWrapper::error_code& e) |
975 | 0 | { |
976 | 0 | if (e) { |
977 | | // Normal shutdown |
978 | 0 | if (e == AsioWrapper::asio::error::operation_aborted) { |
979 | 0 | return; |
980 | 0 | } |
981 | 0 | LOG_ERROR("missingPong: encountered an error " << e.value()); |
982 | 0 | return; |
983 | 0 | } |
984 | | |
985 | 0 | LOG_ERROR("missingPong: the socket has send a Ping request, but received no Pong after " << pingTimeout_ << " seconds."); |
986 | 0 | AsioWrapper::error_code ignored_ec; |
987 | 0 | closeSocket(ignored_ec, "Missing Pong"); |
988 | 0 | } |
989 | | |
990 | | void WWebSocketConnection::setMaximumReceivedSize(std::size_t frameSize, std::size_t messageSize) |
991 | 0 | { |
992 | 0 | frameSize_ = frameSize; |
993 | 0 | messageSize_ = messageSize; |
994 | |
|
995 | 0 | if (socketConnection_) { |
996 | 0 | socketConnection_->setMaximumReceivedFrameSize(frameSize_); |
997 | 0 | socketConnection_->setMaximumReceivedMessageSize(messageSize_); |
998 | 0 | } |
999 | 0 | } |
1000 | | |
1001 | | void WWebSocketConnection::setTakesUpdateLock(bool takesUpdateLock) |
1002 | 0 | { |
1003 | 0 | takesUpdateLock_ = takesUpdateLock; |
1004 | 0 | } |
1005 | | |
1006 | | void WWebSocketConnection::setPingTimeout(int pingInterval, int pingTimeout) |
1007 | 0 | { |
1008 | 0 | pingInterval_ = pingInterval; |
1009 | 0 | pingTimeout_ = pingTimeout; |
1010 | |
|
1011 | 0 | startPingTimer(); |
1012 | 0 | } |
1013 | | |
1014 | | void WWebSocketConnection::startPingTimer() |
1015 | 0 | { |
1016 | 0 | pingSignalTimer_.cancel(); |
1017 | 0 | pongTimeoutTimer_.cancel(); |
1018 | | |
1019 | | // Socket "keep-alive", performed through ping-pong mechanism. |
1020 | 0 | if (pingInterval_ > 0) { |
1021 | 0 | pingSignalTimer_.expires_after(std::chrono::seconds(pingInterval_)); |
1022 | 0 | pingSignalTimer_.async_wait(std::bind(&WWebSocketConnection::doSendPing, this, std::placeholders::_1)); |
1023 | 0 | } |
1024 | 0 | } |
1025 | | |
1026 | | void WWebSocketConnection::closeSocket(const AsioWrapper::error_code& e, const std::string& reason) |
1027 | 0 | { |
1028 | 0 | if (e) { |
1029 | 0 | LOG_ERROR("closeSocket: encountered an error: " << e.value()); |
1030 | 0 | return; |
1031 | 0 | } |
1032 | | |
1033 | 0 | pingSignalTimer_.cancel(); |
1034 | 0 | pongTimeoutTimer_.cancel(); |
1035 | |
|
1036 | 0 | if (socketConnection_->isOpen()) { |
1037 | 0 | socketConnection_->doClose(); |
1038 | 0 | } |
1039 | 0 | if (takesUpdateLock_ && app()) { |
1040 | 0 | WApplication::UpdateLock lock(app()); |
1041 | 0 | if (lock) { |
1042 | 0 | closed_.emit(e, reason); |
1043 | 0 | } |
1044 | 0 | } else { |
1045 | 0 | closed_.emit(e, reason); |
1046 | 0 | } |
1047 | 0 | } |
1048 | | |
1049 | | WApplication* WWebSocketConnection::app() |
1050 | 0 | { |
1051 | 0 | if (resource_) { |
1052 | 0 | return resource_->app_; |
1053 | 0 | } |
1054 | 0 | return nullptr; |
1055 | 0 | } |
1056 | | } |
1057 | | |