opal-up 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +209 -0
  3. data/README.md +81 -28
  4. data/bin/up_ruby +4 -0
  5. data/bin/up_ruby_cluster +4 -0
  6. data/ext/up_ext/App.h +606 -0
  7. data/ext/up_ext/AsyncSocket.h +355 -0
  8. data/ext/up_ext/AsyncSocketData.h +87 -0
  9. data/ext/up_ext/BloomFilter.h +83 -0
  10. data/ext/up_ext/ChunkedEncoding.h +236 -0
  11. data/ext/up_ext/ClientApp.h +36 -0
  12. data/ext/up_ext/HttpContext.h +502 -0
  13. data/ext/up_ext/HttpContextData.h +56 -0
  14. data/ext/up_ext/HttpErrors.h +53 -0
  15. data/ext/up_ext/HttpParser.h +680 -0
  16. data/ext/up_ext/HttpResponse.h +578 -0
  17. data/ext/up_ext/HttpResponseData.h +95 -0
  18. data/ext/up_ext/HttpRouter.h +380 -0
  19. data/ext/up_ext/Loop.h +204 -0
  20. data/ext/up_ext/LoopData.h +112 -0
  21. data/ext/up_ext/MoveOnlyFunction.h +377 -0
  22. data/ext/up_ext/PerMessageDeflate.h +315 -0
  23. data/ext/up_ext/ProxyParser.h +163 -0
  24. data/ext/up_ext/QueryParser.h +120 -0
  25. data/ext/up_ext/TopicTree.h +363 -0
  26. data/ext/up_ext/Utilities.h +66 -0
  27. data/ext/up_ext/WebSocket.h +381 -0
  28. data/ext/up_ext/WebSocketContext.h +434 -0
  29. data/ext/up_ext/WebSocketContextData.h +109 -0
  30. data/ext/up_ext/WebSocketData.h +86 -0
  31. data/ext/up_ext/WebSocketExtensions.h +256 -0
  32. data/ext/up_ext/WebSocketHandshake.h +145 -0
  33. data/ext/up_ext/WebSocketProtocol.h +506 -0
  34. data/ext/up_ext/bsd.c +767 -0
  35. data/ext/up_ext/bsd.h +109 -0
  36. data/ext/up_ext/context.c +524 -0
  37. data/ext/up_ext/epoll_kqueue.c +458 -0
  38. data/ext/up_ext/epoll_kqueue.h +67 -0
  39. data/ext/up_ext/extconf.rb +5 -0
  40. data/ext/up_ext/internal.h +224 -0
  41. data/ext/up_ext/libusockets.h +350 -0
  42. data/ext/up_ext/libuwebsockets.cpp +1374 -0
  43. data/ext/up_ext/libuwebsockets.h +260 -0
  44. data/ext/up_ext/loop.c +386 -0
  45. data/ext/up_ext/loop_data.h +38 -0
  46. data/ext/up_ext/socket.c +231 -0
  47. data/ext/up_ext/up_ext.c +278 -0
  48. data/lib/up/node/rack_env.rb +2 -2
  49. data/lib/up/ruby/cluster_cli.rb +10 -0
  50. data/lib/up/ruby/rack_cluster.rb +26 -0
  51. data/lib/up/ruby/rack_env.rb +97 -0
  52. data/lib/up/ruby/rack_server.rb +26 -0
  53. data/lib/up/ruby/server_cli.rb +10 -0
  54. data/lib/up/u_web_socket/rack_env.rb +1 -1
  55. data/lib/up/version.rb +1 -1
  56. metadata +71 -18
  57. data/.gitignore +0 -5
  58. data/Gemfile +0 -2
  59. data/example_rack_app/Gemfile +0 -3
  60. data/example_rack_app/config.ru +0 -6
  61. data/example_rack_app/rack_app.rb +0 -5
  62. data/example_roda_app/Gemfile +0 -6
  63. data/example_roda_app/config.ru +0 -6
  64. data/example_roda_app/roda_app.rb +0 -37
  65. data/example_sinatra_app/Gemfile +0 -6
  66. data/example_sinatra_app/config.ru +0 -6
  67. data/example_sinatra_app/sinatra_app.rb +0 -7
  68. data/opal-up.gemspec +0 -27
  69. data/up_logo.svg +0 -256
