opal-up 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -51
  3. data/ext/up_ext/App.h +665 -544
  4. data/ext/up_ext/AsyncSocket.h +307 -284
  5. data/ext/up_ext/AsyncSocketData.h +35 -51
  6. data/ext/up_ext/BloomFilter.h +37 -42
  7. data/ext/up_ext/ChunkedEncoding.h +174 -175
  8. data/ext/up_ext/ClientApp.h +20 -23
  9. data/ext/up_ext/HttpContext.h +476 -381
  10. data/ext/up_ext/HttpContextData.h +20 -20
  11. data/ext/up_ext/HttpErrors.h +14 -10
  12. data/ext/up_ext/HttpParser.h +631 -563
  13. data/ext/up_ext/HttpResponse.h +526 -460
  14. data/ext/up_ext/HttpResponseData.h +59 -55
  15. data/ext/up_ext/HttpRouter.h +328 -310
  16. data/ext/up_ext/Loop.h +174 -168
  17. data/ext/up_ext/LoopData.h +60 -67
  18. data/ext/up_ext/MoveOnlyFunction.h +71 -80
  19. data/ext/up_ext/PerMessageDeflate.h +218 -198
  20. data/ext/up_ext/ProxyParser.h +100 -99
  21. data/ext/up_ext/QueryParser.h +91 -84
  22. data/ext/up_ext/TopicTree.h +273 -268
  23. data/ext/up_ext/Utilities.h +25 -25
  24. data/ext/up_ext/WebSocket.h +376 -310
  25. data/ext/up_ext/WebSocketContext.h +487 -372
  26. data/ext/up_ext/WebSocketContextData.h +74 -62
  27. data/ext/up_ext/WebSocketData.h +53 -46
  28. data/ext/up_ext/WebSocketExtensions.h +194 -178
  29. data/ext/up_ext/WebSocketHandshake.h +115 -110
  30. data/ext/up_ext/WebSocketProtocol.h +441 -398
  31. data/ext/up_ext/extconf.rb +1 -1
  32. data/ext/up_ext/libuwebsockets.cpp +1262 -1292
  33. data/ext/up_ext/libuwebsockets.h +337 -201
  34. data/ext/up_ext/up_ext.c +853 -163
  35. data/lib/up/bun/rack_env.rb +1 -13
  36. data/lib/up/bun/server.rb +93 -19
  37. data/lib/up/cli.rb +3 -0
  38. data/lib/up/client.rb +68 -0
  39. data/lib/up/ruby/cluster.rb +62 -0
  40. data/lib/up/ruby/rack_cluster.rb +1 -1
  41. data/lib/up/ruby/rack_server.rb +0 -1
  42. data/lib/up/u_web_socket/cluster.rb +18 -3
  43. data/lib/up/u_web_socket/server.rb +108 -15
  44. data/lib/up/version.rb +1 -1
  45. metadata +4 -15
  46. data/bin/up_node +0 -12
  47. data/bin/up_node_cluster +0 -12
  48. data/lib/up/node/cluster.rb +0 -39
  49. data/lib/up/node/cluster_cli.rb +0 -15
  50. data/lib/up/node/rack_cluster.rb +0 -25
  51. data/lib/up/node/rack_env.rb +0 -106
  52. data/lib/up/node/rack_server.rb +0 -25
  53. data/lib/up/node/server.rb +0 -84
  54. data/lib/up/node/server_cli.rb +0 -15
  55. data/lib/up/ruby/rack_env.rb +0 -97
  56. data/lib/up/u_web_socket/rack_env.rb +0 -101
@@ -18,417 +18,532 @@
18
18
  #ifndef UWS_WEBSOCKETCONTEXT_H
19
19
  #define UWS_WEBSOCKETCONTEXT_H
20
20
 
21
+ #include "WebSocket.h"
21
22
  #include "WebSocketContextData.h"
22
- #include "WebSocketProtocol.h"
23
23
  #include "WebSocketData.h"
