opal-up 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +209 -0
  3. data/README.md +97 -29
  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 +1344 -0
  43. data/ext/up_ext/libuwebsockets.h +396 -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 +930 -0
  48. data/lib/up/bun/rack_env.rb +1 -13
  49. data/lib/up/bun/server.rb +93 -19
  50. data/lib/up/cli.rb +3 -0
  51. data/lib/up/client.rb +68 -0
  52. data/lib/up/ruby/cluster.rb +39 -0
  53. data/lib/up/ruby/cluster_cli.rb +10 -0
  54. data/lib/up/{node → ruby}/rack_cluster.rb +5 -4
  55. data/lib/up/{node → ruby}/rack_server.rb +4 -4
  56. data/lib/up/ruby/server_cli.rb +10 -0
  57. data/lib/up/u_web_socket/cluster.rb +18 -3
  58. data/lib/up/u_web_socket/server.rb +108 -15
  59. data/lib/up/version.rb +1 -1
  60. metadata +72 -30
  61. data/.gitignore +0 -5
  62. data/Gemfile +0 -2
  63. data/bin/up_node +0 -12
  64. data/bin/up_node_cluster +0 -12
  65. data/example_rack_app/Gemfile +0 -3
  66. data/example_rack_app/config.ru +0 -6
  67. data/example_rack_app/rack_app.rb +0 -5
  68. data/example_roda_app/Gemfile +0 -6
  69. data/example_roda_app/config.ru +0 -6
  70. data/example_roda_app/roda_app.rb +0 -37
  71. data/example_sinatra_app/Gemfile +0 -6
  72. data/example_sinatra_app/config.ru +0 -6
  73. data/example_sinatra_app/sinatra_app.rb +0 -7
  74. data/lib/up/node/cluster.rb +0 -39
  75. data/lib/up/node/cluster_cli.rb +0 -15
  76. data/lib/up/node/rack_env.rb +0 -106
  77. data/lib/up/node/server.rb +0 -84
  78. data/lib/up/node/server_cli.rb +0 -15
  79. data/lib/up/u_web_socket/rack_env.rb +0 -101
  80. data/opal-up.gemspec +0 -27
  81. data/up_logo.svg +0 -256