@@ -0,0 +1,434 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2020.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #ifndef UWS_WEBSOCKETCONTEXT_H
19
+ #define UWS_WEBSOCKETCONTEXT_H
20
+
21
+ #include "WebSocketContextData.h"
22
+ #include "WebSocketProtocol.h"
23
+ #include "WebSocketData.h"
24
+ #include "WebSocket.h"
25
+
26
+ namespace uWS {
27
+
28
+ template <bool SSL, bool isServer, typename USERDATA>
29
+ struct WebSocketContext {
30
+ template <bool> friend struct TemplatedApp;
31
+ template <bool, typename> friend struct WebSocketProtocol;
32
+ private:
33
+ WebSocketContext() = delete;
34
+
35
+ us_socket_context_t *getSocketContext() {
36
+ return (us_socket_context_t *) this;
37
+ }
38
+
39
+ WebSocketContextData<SSL, USERDATA> *getExt() {
40
+ return (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, (us_socket_context_t *) this);
41
+ }
42
+
43
+ /* If we have negotiated compression, set this frame compressed */
44
+ static bool setCompressed(WebSocketState<isServer> */*wState*/, void *s) {
45
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) s);
46
+
47
+ if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::ENABLED) {
48
+ webSocketData->compressionStatus = WebSocketData::CompressionStatus::COMPRESSED_FRAME;
49
+ return true;
50
+ } else {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ static void forceClose(WebSocketState<isServer> */*wState*/, void *s, std::string_view reason = {}) {
56
+ us_socket_close(SSL, (us_socket_t *) s, (int) reason.length(), (void *) reason.data());
57
+ }
58
+
59
+ /* Returns true on breakage */
60
+ static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, WebSocketState<isServer> *webSocketState, void *s) {
61
+ /* WebSocketData and WebSocketContextData */
62
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
63
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) s);
64
+
65
+ /* Is this a non-control frame? */
66
+ if (opCode < 3) {
67
+ /* Did we get everything in one go? */
68
+ if (!remainingBytes && fin && !webSocketData->fragmentBuffer.length()) {
69
+
70
+ /* Handle compressed frame */
71
+ if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) {
72
+ webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED;
73
+
74
+ LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(SSL, us_socket_context(SSL, (us_socket_t *) s)));
75
+ /* Decompress using shared or dedicated decompressor */
76
+ std::optional<std::string_view> inflatedFrame;
77
+ if (webSocketData->inflationStream) {
78
+ inflatedFrame = webSocketData->inflationStream->inflate(loopData->zlibContext, {data, length}, webSocketContextData->maxPayloadLength, false);
79
+ } else {
80
+ inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {data, length}, webSocketContextData->maxPayloadLength, true);
81
+ }
82
+
83
+ if (!inflatedFrame.has_value()) {
84
+ forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE_INFLATION);
85
+ return true;
86
+ } else {
87
+ data = (char *) inflatedFrame->data();
88
+ length = inflatedFrame->length();
89
+ }
90
+ }
91
+
92
+ /* Check text messages for Utf-8 validity */
93
+ if (opCode == 1 && !protocol::isValidUtf8((unsigned char *) data, length)) {
94
+ forceClose(webSocketState, s, ERR_INVALID_TEXT);
95
+ return true;
96
+ }
97
+
98
+ /* Emit message event & break if we are closed or shut down when returning */
99
+ if (webSocketContextData->messageHandler) {
100
+ webSocketContextData->messageHandler((WebSocket<SSL, isServer, USERDATA> *) s, std::string_view(data, length), (OpCode) opCode);
101
+ if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
102
+ return true;
103
+ }
104
+ }
105
+ } else {
106
+ /* Allocate fragment buffer up front first time */
107
+ if (!webSocketData->fragmentBuffer.length()) {
108
+ webSocketData->fragmentBuffer.reserve(length + remainingBytes);
109
+ }
110
+ /* Fragments forming a big message are not caught until appending them */
111
+ if (refusePayloadLength(length + webSocketData->fragmentBuffer.length(), webSocketState, s)) {
112
+ forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE);
113
+ return true;
114
+ }
115
+ webSocketData->fragmentBuffer.append(data, length);
116
+
117
+ /* Are we done now? */
118
+ // todo: what if we don't have any remaining bytes yet we are not fin? forceclose!
119
+ if (!remainingBytes && fin) {
120
+
121
+ /* Handle compression */
122
+ if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) {
123
+ webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED;
124
+
125
+ /* 9 bytes of padding for libdeflate, 4 for zlib */
126
+ webSocketData->fragmentBuffer.append("123456789");
127
+
128
+ LoopData *loopData = (LoopData *) us_loop_ext(
129
+ us_socket_context_loop(SSL,
130
+ us_socket_context(SSL, (us_socket_t *) s)
131
+ )
132
+ );
133
+
134
+ /* Decompress using shared or dedicated decompressor */
135
+ std::optional<std::string_view> inflatedFrame;
136
+ if (webSocketData->inflationStream) {
137
+ inflatedFrame = webSocketData->inflationStream->inflate(loopData->zlibContext, {webSocketData->fragmentBuffer.data(), webSocketData->fragmentBuffer.length() - 9}, webSocketContextData->maxPayloadLength, false);
138
+ } else {
139
+ inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {webSocketData->fragmentBuffer.data(), webSocketData->fragmentBuffer.length() - 9}, webSocketContextData->maxPayloadLength, true);
140
+ }
141
+
142
+ if (!inflatedFrame.has_value()) {
143
+ forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE_INFLATION);
144
+ return true;
145
+ } else {
146
+ data = (char *) inflatedFrame->data();
147
+ length = inflatedFrame->length();
148
+ }
149
+
150
+
151
+ } else {
152
+ // reset length and data ptrs
153
+ length = webSocketData->fragmentBuffer.length();
154
+ data = webSocketData->fragmentBuffer.data();
155
+ }
156
+
157
+ /* Check text messages for Utf-8 validity */
158
+ if (opCode == 1 && !protocol::isValidUtf8((unsigned char *) data, length)) {
159
+ forceClose(webSocketState, s, ERR_INVALID_TEXT);
160
+ return true;
161
+ }
162
+
163
+ /* Emit message and check for shutdown or close */
164
+ if (webSocketContextData->messageHandler) {
165
+ webSocketContextData->messageHandler((WebSocket<SSL, isServer, USERDATA> *) s, std::string_view(data, length), (OpCode) opCode);
166
+ if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
167
+ return true;
168
+ }
169
+ }
170
+
171
+ /* If we shutdown or closed, this will be taken care of elsewhere */
172
+ webSocketData->fragmentBuffer.clear();
173
+ }
174
+ }
175
+ } else {
176
+ /* Control frames need the websocket to send pings, pongs and close */
177
+ WebSocket<SSL, isServer, USERDATA> *webSocket = (WebSocket<SSL, isServer, USERDATA> *) s;
178
+
179
+ if (!remainingBytes && fin && !webSocketData->controlTipLength) {
180
+ if (opCode == CLOSE) {
181
+ auto closeFrame = protocol::parseClosePayload(data, length);
182
+ webSocket->end(closeFrame.code, std::string_view(closeFrame.message, closeFrame.length));
183
+ return true;
184
+ } else {
185
+ if (opCode == PING) {
186
+ webSocket->send(std::string_view(data, length), (OpCode) OpCode::PONG);
187
+ if (webSocketContextData->pingHandler) {
188
+ webSocketContextData->pingHandler(webSocket, {data, length});
189
+ if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
190
+ return true;
191
+ }
192
+ }
193
+ } else if (opCode == PONG) {
194
+ if (webSocketContextData->pongHandler) {
195
+ webSocketContextData->pongHandler(webSocket, {data, length});
196
+ if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
197
+ return true;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ } else {
203
+ /* Here we never mind any size optimizations as we are in the worst possible path */
204
+ webSocketData->fragmentBuffer.append(data, length);
205
+ webSocketData->controlTipLength += (unsigned int) length;
206
+
207
+ if (!remainingBytes && fin) {
208
+ char *controlBuffer = (char *) webSocketData->fragmentBuffer.data() + webSocketData->fragmentBuffer.length() - webSocketData->controlTipLength;
209
+ if (opCode == CLOSE) {
210
+ protocol::CloseFrame closeFrame = protocol::parseClosePayload(controlBuffer, webSocketData->controlTipLength);
211
+ webSocket->end(closeFrame.code, std::string_view(closeFrame.message, closeFrame.length));
212
+ return true;
213
+ } else {
214
+ if (opCode == PING) {
215
+ webSocket->send(std::string_view(controlBuffer, webSocketData->controlTipLength), (OpCode) OpCode::PONG);
216
+ if (webSocketContextData->pingHandler) {
217
+ webSocketContextData->pingHandler(webSocket, std::string_view(controlBuffer, webSocketData->controlTipLength));
218
+ if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
219
+ return true;
220
+ }
221
+ }
222
+ } else if (opCode == PONG) {
223
+ if (webSocketContextData->pongHandler) {
224
+ webSocketContextData->pongHandler(webSocket, std::string_view(controlBuffer, webSocketData->controlTipLength));
225
+ if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
226
+ return true;
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ /* Same here, we do not care for any particular smart allocation scheme */
233
+ webSocketData->fragmentBuffer.resize((unsigned int) webSocketData->fragmentBuffer.length() - webSocketData->controlTipLength);
234
+ webSocketData->controlTipLength = 0;
235
+ }
236
+ }
237
+ }
238
+ return false;
239
+ }
240
+
241
+ static bool refusePayloadLength(uint64_t length, WebSocketState<isServer> */*wState*/, void *s) {
242
+ auto *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
243
+
244
+ /* Return true for refuse, false for accept */
245
+ return webSocketContextData->maxPayloadLength < length;
246
+ }
247
+
248
+ WebSocketContext<SSL, isServer, USERDATA> *init() {
249
+ /* Adopting a socket does not trigger open event.
250
+ * We arreive as WebSocket with timeout set and
251
+ * any backpressure from HTTP state kept. */
252
+
253
+ /* Handle socket disconnections */
254
+ us_socket_context_on_close(SSL, getSocketContext(), [](auto *s, int code, void *reason) {
255
+ /* For whatever reason, if we already have emitted close event, do not emit it again */
256
+ WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s));
257
+ if (!webSocketData->isShuttingDown) {
258
+ /* Emit close event */
259
+ auto *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
260
+
261
+ /* At this point we iterate all currently held subscriptions and emit an event for all of them */
262
+ if (webSocketData->subscriber && webSocketContextData->subscriptionHandler) {
263
+ for (Topic *t : webSocketData->subscriber->topics) {
264
+ webSocketContextData->subscriptionHandler((WebSocket<SSL, isServer, USERDATA> *) s, t->name, (int) t->size() - 1, (int) t->size());
265
+ }
266
+ }
267
+
268
+ /* Make sure to unsubscribe from any pub/sub node at exit */
269
+ webSocketContextData->topicTree->freeSubscriber(webSocketData->subscriber);
270
+ webSocketData->subscriber = nullptr;
271
+
272
+ if (webSocketContextData->closeHandler) {
273
+ webSocketContextData->closeHandler((WebSocket<SSL, isServer, USERDATA> *) s, 1006, {(char *) reason, (size_t) code});
274
+ }
275
+ }
276
+
277
+ /* Destruct in-placed data struct */
278
+ webSocketData->~WebSocketData();
279
+
280
+ return s;
281
+ });
282
+
283
+ /* Handle WebSocket data streams */
284
+ us_socket_context_on_data(SSL, getSocketContext(), [](auto *s, char *data, int length) {
285
+
286
+ /* We need the websocket data */
287
+ WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s));
288
+
289
+ /* When in websocket shutdown mode, we do not care for ANY message, whether responding close frame or not.
290
+ * We only care for the TCP FIN really, not emitting any message after closing is key */
291
+ if (webSocketData->isShuttingDown) {
292
+ return s;
293
+ }
294
+
295
+ auto *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
296
+ auto *asyncSocket = (AsyncSocket<SSL> *) s;
297
+
298
+ /* Every time we get data and not in shutdown state we simply reset the timeout */
299
+ asyncSocket->timeout(webSocketContextData->idleTimeoutComponents.first);
300
+ webSocketData->hasTimedOut = false;
301
+
302
+ /* We always cork on data */
303
+ asyncSocket->cork();
304
+
305
+ /* This parser has virtually no overhead */
306
+ WebSocketProtocol<isServer, WebSocketContext<SSL, isServer, USERDATA>>::consume(data, (unsigned int) length, (WebSocketState<isServer> *) webSocketData, s);
307
+
308
+ /* Uncorking a closed socekt is fine, in fact it is needed */
309
+ asyncSocket->uncork();
310
+
311
+ /* If uncorking was successful and we are in shutdown state then send TCP FIN */
312
+ if (asyncSocket->getBufferedAmount() == 0) {
313
+ /* We can now be in shutdown state */
314
+ if (webSocketData->isShuttingDown) {
315
+ /* Shutting down a closed socket is handled by uSockets and just fine */
316
+ asyncSocket->shutdown();
317
+ }
318
+ }
319
+
320
+ return s;
321
+ });
322
+
323
+ /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */
324
+ us_socket_context_on_writable(SSL, getSocketContext(), [](auto *s) {
325
+
326
+ /* NOTE: Are we called here corked? If so, the below write code is broken, since
327
+ * we will have 0 as getBufferedAmount due to writing to cork buffer, then sending TCP FIN before
328
+ * we actually uncorked and sent off things */
329
+
330
+ /* It makes sense to check for us_is_shut_down here and return if so, to avoid shutting down twice */
331
+ if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
332
+ return s;
333
+ }
334
+
335
+ AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
336
+ WebSocketData *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s));
337
+
338
+ /* We store old backpressure since it is unclear whether write drained anything,
339
+ * however, in case of coming here with 0 backpressure we still need to emit drain event */
340
+ unsigned int backpressure = asyncSocket->getBufferedAmount();
341
+
342
+ /* Drain as much as possible */
343
+ asyncSocket->write(nullptr, 0);
344
+
345
+ /* Behavior: if we actively drain backpressure, always reset timeout (even if we are in shutdown) */
346
+ /* Also reset timeout if we came here with 0 backpressure */
347
+ if (!backpressure || backpressure > asyncSocket->getBufferedAmount()) {
348
+ auto *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
349
+ asyncSocket->timeout(webSocketContextData->idleTimeoutComponents.first);
350
+ webSocketData->hasTimedOut = false;
351
+ }
352
+
353
+ /* Are we in (WebSocket) shutdown mode? */
354
+ if (webSocketData->isShuttingDown) {
355
+ /* Check if we just now drained completely */
356
+ if (asyncSocket->getBufferedAmount() == 0) {
357
+ /* Now perform the actual TCP/TLS shutdown which was postponed due to backpressure */
358
+ asyncSocket->shutdown();
359
+ }
360
+ } else if (!backpressure || backpressure > asyncSocket->getBufferedAmount()) {
361
+ /* Only call drain if we actually drained backpressure or if we came here with 0 backpressure */
362
+ auto *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
363
+ if (webSocketContextData->drainHandler) {
364
+ webSocketContextData->drainHandler((WebSocket<SSL, isServer, USERDATA> *) s);
365
+ }
366
+ /* No need to check for closed here as we leave the handler immediately*/
367
+ }
368
+
369
+ return s;
370
+ });
371
+
372
+ /* Handle FIN, HTTP does not support half-closed sockets, so simply close */
373
+ us_socket_context_on_end(SSL, getSocketContext(), [](auto *s) {
374
+
375
+ /* If we get a fin, we just close I guess */
376
+ us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
377
+
378
+ return s;
379
+ });
380
+
381
+ us_socket_context_on_long_timeout(SSL, getSocketContext(), [](auto *s) {
382
+ ((WebSocket<SSL, isServer, USERDATA> *) s)->end(1000, "please reconnect");
383
+
384
+ return s;
385
+ });
386
+
387
+ /* Handle socket timeouts, simply close them so to not confuse client with FIN */
388
+ us_socket_context_on_timeout(SSL, getSocketContext(), [](auto *s) {
389
+
390
+ auto *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s));
391
+ auto *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
392
+
393
+ if (webSocketContextData->sendPingsAutomatically && !webSocketData->isShuttingDown && !webSocketData->hasTimedOut) {
394
+ webSocketData->hasTimedOut = true;
395
+ us_socket_timeout(SSL, s, webSocketContextData->idleTimeoutComponents.second);
396
+ /* Send ping without being corked */
397
+ ((AsyncSocket<SSL> *) s)->write("\x89\x00", 2);
398
+ return s;
399
+ }
400
+
401
+ /* Timeout is very simple; we just close it */
402
+ /* Warning: we happen to know forceClose will not use first parameter so pass nullptr here */
403
+ forceClose(nullptr, s, ERR_WEBSOCKET_TIMEOUT);
404
+
405
+ return s;
406
+ });
407
+
408
+ return this;
409
+ }
410
+
411
+ void free() {
412
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, (us_socket_context_t *) this);
413
+ webSocketContextData->~WebSocketContextData();
414
+
415
+ us_socket_context_free(SSL, (us_socket_context_t *) this);
416
+ }
417
+
418
+ public:
419
+ /* WebSocket contexts are always child contexts to a HTTP context so no SSL options are needed as they are inherited */
420
+ static WebSocketContext *create(Loop */*loop*/, us_socket_context_t *parentSocketContext, TopicTree<TopicTreeMessage, TopicTreeBigMessage> *topicTree) {
421
+ WebSocketContext *webSocketContext = (WebSocketContext *) us_create_child_socket_context(SSL, parentSocketContext, sizeof(WebSocketContextData<SSL, USERDATA>));
422
+ if (!webSocketContext) {
423
+ return nullptr;
424
+ }
425
+
426
+ /* Init socket context data */
427
+ new ((WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, (us_socket_context_t *)webSocketContext)) WebSocketContextData<SSL, USERDATA>(topicTree);
428
+ return webSocketContext->init();
429
+ }
430
+ };
431
+
432
+ }
433
+
434
+ #endif // UWS_WEBSOCKETCONTEXT_H
@@ -0,0 +1,109 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2020.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #ifndef UWS_WEBSOCKETCONTEXTDATA_H
19
+ #define UWS_WEBSOCKETCONTEXTDATA_H
20
+
21
+ #include "Loop.h"
22
+ #include "AsyncSocket.h"
23
+
24
+ #include "MoveOnlyFunction.h"
25
+ #include <string_view>
26
+ #include <vector>
27
+
28
+ #include "WebSocketProtocol.h"
29
+ #include "TopicTree.h"
30
+ #include "WebSocketData.h"
31
+
32
+ namespace uWS {
33
+
34
+ /* Type queued up when publishing */
35
+ struct TopicTreeMessage {
36
+ std::string message;
37
+ /*OpCode*/ int opCode;
38
+ bool compress;
39
+ };
40
+ struct TopicTreeBigMessage {
41
+ std::string_view message;
42
+ /*OpCode*/ int opCode;
43
+ bool compress;
44
+ };
45
+
46
+ template <bool, bool, typename> struct WebSocket;
47
+
48
+ /* todo: this looks identical to WebSocketBehavior, why not just std::move that entire thing in? */
49
+
50
+ template <bool SSL, typename USERDATA>
51
+ struct WebSocketContextData {
52
+ private:
53
+
54
+ public:
55
+
56
+ /* This one points to the App's shared topicTree */
57
+ TopicTree<TopicTreeMessage, TopicTreeBigMessage> *topicTree;
58
+
59
+ /* The callbacks for this context */
60
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *)> openHandler = nullptr;
61
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *, std::string_view, OpCode)> messageHandler = nullptr;
62
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *, std::string_view, OpCode)> droppedHandler = nullptr;
63
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *)> drainHandler = nullptr;
64
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *, std::string_view, int, int)> subscriptionHandler = nullptr;
65
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *, int, std::string_view)> closeHandler = nullptr;
66
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *, std::string_view)> pingHandler = nullptr;
67
+ MoveOnlyFunction<void(WebSocket<SSL, true, USERDATA> *, std::string_view)> pongHandler = nullptr;
68
+
69
+ /* Settings for this context */
70
+ size_t maxPayloadLength = 0;
71
+
72
+ /* We do need these for async upgrade */
73
+ CompressOptions compression;
74
+
75
+ /* There needs to be a maxBackpressure which will force close everything over that limit */
76
+ size_t maxBackpressure = 0;
77
+ bool closeOnBackpressureLimit;
78
+ bool resetIdleTimeoutOnSend;
79
+ bool sendPingsAutomatically;
80
+ unsigned short maxLifetime;
81
+
82
+ /* These are calculated on creation */
83
+ std::pair<unsigned short, unsigned short> idleTimeoutComponents;
84
+
85
+ /* This is run once on start-up */
86
+ void calculateIdleTimeoutCompnents(unsigned short idleTimeout) {
87
+ unsigned short margin = 4;
88
+ /* 4, 8 or 16 seconds margin based on idleTimeout */
89
+ while ((int) idleTimeout - margin * 2 >= margin * 2 && margin < 16) {
90
+ margin = (unsigned short) (margin << 1);
91
+ }
92
+ idleTimeoutComponents = {
93
+ idleTimeout - (sendPingsAutomatically ? margin : 0), /* reduce normal idleTimeout if it is extended by ping-timeout */
94
+ margin /* ping-timeout - also used for end() timeout */
95
+ };
96
+ }
97
+
98
+ ~WebSocketContextData() {
99
+
100
+ }
101
+
102
+ WebSocketContextData(TopicTree<TopicTreeMessage, TopicTreeBigMessage> *topicTree) : topicTree(topicTree) {
103
+
104
+ }
105
+ };
106
+
107
+ }
108
+
109
+ #endif // UWS_WEBSOCKETCONTEXTDATA_H
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2020.
3
+ * Intellectual property of third-party.
4
+
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #ifndef UWS_WEBSOCKETDATA_H
19
+ #define UWS_WEBSOCKETDATA_H
20
+
21
+ #include "WebSocketProtocol.h"
22
+ #include "AsyncSocketData.h"
23
+ #include "PerMessageDeflate.h"
24
+ #include "TopicTree.h"
25
+
26
+ #include <string>
27
+
28
+ namespace uWS {
29
+
30
+ struct WebSocketData : AsyncSocketData<false>, WebSocketState<true> {
31
+ /* This guy has a lot of friends - why? */
32
+ template <bool, bool, typename> friend struct WebSocketContext;
33
+ template <bool, typename> friend struct WebSocketContextData;
34
+ template <bool, bool, typename> friend struct WebSocket;
35
+ template <bool> friend struct HttpContext;
36
+ private:
37
+ std::string fragmentBuffer;
38
+ unsigned int controlTipLength = 0;
39
+ bool isShuttingDown = 0;
40
+ bool hasTimedOut = false;
41
+ enum CompressionStatus : char {
42
+ DISABLED,
43
+ ENABLED,
44
+ COMPRESSED_FRAME
45
+ } compressionStatus;
46
+
47
+ /* We might have a dedicated compressor */
48
+ DeflationStream *deflationStream = nullptr;
49
+ /* And / or a dedicated decompressor */
50
+ InflationStream *inflationStream = nullptr;
51
+
52
+ /* We could be a subscriber */
53
+ Subscriber *subscriber = nullptr;
54
+ public:
55
+ WebSocketData(bool perMessageDeflate, CompressOptions compressOptions, BackPressure &&backpressure) : AsyncSocketData<false>(std::move(backpressure)), WebSocketState<true>() {
56
+ compressionStatus = perMessageDeflate ? ENABLED : DISABLED;
57
+
58
+ /* Initialize the dedicated sliding window(s) */
59
+ if (perMessageDeflate) {
60
+ if ((compressOptions & CompressOptions::_COMPRESSOR_MASK) != CompressOptions::SHARED_COMPRESSOR) {
61
+ deflationStream = new DeflationStream(compressOptions);
62
+ }
63
+ if ((compressOptions & CompressOptions::_DECOMPRESSOR_MASK) != CompressOptions::SHARED_DECOMPRESSOR) {
64
+ inflationStream = new InflationStream(compressOptions);
65
+ }
66
+ }
67
+ }
68
+
69
+ ~WebSocketData() {
70
+ if (deflationStream) {
71
+ delete deflationStream;
72
+ }
73
+
74
+ if (inflationStream) {
75
+ delete inflationStream;
76
+ }
77
+
78
+ if (subscriber) {
79
+ delete subscriber;
80
+ }
81
+ }
82
+ };
83
+
84
+ }
85
+
86
+ #endif // UWS_WEBSOCKETDATA_H