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.
- checksums.yaml +4 -4
- data/LICENSE +209 -0
- data/README.md +97 -29
- data/bin/up_ruby +4 -0
- data/bin/up_ruby_cluster +4 -0
- data/ext/up_ext/App.h +606 -0
- data/ext/up_ext/AsyncSocket.h +355 -0
- data/ext/up_ext/AsyncSocketData.h +87 -0
- data/ext/up_ext/BloomFilter.h +83 -0
- data/ext/up_ext/ChunkedEncoding.h +236 -0
- data/ext/up_ext/ClientApp.h +36 -0
- data/ext/up_ext/HttpContext.h +502 -0
- data/ext/up_ext/HttpContextData.h +56 -0
- data/ext/up_ext/HttpErrors.h +53 -0
- data/ext/up_ext/HttpParser.h +680 -0
- data/ext/up_ext/HttpResponse.h +578 -0
- data/ext/up_ext/HttpResponseData.h +95 -0
- data/ext/up_ext/HttpRouter.h +380 -0
- data/ext/up_ext/Loop.h +204 -0
- data/ext/up_ext/LoopData.h +112 -0
- data/ext/up_ext/MoveOnlyFunction.h +377 -0
- data/ext/up_ext/PerMessageDeflate.h +315 -0
- data/ext/up_ext/ProxyParser.h +163 -0
- data/ext/up_ext/QueryParser.h +120 -0
- data/ext/up_ext/TopicTree.h +363 -0
- data/ext/up_ext/Utilities.h +66 -0
- data/ext/up_ext/WebSocket.h +381 -0
- data/ext/up_ext/WebSocketContext.h +434 -0
- data/ext/up_ext/WebSocketContextData.h +109 -0
- data/ext/up_ext/WebSocketData.h +86 -0
- data/ext/up_ext/WebSocketExtensions.h +256 -0
- data/ext/up_ext/WebSocketHandshake.h +145 -0
- data/ext/up_ext/WebSocketProtocol.h +506 -0
- data/ext/up_ext/bsd.c +767 -0
- data/ext/up_ext/bsd.h +109 -0
- data/ext/up_ext/context.c +524 -0
- data/ext/up_ext/epoll_kqueue.c +458 -0
- data/ext/up_ext/epoll_kqueue.h +67 -0
- data/ext/up_ext/extconf.rb +5 -0
- data/ext/up_ext/internal.h +224 -0
- data/ext/up_ext/libusockets.h +350 -0
- data/ext/up_ext/libuwebsockets.cpp +1344 -0
- data/ext/up_ext/libuwebsockets.h +396 -0
- data/ext/up_ext/loop.c +386 -0
- data/ext/up_ext/loop_data.h +38 -0
- data/ext/up_ext/socket.c +231 -0
- data/ext/up_ext/up_ext.c +930 -0
- data/lib/up/bun/rack_env.rb +1 -13
- data/lib/up/bun/server.rb +93 -19
- data/lib/up/cli.rb +3 -0
- data/lib/up/client.rb +68 -0
- data/lib/up/ruby/cluster.rb +39 -0
- data/lib/up/ruby/cluster_cli.rb +10 -0
- data/lib/up/{node → ruby}/rack_cluster.rb +5 -4
- data/lib/up/{node → ruby}/rack_server.rb +4 -4
- data/lib/up/ruby/server_cli.rb +10 -0
- data/lib/up/u_web_socket/cluster.rb +18 -3
- data/lib/up/u_web_socket/server.rb +108 -15
- data/lib/up/version.rb +1 -1
- metadata +72 -30
- data/.gitignore +0 -5
- data/Gemfile +0 -2
- data/bin/up_node +0 -12
- data/bin/up_node_cluster +0 -12
- data/example_rack_app/Gemfile +0 -3
- data/example_rack_app/config.ru +0 -6
- data/example_rack_app/rack_app.rb +0 -5
- data/example_roda_app/Gemfile +0 -6
- data/example_roda_app/config.ru +0 -6
- data/example_roda_app/roda_app.rb +0 -37
- data/example_sinatra_app/Gemfile +0 -6
- data/example_sinatra_app/config.ru +0 -6
- data/example_sinatra_app/sinatra_app.rb +0 -7
- data/lib/up/node/cluster.rb +0 -39
- data/lib/up/node/cluster_cli.rb +0 -15
- data/lib/up/node/rack_env.rb +0 -106
- data/lib/up/node/server.rb +0 -84
- data/lib/up/node/server_cli.rb +0 -15
- data/lib/up/u_web_socket/rack_env.rb +0 -101
- data/opal-up.gemspec +0 -27
- 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
|