24
- #include "WebSocket.h"
24
+ #include "WebSocketProtocol.h"
25
25
 
26
26
  namespace uWS {
27
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
- }
28
+ template <bool SSL, bool isServer, typename USERDATA> struct WebSocketContext {
29
+ template <bool> friend struct TemplatedApp;
30
+ template <bool, typename> friend struct WebSocketProtocol;
38
31
 
39
- WebSocketContextData<SSL, USERDATA> *getExt() {
40
- return (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL, (us_socket_context_t *) this);
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(
41
+ SSL, (us_socket_context_t *)this);
42
+ }
43
+
44
+ /* If we have negotiated compression, set this frame compressed */
45
+ static bool setCompressed(WebSocketState<isServer> * /*wState*/, void *s) {
46
+ WebSocketData *webSocketData =
47
+ (WebSocketData *)us_socket_ext(SSL, (us_socket_t *)s);
48
+
49
+ if (webSocketData->compressionStatus ==
50
+ WebSocketData::CompressionStatus::ENABLED) {
51
+ webSocketData->compressionStatus =
52
+ WebSocketData::CompressionStatus::COMPRESSED_FRAME;
53
+ return true;
54
+ } else {
55
+ return false;
41
56
  }
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;
57
+ }
58
+
59
+ static void forceClose(WebSocketState<isServer> * /*wState*/, void *s,
60
+ std::string_view reason = {}) {
61
+ us_socket_close(SSL, (us_socket_t *)s, (int)reason.length(),
62
+ (void *)reason.data());
63
+ }
64
+
65
+ /* Returns true on breakage */
66
+ static bool handleFragment(char *data, size_t length,
67
+ unsigned int remainingBytes, int opCode, bool fin,
68
+ WebSocketState<isServer> *webSocketState,
69
+ void *s) {
70
+ /* WebSocketData and WebSocketContextData */
71
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData =
72
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
73
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
74
+ WebSocketData *webSocketData =
75
+ (WebSocketData *)us_socket_ext(SSL, (us_socket_t *)s);
76
+
77
+ /* Is this a non-control frame? */
78
+ if (opCode < 3) {
79
+ /* Did we get everything in one go? */
80
+ if (!remainingBytes && fin && !webSocketData->fragmentBuffer.length()) {
81
+
82
+ /* Handle compressed frame */
83
+ if (webSocketData->compressionStatus ==
84
+ WebSocketData::CompressionStatus::COMPRESSED_FRAME) {
85
+ webSocketData->compressionStatus =
86
+ WebSocketData::CompressionStatus::ENABLED;
87
+
88
+ LoopData *loopData = (LoopData *)us_loop_ext(us_socket_context_loop(
89
+ SSL, us_socket_context(SSL, (us_socket_t *)s)));
90
+ /* Decompress using shared or dedicated decompressor */
91
+ std::optional<std::string_view> inflatedFrame;
92
+ if (webSocketData->inflationStream) {
93
+ inflatedFrame = webSocketData->inflationStream->inflate(
94
+ loopData->zlibContext, {data, length},
95
+ webSocketContextData->maxPayloadLength, false);
96
+ } else {
97
+ inflatedFrame = loopData->inflationStream->inflate(
98
+ loopData->zlibContext, {data, length},
99
+ webSocketContextData->maxPayloadLength, true);
100
+ }
101
+
102
+ if (!inflatedFrame.has_value()) {
103
+ forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE_INFLATION);
49
104
  return true;
50
- } else {
51
- return false;
105
+ } else {
106
+ data = (char *)inflatedFrame->data();
107
+ length = inflatedFrame->length();
108
+ }
52
109
  }
53
- }
54
110
 
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
- }
111
+ /* Check text messages for Utf-8 validity */
112
+ if (opCode == 1 &&
113
+ !protocol::isValidUtf8((unsigned char *)data, length)) {
114
+ forceClose(webSocketState, s, ERR_INVALID_TEXT);
115
+ return true;
237
116
  }
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
117
 
272
- if (webSocketContextData->closeHandler) {
273
- webSocketContextData->closeHandler((WebSocket<SSL, isServer, USERDATA> *) s, 1006, {(char *) reason, (size_t) code});
274
- }
118
+ /* Emit message event & break if we are closed or shut down when
119
+ * returning */
120
+ if (webSocketContextData->messageHandler) {
121
+ webSocketContextData->messageHandler(
122
+ (WebSocket<SSL, isServer, USERDATA> *)s,
123
+ std::string_view(data, length), (OpCode)opCode);
124
+ if (us_socket_is_closed(SSL, (us_socket_t *)s) ||
125
+ webSocketData->isShuttingDown) {
126
+ return true;
127
+ }
128
+ }
129
+ } else {
130
+ /* Allocate fragment buffer up front first time */
131
+ if (!webSocketData->fragmentBuffer.length()) {
132
+ webSocketData->fragmentBuffer.reserve(length + remainingBytes);
133
+ }
134
+ /* Fragments forming a big message are not caught until appending them
135
+ */
136
+ if (refusePayloadLength(length + webSocketData->fragmentBuffer.length(),
137
+ webSocketState, s)) {
138
+ forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE);
139
+ return true;
140
+ }
141
+ webSocketData->fragmentBuffer.append(data, length);
142
+
143
+ /* Are we done now? */
144
+ // todo: what if we don't have any remaining bytes yet we are not fin?
145
+ // forceclose!
146
+ if (!remainingBytes && fin) {
147
+
148
+ /* Handle compression */
149
+ if (webSocketData->compressionStatus ==
150
+ WebSocketData::CompressionStatus::COMPRESSED_FRAME) {
151
+ webSocketData->compressionStatus =
152
+ WebSocketData::CompressionStatus::ENABLED;
153
+
154
+ /* 9 bytes of padding for libdeflate, 4 for zlib */
155
+ webSocketData->fragmentBuffer.append("123456789");
156
+
157
+ LoopData *loopData = (LoopData *)us_loop_ext(us_socket_context_loop(
158
+ SSL, us_socket_context(SSL, (us_socket_t *)s)));
159
+
160
+ /* Decompress using shared or dedicated decompressor */
161
+ std::optional<std::string_view> inflatedFrame;
162
+ if (webSocketData->inflationStream) {
163
+ inflatedFrame = webSocketData->inflationStream->inflate(
164
+ loopData->zlibContext,
165
+ {webSocketData->fragmentBuffer.data(),
166
+ webSocketData->fragmentBuffer.length() - 9},
167
+ webSocketContextData->maxPayloadLength, false);
168
+ } else {
169
+ inflatedFrame = loopData->inflationStream->inflate(
170
+ loopData->zlibContext,
171
+ {webSocketData->fragmentBuffer.data(),
172
+ webSocketData->fragmentBuffer.length() - 9},
173
+ webSocketContextData->maxPayloadLength, true);
275
174
  }
276
175
 
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;
176
+ if (!inflatedFrame.has_value()) {
177
+ forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE_INFLATION);
178
+ return true;
179
+ } else {
180
+ data = (char *)inflatedFrame->data();
181
+ length = inflatedFrame->length();
293
182
  }
294
183
 
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();
184
+ } else {
185
+ // reset length and data ptrs
186
+ length = webSocketData->fragmentBuffer.length();
187
+ data = webSocketData->fragmentBuffer.data();
188
+ }
310
189
 
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
- }
190
+ /* Check text messages for Utf-8 validity */
191
+ if (opCode == 1 &&
192
+ !protocol::isValidUtf8((unsigned char *)data, length)) {
193
+ forceClose(webSocketState, s, ERR_INVALID_TEXT);
194
+ return true;
195
+ }
196
+
197
+ /* Emit message and check for shutdown or close */
198
+ if (webSocketContextData->messageHandler) {
199
+ webSocketContextData->messageHandler(
200
+ (WebSocket<SSL, isServer, USERDATA> *)s,
201
+ std::string_view(data, length), (OpCode)opCode);
202
+ if (us_socket_is_closed(SSL, (us_socket_t *)s) ||
203
+ webSocketData->isShuttingDown) {
204
+ return true;
318
205
  }
206
+ }
319
207
 
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;
208
+ /* If we shutdown or closed, this will be taken care of elsewhere */
209
+ webSocketData->fragmentBuffer.clear();
210
+ }
211
+ }
212
+ } else {
213
+ /* Control frames need the websocket to send pings, pongs and close */
214
+ WebSocket<SSL, isServer, USERDATA> *webSocket =
215
+ (WebSocket<SSL, isServer, USERDATA> *)s;
216
+
217
+ if (!remainingBytes && fin && !webSocketData->controlTipLength) {
218
+ if (opCode == CLOSE) {
219
+ auto closeFrame = protocol::parseClosePayload(data, length);
220
+ webSocket->end(closeFrame.code, std::string_view(closeFrame.message,
221
+ closeFrame.length));
222
+ return true;
223
+ } else {
224
+ if (opCode == PING) {
225
+ webSocket->send(std::string_view(data, length),
226
+ (OpCode)OpCode::PONG);
227
+ if (webSocketContextData->pingHandler) {
228
+ webSocketContextData->pingHandler(webSocket, {data, length});
229
+ if (us_socket_is_closed(SSL, (us_socket_t *)s) ||
230
+ webSocketData->isShuttingDown) {
231
+ return true;
232
+ }
333
233
  }
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;
234
+ } else if (opCode == PONG) {
235
+ if (webSocketContextData->pongHandler) {
236
+ webSocketContextData->pongHandler(webSocket, {data, length});
237
+ if (us_socket_is_closed(SSL, (us_socket_t *)s) ||
238
+ webSocketData->isShuttingDown) {
239
+ return true;
240
+ }
351
241
  }
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();
242
+ }
243
+ }
244
+ } else {
245
+ /* Here we never mind any size optimizations as we are in the worst
246
+ * possible path */
247
+ webSocketData->fragmentBuffer.append(data, length);
248
+ webSocketData->controlTipLength += (unsigned int)length;
249
+
250
+ if (!remainingBytes && fin) {
251
+ char *controlBuffer = (char *)webSocketData->fragmentBuffer.data() +
252
+ webSocketData->fragmentBuffer.length() -
253
+ webSocketData->controlTipLength;
254
+ if (opCode == CLOSE) {
255
+ protocol::CloseFrame closeFrame = protocol::parseClosePayload(
256
+ controlBuffer, webSocketData->controlTipLength);
257
+ webSocket->end(
258
+ closeFrame.code,
259
+ std::string_view(closeFrame.message, closeFrame.length));
260
+ return true;
261
+ } else {
262
+ if (opCode == PING) {
263
+ webSocket->send(std::string_view(controlBuffer,
264
+ webSocketData->controlTipLength),
265
+ (OpCode)OpCode::PONG);
266
+ if (webSocketContextData->pingHandler) {
267
+ webSocketContextData->pingHandler(
268
+ webSocket,
269
+ std::string_view(controlBuffer,
270
+ webSocketData->controlTipLength));
271
+ if (us_socket_is_closed(SSL, (us_socket_t *)s) ||
272
+ webSocketData->isShuttingDown) {
273
+ return true;
359
274
  }
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);
275
+ }
276
+ } else if (opCode == PONG) {
277
+ if (webSocketContextData->pongHandler) {
278
+ webSocketContextData->pongHandler(
279
+ webSocket,
280
+ std::string_view(controlBuffer,
281
+ webSocketData->controlTipLength));
282
+ if (us_socket_is_closed(SSL, (us_socket_t *)s) ||
283
+ webSocketData->isShuttingDown) {
284
+ return true;
365
285
  }
366
- /* No need to check for closed here as we leave the handler immediately*/
286
+ }
287
+ }
288
+ }
289
+
290
+ /* Same here, we do not care for any particular smart allocation
291
+ * scheme */
292
+ webSocketData->fragmentBuffer.resize(
293
+ (unsigned int)webSocketData->fragmentBuffer.length() -
294
+ webSocketData->controlTipLength);
295
+ webSocketData->controlTipLength = 0;
296
+ }
297
+ }
298
+ }
299
+ return false;
300
+ }
301
+
302
+ static bool refusePayloadLength(uint64_t length,
303
+ WebSocketState<isServer> * /*wState*/,
304
+ void *s) {
305
+ auto *webSocketContextData =
306
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
307
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
308
+
309
+ /* Return true for refuse, false for accept */
310
+ return webSocketContextData->maxPayloadLength < length;
311
+ }
312
+
313
+ WebSocketContext<SSL, isServer, USERDATA> *init() {
314
+ /* Adopting a socket does not trigger open event.
315
+ * We arreive as WebSocket with timeout set and
316
+ * any backpressure from HTTP state kept. */
317
+
318
+ /* Handle socket disconnections */
319
+ us_socket_context_on_close(
320
+ SSL, getSocketContext(), [](auto *s, int code, void *reason) {
321
+ /* For whatever reason, if we already have emitted close event, do not
322
+ * emit it again */
323
+ WebSocketData *webSocketData =
324
+ (WebSocketData *)(us_socket_ext(SSL, s));
325
+ if (!webSocketData->isShuttingDown) {
326
+ /* Emit close event */
327
+ auto *webSocketContextData =
328
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
329
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
330
+
331
+ /* At this point we iterate all currently held subscriptions and
332
+ * emit an event for all of them */
333
+ if (webSocketData->subscriber &&
334
+ webSocketContextData->subscriptionHandler) {
335
+ for (Topic *t : webSocketData->subscriber->topics) {
336
+ webSocketContextData->subscriptionHandler(
337
+ (WebSocket<SSL, isServer, USERDATA> *)s, t->name,
338
+ (int)t->size() - 1, (int)t->size());
339
+ }
367
340
  }
368
341
 
369
- return s;
370
- });
342
+ /* Make sure to unsubscribe from any pub/sub node at exit */
343
+ webSocketContextData->topicTree->freeSubscriber(
344
+ webSocketData->subscriber);
345
+ webSocketData->subscriber = nullptr;
371
346
 
372
- /* Handle FIN, HTTP does not support half-closed sockets, so simply close */
373
- us_socket_context_on_end(SSL, getSocketContext(), [](auto *s) {
347
+ if (webSocketContextData->closeHandler) {
348
+ webSocketContextData->closeHandler(
349
+ (WebSocket<SSL, isServer, USERDATA> *)s, 1006,
350
+ {(char *)reason, (size_t)code});
351
+ }
352
+ }
374
353
 
375
- /* If we get a fin, we just close I guess */
376
- us_socket_close(SSL, (us_socket_t *) s, 0, nullptr);
354
+ /* Destruct in-placed data struct */
355
+ webSocketData->~WebSocketData();
377
356
 
378
- return s;
357
+ return s;
379
358
  });
380
359
 
381
- us_socket_context_on_long_timeout(SSL, getSocketContext(), [](auto *s) {
382
- ((WebSocket<SSL, isServer, USERDATA> *) s)->end(1000, "please reconnect");
383
-
360
+ /* Handle WebSocket data streams */
361
+ us_socket_context_on_data(
362
+ SSL, getSocketContext(), [](auto *s, char *data, int length) {
363
+ /* We need the websocket data */
364
+ WebSocketData *webSocketData =
365
+ (WebSocketData *)(us_socket_ext(SSL, s));
366
+
367
+ /* When in websocket shutdown mode, we do not care for ANY message,
368
+ * whether responding close frame or not. We only care for the TCP FIN
369
+ * really, not emitting any message after closing is key */
370
+ if (webSocketData->isShuttingDown) {
384
371
  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;
372
+ }
373
+
374
+ auto *webSocketContextData =
375
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
376
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
377
+ auto *asyncSocket = (AsyncSocket<SSL> *)s;
378
+
379
+ /* Every time we get data and not in shutdown state we simply reset
380
+ * the timeout */
381
+ asyncSocket->timeout(
382
+ webSocketContextData->idleTimeoutComponents.first);
383
+ webSocketData->hasTimedOut = false;
384
+
385
+ /* We always cork on data */
386
+ asyncSocket->cork();
387
+
388
+ /* This parser has virtually no overhead */
389
+ WebSocketProtocol<isServer,
390
+ WebSocketContext<SSL, isServer, USERDATA>>::
391
+ consume(data, (unsigned int)length,
392
+ (WebSocketState<isServer> *)webSocketData, s);
393
+
394
+ /* Uncorking a closed socekt is fine, in fact it is needed */
395
+ asyncSocket->uncork();
396
+
397
+ /* If uncorking was successful and we are in shutdown state then send
398
+ * TCP FIN */
399
+ if (asyncSocket->getBufferedAmount() == 0) {
400
+ /* We can now be in shutdown state */
401
+ if (webSocketData->isShuttingDown) {
402
+ /* Shutting down a closed socket is handled by uSockets and just
403
+ * fine */
404
+ asyncSocket->shutdown();
399
405
  }
406
+ }
400
407
 
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;
408
+ return s;
406
409
  });
407
410
 
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;
411
+ /* Handle HTTP write out (note: SSL_read may trigger this spuriously, the
412
+ * app need to handle spurious calls) */
413
+ us_socket_context_on_writable(SSL, getSocketContext(), [](auto *s) {
414
+ /* NOTE: Are we called here corked? If so, the below write code is broken,
415
+ * since we will have 0 as getBufferedAmount due to writing to cork
416
+ * buffer, then sending TCP FIN before we actually uncorked and sent off
417
+ * things */
418
+
419
+ /* It makes sense to check for us_is_shut_down here and return if so, to
420
+ * avoid shutting down twice */
421
+ if (us_socket_is_shut_down(SSL, (us_socket_t *)s)) {
422
+ return s;
423
+ }
424
+
425
+ AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *)s;
426
+ WebSocketData *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s));
427
+
428
+ /* We store old backpressure since it is unclear whether write drained
429
+ * anything, however, in case of coming here with 0 backpressure we still
430
+ * need to emit drain event */
431
+ unsigned int backpressure = asyncSocket->getBufferedAmount();
432
+
433
+ /* Drain as much as possible */
434
+ asyncSocket->write(nullptr, 0);
435
+
436
+ /* Behavior: if we actively drain backpressure, always reset timeout (even
437
+ * if we are in shutdown) */
438
+ /* Also reset timeout if we came here with 0 backpressure */
439
+ if (!backpressure || backpressure > asyncSocket->getBufferedAmount()) {
440
+ auto *webSocketContextData =
441
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
442
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
443
+ asyncSocket->timeout(webSocketContextData->idleTimeoutComponents.first);
444
+ webSocketData->hasTimedOut = false;
445
+ }
446
+
447
+ /* Are we in (WebSocket) shutdown mode? */
448
+ if (webSocketData->isShuttingDown) {
449
+ /* Check if we just now drained completely */
450
+ if (asyncSocket->getBufferedAmount() == 0) {
451
+ /* Now perform the actual TCP/TLS shutdown which was postponed due to
452
+ * backpressure */
453
+ asyncSocket->shutdown();
454
+ }
455
+ } else if (!backpressure ||
456
+ backpressure > asyncSocket->getBufferedAmount()) {
457
+ /* Only call drain if we actually drained backpressure or if we came
458
+ * here with 0 backpressure */
459
+ auto *webSocketContextData =
460
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
461
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
462
+ if (webSocketContextData->drainHandler) {
463
+ webSocketContextData->drainHandler(
464
+ (WebSocket<SSL, isServer, USERDATA> *)s);
424
465
  }
466
+ /* No need to check for closed here as we leave the handler
467
+ * immediately*/
468
+ }
469
+
470
+ return s;
471
+ });
472
+
473
+ /* Handle FIN, HTTP does not support half-closed sockets, so simply close */
474
+ us_socket_context_on_end(SSL, getSocketContext(), [](auto *s) {
475
+ /* If we get a fin, we just close I guess */
476
+ us_socket_close(SSL, (us_socket_t *)s, 0, nullptr);
477
+
478
+ return s;
479
+ });
480
+
481
+ us_socket_context_on_long_timeout(SSL, getSocketContext(), [](auto *s) {
482
+ ((WebSocket<SSL, isServer, USERDATA> *)s)->end(1000, "please reconnect");
483
+
484
+ return s;
485
+ });
486
+
487
+ /* Handle socket timeouts, simply close them so to not confuse client with
488
+ * FIN */
489
+ us_socket_context_on_timeout(SSL, getSocketContext(), [](auto *s) {
490
+ auto *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s));
491
+ auto *webSocketContextData =
492
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
493
+ SSL, us_socket_context(SSL, (us_socket_t *)s));
494
+
495
+ if (webSocketContextData->sendPingsAutomatically &&
496
+ !webSocketData->isShuttingDown && !webSocketData->hasTimedOut) {
497
+ webSocketData->hasTimedOut = true;
498
+ us_socket_timeout(SSL, s,
499
+ webSocketContextData->idleTimeoutComponents.second);
500
+ /* Send ping without being corked */
501
+ ((AsyncSocket<SSL> *)s)->write("\x89\x00", 2);
502
+ return s;
503
+ }
504
+
505
+ /* Timeout is very simple; we just close it */
506
+ /* Warning: we happen to know forceClose will not use first parameter so
507
+ * pass nullptr here */
508
+ forceClose(nullptr, s, ERR_WEBSOCKET_TIMEOUT);
509
+
510
+ return s;
511
+ });
512
+
513
+ return this;
514
+ }
515
+
516
+ void free() {
517
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData =
518
+ (WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
519
+ SSL, (us_socket_context_t *)this);
520
+ webSocketContextData->~WebSocketContextData();
521
+
522
+ us_socket_context_free(SSL, (us_socket_context_t *)this);
523
+ }
425
524
 
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();
525
+ public:
526
+ /* WebSocket contexts are always child contexts to a HTTP context so no SSL
527
+ * options are needed as they are inherited */
528
+ static WebSocketContext *
529
+ create(Loop * /*loop*/, us_socket_context_t *parentSocketContext,
530
+ TopicTree<TopicTreeMessage, TopicTreeBigMessage> *topicTree) {
531
+ WebSocketContext *webSocketContext =
532
+ (WebSocketContext *)us_create_child_socket_context(
533
+ SSL, parentSocketContext,
534
+ sizeof(WebSocketContextData<SSL, USERDATA>));
535
+ if (!webSocketContext) {
536
+ return nullptr;
429
537
  }
538
+
539
+ /* Init socket context data */
540
+ new ((WebSocketContextData<SSL, USERDATA> *)us_socket_context_ext(
541
+ SSL, (us_socket_context_t *)webSocketContext))
542
+ WebSocketContextData<SSL, USERDATA>(topicTree);
543
+ return webSocketContext->init();
544
+ }
430
545
  };
431
546
 
432
- }
547
+ } // namespace uWS
433
548
 
434
549
  #endif // UWS_WEBSOCKETCONTEXT_H