data/ext/up_ext/App.h ADDED
@@ -0,0 +1,606 @@
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_APP_H
19
+ #define UWS_APP_H
20
+
21
+ #include <string>
22
+ #include <charconv>
23
+ #include <string_view>
24
+
25
+ namespace uWS {
26
+ /* Safari 15.0 - 15.3 has a completely broken compression implementation (client_no_context_takeover not
27
+ * properly implemented) - so we fully disable compression for this browser :-(
28
+ * see https://github.com/uNetworking/uWebSockets/issues/1347 */
29
+ inline bool hasBrokenCompression(std::string_view userAgent) {
30
+ size_t posStart = userAgent.find(" Version/15.");
31
+ if (posStart == std::string_view::npos) return false;
32
+ posStart += 12;
33
+
34
+ size_t posEnd = userAgent.find(' ', posStart);
35
+ if (posEnd == std::string_view::npos) return false;
36
+
37
+ unsigned int minorVersion = 0;
38
+ auto result = std::from_chars(userAgent.data() + posStart, userAgent.data() + posEnd, minorVersion);
39
+ if (result.ec != std::errc()) return false;
40
+ if (result.ptr != userAgent.data() + posEnd) return false; // do not accept trailing chars
41
+ if (minorVersion > 3) return false; // we target just Safari 15.0 - 15.3
42
+
43
+ if (userAgent.find(" Safari/", posEnd) == std::string_view::npos) return false;
44
+
45
+ return true;
46
+ }
47
+ }
48
+
49
+ /* An app is a convenience wrapper of some of the most used fuctionalities and allows a
50
+ * builder-pattern kind of init. Apps operate on the implicit thread local Loop */
51
+
52
+ #include "HttpContext.h"
53
+ #include "HttpResponse.h"
54
+ #include "WebSocketContext.h"
55
+ #include "WebSocket.h"
56
+ #include "PerMessageDeflate.h"
57
+
58
+ namespace uWS {
59
+
60
+ /* This one matches us_socket_context_options_t but has default values */
61
+ struct SocketContextOptions {
62
+ const char *key_file_name = nullptr;
63
+ const char *cert_file_name = nullptr;
64
+ const char *passphrase = nullptr;
65
+ const char *dh_params_file_name = nullptr;
66
+ const char *ca_file_name = nullptr;
67
+ const char *ssl_ciphers = nullptr;
68
+ int ssl_prefer_low_memory_usage = 0;
69
+
70
+ /* Conversion operator used internally */
71
+ operator struct us_socket_context_options_t() const {
72
+ struct us_socket_context_options_t socket_context_options;
73
+ memcpy(&socket_context_options, this, sizeof(SocketContextOptions));
74
+ return socket_context_options;
75
+ }
76
+ };
77
+
78
+ static_assert(sizeof(struct us_socket_context_options_t) == sizeof(SocketContextOptions), "Mismatching uSockets/uWebSockets ABI");
79
+
80
+ template <bool SSL>
81
+ struct TemplatedApp {
82
+ private:
83
+ /* The app always owns at least one http context, but creates websocket contexts on demand */
84
+ HttpContext<SSL> *httpContext;
85
+ /* WebSocketContexts are of differing type, but we as owners and creators must delete them correctly */
86
+ std::vector<MoveOnlyFunction<void()>> webSocketContextDeleters;
87
+
88
+ std::vector<void *> webSocketContexts;
89
+
90
+ public:
91
+
92
+ TopicTree<TopicTreeMessage, TopicTreeBigMessage> *topicTree = nullptr;
93
+
94
+ /* Server name */
95
+ TemplatedApp &&addServerName(std::string hostname_pattern, SocketContextOptions options = {}) {
96
+
97
+ /* Do nothing if not even on SSL */
98
+ if constexpr (SSL) {
99
+ /* First we create a new router for this domain */
100
+ auto *domainRouter = new HttpRouter<typename HttpContextData<SSL>::RouterData>();
101
+
102
+ us_socket_context_add_server_name(SSL, (struct us_socket_context_t *) httpContext, hostname_pattern.c_str(), options, domainRouter);
103
+ }
104
+
105
+ return std::move(*this);
106
+ }
107
+
108
+ TemplatedApp &&removeServerName(std::string hostname_pattern) {
109
+
110
+ /* This will do for now, would be better if us_socket_context_remove_server_name returned the user data */
111
+ auto *domainRouter = us_socket_context_find_server_name_userdata(SSL, (struct us_socket_context_t *) httpContext, hostname_pattern.c_str());
112
+ if (domainRouter) {
113
+ delete (HttpRouter<typename HttpContextData<SSL>::RouterData> *) domainRouter;
114
+ }
115
+
116
+ us_socket_context_remove_server_name(SSL, (struct us_socket_context_t *) httpContext, hostname_pattern.c_str());
117
+ return std::move(*this);
118
+ }
119
+
120
+ TemplatedApp &&missingServerName(MoveOnlyFunction<void(const char *hostname)> handler) {
121
+
122
+ if (!constructorFailed()) {
123
+ httpContext->getSocketContextData()->missingServerNameHandler = std::move(handler);
124
+
125
+ us_socket_context_on_server_name(SSL, (struct us_socket_context_t *) httpContext, [](struct us_socket_context_t *context, const char *hostname) {
126
+
127
+ /* This is the only requirements of being friends with HttpContextData */
128
+ HttpContext<SSL> *httpContext = (HttpContext<SSL> *) context;
129
+ httpContext->getSocketContextData()->missingServerNameHandler(hostname);
130
+ });
131
+ }
132
+
133
+ return std::move(*this);
134
+ }
135
+
136
+ /* Returns the SSL_CTX of this app, or nullptr. */
137
+ void *getNativeHandle() {
138
+ return us_socket_context_get_native_handle(SSL, (struct us_socket_context_t *) httpContext);
139
+ }
140
+
141
+ /* Attaches a "filter" function to track socket connections/disconnections */
142
+ TemplatedApp &&filter(MoveOnlyFunction<void(HttpResponse<SSL> *, int)> &&filterHandler) {
143
+ httpContext->filter(std::move(filterHandler));
144
+
145
+ return std::move(*this);
146
+ }
147
+
148
+ /* Publishes a message to all websocket contexts - conceptually as if publishing to the one single
149
+ * TopicTree of this app (technically there are many TopicTrees, however the concept is that one
150
+ * app has one conceptual Topic tree) */
151
+ bool publish(std::string_view topic, std::string_view message, OpCode opCode, bool compress = false) {
152
+ /* Anything big bypasses corking efforts */
153
+ if (message.length() >= LoopData::CORK_BUFFER_SIZE) {
154
+ return topicTree->publishBig(nullptr, topic, {message, opCode, compress}, [](Subscriber *s, TopicTreeBigMessage &message) {
155
+ auto *ws = (WebSocket<SSL, true, int> *) s->user;
156
+
157
+ /* Send will drain if needed */
158
+ ws->send(message.message, (OpCode)message.opCode, message.compress);
159
+ });
160
+ } else {
161
+ return topicTree->publish(nullptr, topic, {std::string(message), opCode, compress});
162
+ }
163
+ }
164
+
165
+ /* Returns number of subscribers for this topic, or 0 for failure.
166
+ * This function should probably be optimized a lot in future releases,
167
+ * it could be O(1) with a hash map of fullnames and their counts. */
168
+ unsigned int numSubscribers(std::string_view topic) {
169
+ Topic *t = topicTree->lookupTopic(topic);
170
+ if (t) {
171
+ return (unsigned int) t->size();
172
+ }
173
+
174
+ return 0;
175
+ }
176
+
177
+ ~TemplatedApp() {
178
+ /* Let's just put everything here */
179
+ if (httpContext) {
180
+ httpContext->free();
181
+
182
+ /* Free all our webSocketContexts in a type less way */
183
+ for (auto &webSocketContextDeleter : webSocketContextDeleters) {
184
+ webSocketContextDeleter();
185
+ }
186
+ }
187
+
188
+ /* Delete TopicTree */
189
+ if (topicTree) {
190
+ delete topicTree;
191
+
192
+ /* And unregister loop callbacks */
193
+ /* We must unregister any loop post handler here */
194
+ Loop::get()->removePostHandler(topicTree);
195
+ Loop::get()->removePreHandler(topicTree);
196
+ }
197
+ }
198
+
199
+ /* Disallow copying, only move */
200
+ TemplatedApp(const TemplatedApp &other) = delete;
201
+
202
+ TemplatedApp(TemplatedApp &&other) {
203
+ /* Move HttpContext */
204
+ httpContext = other.httpContext;
205
+ other.httpContext = nullptr;
206
+
207
+ /* Move webSocketContextDeleters */
208
+ webSocketContextDeleters = std::move(other.webSocketContextDeleters);
209
+
210
+ webSocketContexts = std::move(other.webSocketContexts);
211
+
212
+ /* Move TopicTree */
213
+ topicTree = other.topicTree;
214
+ other.topicTree = nullptr;
215
+ }
216
+
217
+ TemplatedApp(SocketContextOptions options = {}) {
218
+ httpContext = HttpContext<SSL>::create(Loop::get(), options);
219
+
220
+ /* Register default handler for 404 (can be overridden by user) */
221
+ this->any("/*", [](auto *res, auto */*req*/) {
222
+ res->writeStatus("404 File Not Found");
223
+ res->end("<html><body><h1>File Not Found</h1><hr><i>uWebSockets/20 Server</i></body></html>");
224
+ });
225
+ }
226
+
227
+ bool constructorFailed() {
228
+ return !httpContext;
229
+ }
230
+
231
+ template <typename UserData>
232
+ struct WebSocketBehavior {
233
+ /* Disabled compression by default - probably a bad default */
234
+ CompressOptions compression = DISABLED;
235
+ /* Maximum message size we can receive */
236
+ unsigned int maxPayloadLength = 16 * 1024;
237
+ /* 2 minutes timeout is good */
238
+ unsigned short idleTimeout = 120;
239
+ /* 64kb backpressure is probably good */
240
+ unsigned int maxBackpressure = 64 * 1024;
241
+ bool closeOnBackpressureLimit = false;
242
+ /* This one depends on kernel timeouts and is a bad default */
243
+ bool resetIdleTimeoutOnSend = false;
244
+ /* A good default, esp. for newcomers */
245
+ bool sendPingsAutomatically = true;
246
+ /* Maximum socket lifetime in minutes before forced closure (defaults to disabled) */
247
+ unsigned short maxLifetime = 0;
248
+ MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *, struct us_socket_context_t *)> upgrade = nullptr;
249
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *)> open = nullptr;
250
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *, std::string_view, OpCode)> message = nullptr;
251
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *, std::string_view, OpCode)> dropped = nullptr;
252
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *)> drain = nullptr;
253
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *, std::string_view)> ping = nullptr;
254
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *, std::string_view)> pong = nullptr;
255
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *, std::string_view, int, int)> subscription = nullptr;
256
+ MoveOnlyFunction<void(WebSocket<SSL, true, UserData> *, int, std::string_view)> close = nullptr;
257
+ };
258
+
259
+ /* Closes all sockets including listen sockets. */
260
+ TemplatedApp &&close() {
261
+ us_socket_context_close(SSL, (struct us_socket_context_t *) httpContext);
262
+ for (void *webSocketContext : webSocketContexts) {
263
+ us_socket_context_close(SSL, (struct us_socket_context_t *) webSocketContext);
264
+ }
265
+
266
+ return std::move(*this);
267
+ }
268
+
269
+ template <typename UserData>
270
+ TemplatedApp &&ws(std::string pattern, WebSocketBehavior<UserData> &&behavior) {
271
+ /* Don't compile if alignment rules cannot be satisfied */
272
+ static_assert(alignof(UserData) <= LIBUS_EXT_ALIGNMENT,
273
+ "µWebSockets cannot satisfy UserData alignment requirements. You need to recompile µSockets with LIBUS_EXT_ALIGNMENT adjusted accordingly.");
274
+
275
+ if (!httpContext) {
276
+ return std::move(*this);
277
+ }
278
+
279
+ /* Terminate on misleading idleTimeout values */
280
+ if (behavior.idleTimeout && behavior.idleTimeout < 8) {
281
+ std::cerr << "Error: idleTimeout must be either 0 or greater than 8!" << std::endl;
282
+ std::terminate();
283
+ }
284
+
285
+ /* Maximum idleTimeout is 16 minutes */
286
+ if (behavior.idleTimeout > 240 * 4) {
287
+ std::cerr << "Error: idleTimeout must not be greater than 960 seconds!" << std::endl;
288
+ std::terminate();
289
+ }
290
+
291
+ /* Maximum maxLifetime is 4 hours */
292
+ if (behavior.maxLifetime > 240) {
293
+ std::cerr << "Error: maxLifetime must not be greater than 240 minutes!" << std::endl;
294
+ std::terminate();
295
+ }
296
+
297
+ /* If we don't have a TopicTree yet, create one now */
298
+ if (!topicTree) {
299
+
300
+ bool needsUncork = false;
301
+ topicTree = new TopicTree<TopicTreeMessage, TopicTreeBigMessage>([needsUncork](Subscriber *s, TopicTreeMessage &message, TopicTree<TopicTreeMessage, TopicTreeBigMessage>::IteratorFlags flags) mutable {
302
+ /* Subscriber's user is the socket */
303
+ /* Unfortunately we need to cast is to PerSocketData = int
304
+ * since many different WebSocketContexts use the same
305
+ * TopicTree now */
306
+ auto *ws = (WebSocket<SSL, true, int> *) s->user;
307
+
308
+ /* If this is the first message we try and cork */
309
+ if (flags & TopicTree<TopicTreeMessage, TopicTreeBigMessage>::IteratorFlags::FIRST) {
310
+ if (ws->canCork() && !ws->isCorked()) {
311
+ ((AsyncSocket<SSL> *)ws)->cork();
312
+ needsUncork = true;
313
+ }
314
+ }
315
+
316
+ /* If we ever overstep maxBackpresure, exit immediately */
317
+ if (WebSocket<SSL, true, int>::SendStatus::DROPPED == ws->send(message.message, (OpCode)message.opCode, message.compress)) {
318
+ if (needsUncork) {
319
+ ((AsyncSocket<SSL> *)ws)->uncork();
320
+ needsUncork = false;
321
+ }
322
+ /* Stop draining */
323
+ return true;
324
+ }
325
+
326
+ /* If this is the last message we uncork if we are corked */
327
+ if (flags & TopicTree<TopicTreeMessage, TopicTreeBigMessage>::IteratorFlags::LAST) {
328
+ /* We should not uncork in all cases? */
329
+ if (needsUncork) {
330
+ ((AsyncSocket<SSL> *)ws)->uncork();
331
+ }
332
+ }
333
+
334
+ /* Success */
335
+ return false;
336
+ });
337
+
338
+ /* And hook it up with the loop */
339
+ /* We empty for both pre and post just to make sure */
340
+ Loop::get()->addPostHandler(topicTree, [topicTree = topicTree](Loop */*loop*/) {
341
+ /* Commit pub/sub batches every loop iteration */
342
+ topicTree->drain();
343
+ });
344
+
345
+ Loop::get()->addPreHandler(topicTree, [topicTree = topicTree](Loop */*loop*/) {
346
+ /* Commit pub/sub batches every loop iteration */
347
+ topicTree->drain();
348
+ });
349
+ }
350
+
351
+ /* Every route has its own websocket context with its own behavior and user data type */
352
+ auto *webSocketContext = WebSocketContext<SSL, true, UserData>::create(Loop::get(), (us_socket_context_t *) httpContext, topicTree);
353
+
354
+ /* We need to clear this later on */
355
+ webSocketContextDeleters.push_back([webSocketContext]() {
356
+ webSocketContext->free();
357
+ });
358
+
359
+ /* We also keep this list for easy closing */
360
+ webSocketContexts.push_back((void *)webSocketContext);
361
+
362
+ /* Quick fix to disable any compression if set */
363
+ #ifdef UWS_NO_ZLIB
364
+ behavior.compression = DISABLED;
365
+ #endif
366
+
367
+ /* If we are the first one to use compression, initialize it */
368
+ if (behavior.compression) {
369
+ LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(SSL, webSocketContext->getSocketContext()));
370
+
371
+ /* Initialize loop's deflate inflate streams */
372
+ if (!loopData->zlibContext) {
373
+ loopData->zlibContext = new ZlibContext;
374
+ loopData->inflationStream = new InflationStream(CompressOptions::DEDICATED_DECOMPRESSOR);
375
+ loopData->deflationStream = new DeflationStream(CompressOptions::DEDICATED_COMPRESSOR);
376
+ }
377
+ }
378
+
379
+ /* Copy all handlers */
380
+ webSocketContext->getExt()->openHandler = std::move(behavior.open);
381
+ webSocketContext->getExt()->messageHandler = std::move(behavior.message);
382
+ webSocketContext->getExt()->droppedHandler = std::move(behavior.dropped);
383
+ webSocketContext->getExt()->drainHandler = std::move(behavior.drain);
384
+ webSocketContext->getExt()->subscriptionHandler = std::move(behavior.subscription);
385
+ webSocketContext->getExt()->closeHandler = std::move([closeHandler = std::move(behavior.close)](WebSocket<SSL, true, UserData> *ws, int code, std::string_view message) mutable {
386
+ if (closeHandler) {
387
+ closeHandler(ws, code, message);
388
+ }
389
+
390
+ /* Destruct user data after returning from close handler */
391
+ ((UserData *) ws->getUserData())->~UserData();
392
+ });
393
+ webSocketContext->getExt()->pingHandler = std::move(behavior.ping);
394
+ webSocketContext->getExt()->pongHandler = std::move(behavior.pong);
395
+
396
+ /* Copy settings */
397
+ webSocketContext->getExt()->maxPayloadLength = behavior.maxPayloadLength;
398
+ webSocketContext->getExt()->maxBackpressure = behavior.maxBackpressure;
399
+ webSocketContext->getExt()->closeOnBackpressureLimit = behavior.closeOnBackpressureLimit;
400
+ webSocketContext->getExt()->resetIdleTimeoutOnSend = behavior.resetIdleTimeoutOnSend;
401
+ webSocketContext->getExt()->sendPingsAutomatically = behavior.sendPingsAutomatically;
402
+ webSocketContext->getExt()->maxLifetime = behavior.maxLifetime;
403
+ webSocketContext->getExt()->compression = behavior.compression;
404
+
405
+ /* Calculate idleTimeoutCompnents */
406
+ webSocketContext->getExt()->calculateIdleTimeoutCompnents(behavior.idleTimeout);
407
+
408
+ httpContext->onHttp("GET", pattern, [webSocketContext, behavior = std::move(behavior)](auto *res, auto *req) mutable {
409
+
410
+ /* If we have this header set, it's a websocket */
411
+ std::string_view secWebSocketKey = req->getHeader("sec-websocket-key");
412
+ if (secWebSocketKey.length() == 24) {
413
+
414
+ /* Emit upgrade handler */
415
+ if (behavior.upgrade) {
416
+
417
+ /* Nasty, ugly Safari 15 hack */
418
+ if (hasBrokenCompression(req->getHeader("user-agent"))) {
419
+ std::string_view secWebSocketExtensions = req->getHeader("sec-websocket-extensions");
420
+ memset((void *) secWebSocketExtensions.data(), ' ', secWebSocketExtensions.length());
421
+ }
422
+
423
+ behavior.upgrade(res, req, (struct us_socket_context_t *) webSocketContext);
424
+ } else {
425
+ /* Default handler upgrades to WebSocket */
426
+ std::string_view secWebSocketProtocol = req->getHeader("sec-websocket-protocol");
427
+ std::string_view secWebSocketExtensions = req->getHeader("sec-websocket-extensions");
428
+
429
+ /* Safari 15 hack */
430
+ if (hasBrokenCompression(req->getHeader("user-agent"))) {
431
+ secWebSocketExtensions = "";
432
+ }
433
+
434
+ res->template upgrade<UserData>({}, secWebSocketKey, secWebSocketProtocol, secWebSocketExtensions, (struct us_socket_context_t *) webSocketContext);
435
+ }
436
+
437
+ /* We are going to get uncorked by the Http get return */
438
+
439
+ /* We do not need to check for any close or shutdown here as we immediately return from get handler */
440
+
441
+ } else {
442
+ /* Tell the router that we did not handle this request */
443
+ req->setYield(true);
444
+ }
445
+ }, true);
446
+ return std::move(*this);
447
+ }
448
+
449
+ /* Browse to a server name, changing the router to this domain */
450
+ TemplatedApp &&domain(std::string serverName) {
451
+ HttpContextData<SSL> *httpContextData = httpContext->getSocketContextData();
452
+
453
+ void *domainRouter = us_socket_context_find_server_name_userdata(SSL, (struct us_socket_context_t *) httpContext, serverName.c_str());
454
+ if (domainRouter) {
455
+ std::cout << "Browsed to SNI: " << serverName << std::endl;
456
+ httpContextData->currentRouter = (decltype(httpContextData->currentRouter)) domainRouter;
457
+ } else {
458
+ std::cout << "Cannot browse to SNI: " << serverName << std::endl;
459
+ httpContextData->currentRouter = &httpContextData->router;
460
+ }
461
+
462
+ return std::move(*this);
463
+ }
464
+
465
+ TemplatedApp &&get(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
466
+ if (httpContext) {
467
+ httpContext->onHttp("GET", pattern, std::move(handler));
468
+ }
469
+ return std::move(*this);
470
+ }
471
+
472
+ TemplatedApp &&post(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
473
+ if (httpContext) {
474
+ httpContext->onHttp("POST", pattern, std::move(handler));
475
+ }
476
+ return std::move(*this);
477
+ }
478
+
479
+ TemplatedApp &&options(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
480
+ if (httpContext) {
481
+ httpContext->onHttp("OPTIONS", pattern, std::move(handler));
482
+ }
483
+ return std::move(*this);
484
+ }
485
+
486
+ TemplatedApp &&del(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
487
+ if (httpContext) {
488
+ httpContext->onHttp("DELETE", pattern, std::move(handler));
489
+ }
490
+ return std::move(*this);
491
+ }
492
+
493
+ TemplatedApp &&patch(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
494
+ if (httpContext) {
495
+ httpContext->onHttp("PATCH", pattern, std::move(handler));
496
+ }
497
+ return std::move(*this);
498
+ }
499
+
500
+ TemplatedApp &&put(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
501
+ if (httpContext) {
502
+ httpContext->onHttp("PUT", pattern, std::move(handler));
503
+ }
504
+ return std::move(*this);
505
+ }
506
+
507
+ TemplatedApp &&head(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
508
+ if (httpContext) {
509
+ httpContext->onHttp("HEAD", pattern, std::move(handler));
510
+ }
511
+ return std::move(*this);
512
+ }
513
+
514
+ TemplatedApp &&connect(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
515
+ if (httpContext) {
516
+ httpContext->onHttp("CONNECT", pattern, std::move(handler));
517
+ }
518
+ return std::move(*this);
519
+ }
520
+
521
+ TemplatedApp &&trace(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
522
+ if (httpContext) {
523
+ httpContext->onHttp("TRACE", pattern, std::move(handler));
524
+ }
525
+ return std::move(*this);
526
+ }
527
+
528
+ /* This one catches any method */
529
+ TemplatedApp &&any(std::string pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
530
+ if (httpContext) {
531
+ httpContext->onHttp("*", pattern, std::move(handler));
532
+ }
533
+ return std::move(*this);
534
+ }
535
+
536
+ /* Host, port, callback */
537
+ TemplatedApp &&listen(std::string host, int port, MoveOnlyFunction<void(us_listen_socket_t *)> &&handler) {
538
+ if (!host.length()) {
539
+ return listen(port, std::move(handler));
540
+ }
541
+ handler(httpContext ? httpContext->listen(host.c_str(), port, 0) : nullptr);
542
+ return std::move(*this);
543
+ }
544
+
545
+ /* Host, port, options, callback */
546
+ TemplatedApp &&listen(std::string host, int port, int options, MoveOnlyFunction<void(us_listen_socket_t *)> &&handler) {
547
+ if (!host.length()) {
548
+ return listen(port, options, std::move(handler));
549
+ }
550
+ handler(httpContext ? httpContext->listen(host.c_str(), port, options) : nullptr);
551
+ return std::move(*this);
552
+ }
553
+
554
+ /* Port, callback */
555
+ TemplatedApp &&listen(int port, MoveOnlyFunction<void(us_listen_socket_t *)> &&handler) {
556
+ handler(httpContext ? httpContext->listen(nullptr, port, 0) : nullptr);
557
+ return std::move(*this);
558
+ }
559
+
560
+ /* Port, options, callback */
561
+ TemplatedApp &&listen(int port, int options, MoveOnlyFunction<void(us_listen_socket_t *)> &&handler) {
562
+ handler(httpContext ? httpContext->listen(nullptr, port, options) : nullptr);
563
+ return std::move(*this);
564
+ }
565
+
566
+ /* options, callback, path to unix domain socket */
567
+ TemplatedApp &&listen(int options, MoveOnlyFunction<void(us_listen_socket_t *)> &&handler, std::string path) {
568
+ handler(httpContext ? httpContext->listen(path.c_str(), options) : nullptr);
569
+ return std::move(*this);
570
+ }
571
+
572
+ /* callback, path to unix domain socket */
573
+ TemplatedApp &&listen(MoveOnlyFunction<void(us_listen_socket_t *)> &&handler, std::string path) {
574
+ handler(httpContext ? httpContext->listen(path.c_str(), 0) : nullptr);
575
+ return std::move(*this);
576
+ }
577
+
578
+ /* Register event handler for accepted FD. Can be used together with adoptSocket. */
579
+ TemplatedApp &&preOpen(LIBUS_SOCKET_DESCRIPTOR (*handler)(LIBUS_SOCKET_DESCRIPTOR)) {
580
+ httpContext->onPreOpen(handler);
581
+ return std::move(*this);
582
+ }
583
+
584
+ /* adopt an externally accepted socket */
585
+ TemplatedApp &&adoptSocket(LIBUS_SOCKET_DESCRIPTOR accepted_fd) {
586
+ httpContext->adoptAcceptedSocket(accepted_fd);
587
+ return std::move(*this);
588
+ }
589
+
590
+ TemplatedApp &&run() {
591
+ uWS::run();
592
+ return std::move(*this);
593
+ }
594
+
595
+ Loop *getLoop() {
596
+ return (Loop *) httpContext->getLoop();
597
+ }
598
+
599
+ };
600
+
601
+ typedef TemplatedApp<false> App;
602
+ typedef TemplatedApp<true> SSLApp;
603
+
604
+ }
605
+
606
+ #endif // UWS_APP_H