opal-up 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +209 -0
  3. data/README.md +81 -28
  4. data/bin/up_ruby +4 -0
  5. data/bin/up_ruby_cluster +4 -0
  6. data/ext/up_ext/App.h +606 -0
  7. data/ext/up_ext/AsyncSocket.h +355 -0
  8. data/ext/up_ext/AsyncSocketData.h +87 -0
  9. data/ext/up_ext/BloomFilter.h +83 -0
  10. data/ext/up_ext/ChunkedEncoding.h +236 -0
  11. data/ext/up_ext/ClientApp.h +36 -0
  12. data/ext/up_ext/HttpContext.h +502 -0
  13. data/ext/up_ext/HttpContextData.h +56 -0
  14. data/ext/up_ext/HttpErrors.h +53 -0
  15. data/ext/up_ext/HttpParser.h +680 -0
  16. data/ext/up_ext/HttpResponse.h +578 -0
  17. data/ext/up_ext/HttpResponseData.h +95 -0
  18. data/ext/up_ext/HttpRouter.h +380 -0
  19. data/ext/up_ext/Loop.h +204 -0
  20. data/ext/up_ext/LoopData.h +112 -0
  21. data/ext/up_ext/MoveOnlyFunction.h +377 -0
  22. data/ext/up_ext/PerMessageDeflate.h +315 -0
  23. data/ext/up_ext/ProxyParser.h +163 -0
  24. data/ext/up_ext/QueryParser.h +120 -0
  25. data/ext/up_ext/TopicTree.h +363 -0
  26. data/ext/up_ext/Utilities.h +66 -0
  27. data/ext/up_ext/WebSocket.h +381 -0
  28. data/ext/up_ext/WebSocketContext.h +434 -0
  29. data/ext/up_ext/WebSocketContextData.h +109 -0
  30. data/ext/up_ext/WebSocketData.h +86 -0
  31. data/ext/up_ext/WebSocketExtensions.h +256 -0
  32. data/ext/up_ext/WebSocketHandshake.h +145 -0
  33. data/ext/up_ext/WebSocketProtocol.h +506 -0
  34. data/ext/up_ext/bsd.c +767 -0
  35. data/ext/up_ext/bsd.h +109 -0
  36. data/ext/up_ext/context.c +524 -0
  37. data/ext/up_ext/epoll_kqueue.c +458 -0
  38. data/ext/up_ext/epoll_kqueue.h +67 -0
  39. data/ext/up_ext/extconf.rb +5 -0
  40. data/ext/up_ext/internal.h +224 -0
  41. data/ext/up_ext/libusockets.h +350 -0
  42. data/ext/up_ext/libuwebsockets.cpp +1374 -0
  43. data/ext/up_ext/libuwebsockets.h +260 -0
  44. data/ext/up_ext/loop.c +386 -0
  45. data/ext/up_ext/loop_data.h +38 -0
  46. data/ext/up_ext/socket.c +231 -0
  47. data/ext/up_ext/up_ext.c +278 -0
  48. data/lib/up/node/rack_env.rb +2 -2
  49. data/lib/up/ruby/cluster_cli.rb +10 -0
  50. data/lib/up/ruby/rack_cluster.rb +26 -0
  51. data/lib/up/ruby/rack_env.rb +97 -0
  52. data/lib/up/ruby/rack_server.rb +26 -0
  53. data/lib/up/ruby/server_cli.rb +10 -0
  54. data/lib/up/u_web_socket/rack_env.rb +1 -1
  55. data/lib/up/version.rb +1 -1
  56. metadata +71 -18
  57. data/.gitignore +0 -5
  58. data/Gemfile +0 -2
  59. data/example_rack_app/Gemfile +0 -3
  60. data/example_rack_app/config.ru +0 -6
  61. data/example_rack_app/rack_app.rb +0 -5
  62. data/example_roda_app/Gemfile +0 -6
  63. data/example_roda_app/config.ru +0 -6
  64. data/example_roda_app/roda_app.rb +0 -37
  65. data/example_sinatra_app/Gemfile +0 -6
  66. data/example_sinatra_app/config.ru +0 -6
  67. data/example_sinatra_app/sinatra_app.rb +0 -7
  68. data/opal-up.gemspec +0 -27
  69. data/up_logo.svg +0 -256
@@ -0,0 +1,66 @@
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_UTILITIES_H
19
+ #define UWS_UTILITIES_H
20
+
21
+ /* Various common utilities */
22
+
23
+ #include <cstdint>
24
+
25
+ namespace uWS {
26
+ namespace utils {
27
+
28
+ inline int u32toaHex(uint32_t value, char *dst) {
29
+ char palette[] = "0123456789abcdef";
30
+ char temp[10];
31
+ char *p = temp;
32
+ do {
33
+ *p++ = palette[value & 15];
34
+ value >>= 4;
35
+ } while (value > 0);
36
+
37
+ int ret = (int) (p - temp);
38
+
39
+ do {
40
+ *dst++ = *--p;
41
+ } while (p != temp);
42
+
43
+ return ret;
44
+ }
45
+
46
+ inline int u64toa(uint64_t value, char *dst) {
47
+ char temp[20];
48
+ char *p = temp;
49
+ do {
50
+ *p++ = (char) ((value % 10) + '0');
51
+ value /= 10;
52
+ } while (value > 0);
53
+
54
+ int ret = (int) (p - temp);
55
+
56
+ do {
57
+ *dst++ = *--p;
58
+ } while (p != temp);
59
+
60
+ return ret;
61
+ }
62
+
63
+ }
64
+ }
65
+
66
+ #endif // UWS_UTILITIES_H
@@ -0,0 +1,381 @@
1
+ /*
2
+ * Authored by Alex Hultman, 2018-2021.
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_WEBSOCKET_H
19
+ #define UWS_WEBSOCKET_H
20
+
21
+ #include "WebSocketData.h"
22
+ #include "WebSocketProtocol.h"
23
+ #include "AsyncSocket.h"
24
+ #include "WebSocketContextData.h"
25
+
26
+ #include <string_view>
27
+
28
+ namespace uWS {
29
+
30
+ template <bool SSL, bool isServer, typename USERDATA>
31
+ struct WebSocket : AsyncSocket<SSL> {
32
+ template <bool> friend struct TemplatedApp;
33
+ template <bool> friend struct HttpResponse;
34
+ private:
35
+ typedef AsyncSocket<SSL> Super;
36
+
37
+ void *init(bool perMessageDeflate, CompressOptions compressOptions, BackPressure &&backpressure) {
38
+ new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, compressOptions, std::move(backpressure));
39
+ return this;
40
+ }
41
+ public:
42
+
43
+ /* Returns pointer to the per socket user data */
44
+ USERDATA *getUserData() {
45
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
46
+ /* We just have it overallocated by sizeof type */
47
+ return (USERDATA *) (webSocketData + 1);
48
+ }
49
+
50
+ /* See AsyncSocket */
51
+ using Super::getBufferedAmount;
52
+ using Super::getRemoteAddress;
53
+ using Super::getRemoteAddressAsText;
54
+ using Super::getNativeHandle;
55
+
56
+ /* WebSocket close cannot be an alias to AsyncSocket::close since
57
+ * we need to check first if it was shut down by remote peer */
58
+ us_socket_t *close() {
59
+ if (us_socket_is_closed(SSL, (us_socket_t *) this)) {
60
+ return nullptr;
61
+ }
62
+ WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData();
63
+ if (webSocketData->isShuttingDown) {
64
+ return nullptr;
65
+ }
66
+
67
+ return us_socket_close(SSL, (us_socket_t *) this, 0, nullptr);
68
+ }
69
+
70
+ enum SendStatus : int {
71
+ BACKPRESSURE,
72
+ SUCCESS,
73
+ DROPPED
74
+ };
75
+
76
+ /* Sending fragmented messages puts a bit of effort on the user; you must not interleave regular sends
77
+ * with fragmented sends and you must sendFirstFragment, [sendFragment], then finally sendLastFragment. */
78
+ SendStatus sendFirstFragment(std::string_view message, OpCode opCode = OpCode::BINARY, bool compress = false) {
79
+ return send(message, opCode, compress, false);
80
+ }
81
+
82
+ SendStatus sendFragment(std::string_view message, bool compress = false) {
83
+ return send(message, CONTINUATION, compress, false);
84
+ }
85
+
86
+ SendStatus sendLastFragment(std::string_view message, bool compress = false) {
87
+ return send(message, CONTINUATION, compress, true);
88
+ }
89
+
90
+ /* Send or buffer a WebSocket frame, compressed or not. Returns BACKPRESSURE on increased user space backpressure,
91
+ * DROPPED on dropped message (due to backpressure) or SUCCCESS if you are free to send even more now. */
92
+ SendStatus send(std::string_view message, OpCode opCode = OpCode::BINARY, bool compress = false, bool fin = true) {
93
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
94
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
95
+ );
96
+
97
+ /* Skip sending and report success if we are over the limit of maxBackpressure */
98
+ if (webSocketContextData->maxBackpressure && webSocketContextData->maxBackpressure < getBufferedAmount()) {
99
+ /* Also defer a close if we should */
100
+ if (webSocketContextData->closeOnBackpressureLimit) {
101
+ us_socket_shutdown_read(SSL, (us_socket_t *) this);
102
+ }
103
+
104
+ /* It is okay to call send again from within this callback since we immediately return with DROPPED afterwards */
105
+ if (webSocketContextData->droppedHandler) {
106
+ webSocketContextData->droppedHandler(this, message, opCode);
107
+ }
108
+
109
+ return DROPPED;
110
+ }
111
+
112
+ /* If we are subscribers and have messages to drain we need to drain them here to stay synced */
113
+ WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData();
114
+
115
+ /* Special path for long sends of non-compressed, non-SSL messages */
116
+ if (message.length() >= 16 * 1024 && !compress && !SSL && !webSocketData->subscriber && getBufferedAmount() == 0 && Super::getLoopData()->corkOffset == 0) {
117
+ char header[10];
118
+ int header_length = (int) protocol::formatMessage<isServer>(header, nullptr, 0, opCode, message.length(), compress, fin);
119
+ int written = us_socket_write2(0, (struct us_socket_t *)this, header, header_length, message.data(), (int) message.length());
120
+
121
+ if (written != header_length + (int) message.length()) {
122
+ /* Buffer up backpressure */
123
+ if (written > header_length) {
124
+ webSocketData->buffer.append(message.data() + written - header_length, message.length() - (size_t) (written - header_length));
125
+ } else {
126
+ webSocketData->buffer.append(header + written, (size_t) header_length - (size_t) written);
127
+ webSocketData->buffer.append(message.data(), message.length());
128
+ }
129
+ /* We cannot still be corked if we have backpressure.
130
+ * We also cannot uncork normally since it will re-write the already buffered
131
+ * up backpressure again. */
132
+ Super::uncorkWithoutSending();
133
+ return BACKPRESSURE;
134
+ }
135
+ } else {
136
+
137
+ if (webSocketData->subscriber) {
138
+ /* This will call back into us, send. */
139
+ webSocketContextData->topicTree->drain(webSocketData->subscriber);
140
+ }
141
+
142
+ /* Transform the message to compressed domain if requested */
143
+ if (compress) {
144
+ WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData();
145
+
146
+ /* Check and correct the compress hint. It is never valid to compress 0 bytes */
147
+ if (message.length() && opCode < 3 && webSocketData->compressionStatus == WebSocketData::ENABLED) {
148
+ LoopData *loopData = Super::getLoopData();
149
+ /* Compress using either shared or dedicated deflationStream */
150
+ if (webSocketData->deflationStream) {
151
+ message = webSocketData->deflationStream->deflate(loopData->zlibContext, message, false);
152
+ } else {
153
+ message = loopData->deflationStream->deflate(loopData->zlibContext, message, true);
154
+ }
155
+ } else {
156
+ compress = false;
157
+ }
158
+ }
159
+
160
+ /* Get size, allocate size, write if needed */
161
+ size_t messageFrameSize = protocol::messageFrameSize(message.length());
162
+ auto [sendBuffer, sendBufferAttribute] = Super::getSendBuffer(messageFrameSize);
163
+ protocol::formatMessage<isServer>(sendBuffer, message.data(), message.length(), opCode, message.length(), compress, fin);
164
+
165
+ /* Depending on size of message we have different paths */
166
+ if (sendBufferAttribute == SendBufferAttribute::NEEDS_DRAIN) {
167
+ /* This is a drain */
168
+ auto[written, failed] = Super::write(nullptr, 0);
169
+ if (failed) {
170
+ /* Return false for failure, skipping to reset the timeout below */
171
+ return BACKPRESSURE;
172
+ }
173
+ } else if (sendBufferAttribute == SendBufferAttribute::NEEDS_UNCORK) {
174
+ /* Uncork if we came here uncorked */
175
+ auto [written, failed] = Super::uncork();
176
+ if (failed) {
177
+ return BACKPRESSURE;
178
+ }
179
+ }
180
+
181
+ }
182
+
183
+ /* Every successful send resets the timeout */
184
+ if (webSocketContextData->resetIdleTimeoutOnSend) {
185
+ Super::timeout(webSocketContextData->idleTimeoutComponents.first);
186
+ WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData();
187
+ webSocketData->hasTimedOut = false;
188
+ }
189
+
190
+ /* Return success */
191
+ return SUCCESS;
192
+ }
193
+
194
+ /* Send websocket close frame, emit close event, send FIN if successful.
195
+ * Will not append a close reason if code is 0 or 1005. */
196
+ void end(int code = 0, std::string_view message = {}) {
197
+ /* Check if we already called this one */
198
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
199
+ if (webSocketData->isShuttingDown) {
200
+ return;
201
+ }
202
+
203
+ /* We postpone any FIN sending to either drainage or uncorking */
204
+ webSocketData->isShuttingDown = true;
205
+
206
+ /* Format and send the close frame */
207
+ static const int MAX_CLOSE_PAYLOAD = 123;
208
+ size_t length = std::min<size_t>(MAX_CLOSE_PAYLOAD, message.length());
209
+ char closePayload[MAX_CLOSE_PAYLOAD + 2];
210
+ size_t closePayloadLength = protocol::formatClosePayload(closePayload, (uint16_t) code, message.data(), length);
211
+ bool ok = send(std::string_view(closePayload, closePayloadLength), OpCode::CLOSE);
212
+
213
+ /* FIN if we are ok and not corked */
214
+ if (!this->isCorked()) {
215
+ if (ok) {
216
+ /* If we are not corked, and we just sent off everything, we need to FIN right here.
217
+ * In all other cases, we need to fin either if uncork was successful, or when drainage is complete. */
218
+ this->shutdown();
219
+ }
220
+ }
221
+
222
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
223
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
224
+ );
225
+
226
+ /* Set shorter timeout (use ping-timeout) to avoid long hanging sockets after end() on broken connections */
227
+ Super::timeout(webSocketContextData->idleTimeoutComponents.second);
228
+
229
+ /* At this point we iterate all currently held subscriptions and emit an event for all of them */
230
+ if (webSocketData->subscriber && webSocketContextData->subscriptionHandler) {
231
+ for (Topic *t : webSocketData->subscriber->topics) {
232
+ webSocketContextData->subscriptionHandler(this, t->name, (int) t->size() - 1, (int) t->size());
233
+ }
234
+ }
235
+
236
+ /* Make sure to unsubscribe from any pub/sub node at exit */
237
+ webSocketContextData->topicTree->freeSubscriber(webSocketData->subscriber);
238
+ webSocketData->subscriber = nullptr;
239
+
240
+ /* Emit close event */
241
+ if (webSocketContextData->closeHandler) {
242
+ webSocketContextData->closeHandler(this, code, message);
243
+ }
244
+ }
245
+
246
+ /* Corks the response if possible. Leaves already corked socket be. */
247
+ void cork(MoveOnlyFunction<void()> &&handler) {
248
+ if (!Super::isCorked() && Super::canCork()) {
249
+ Super::cork();
250
+ handler();
251
+
252
+ /* There is no timeout when failing to uncork for WebSockets,
253
+ * as that is handled by idleTimeout */
254
+ auto [written, failed] = Super::uncork();
255
+ (void)written;
256
+ (void)failed;
257
+ } else {
258
+ /* We are already corked, or can't cork so let's just call the handler */
259
+ handler();
260
+ }
261
+ }
262
+
263
+ /* Subscribe to a topic according to MQTT rules and syntax. Returns success */
264
+ bool subscribe(std::string_view topic, bool = false) {
265
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
266
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
267
+ );
268
+
269
+ /* Make us a subscriber if we aren't yet */
270
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
271
+ if (!webSocketData->subscriber) {
272
+ webSocketData->subscriber = webSocketContextData->topicTree->createSubscriber();
273
+ webSocketData->subscriber->user = this;
274
+ }
275
+
276
+ /* Cannot return numSubscribers as this is only for this particular websocket context */
277
+ Topic *topicOrNull = webSocketContextData->topicTree->subscribe(webSocketData->subscriber, topic);
278
+ if (topicOrNull && webSocketContextData->subscriptionHandler) {
279
+ /* Emit this socket, the topic, new count, old count */
280
+ webSocketContextData->subscriptionHandler(this, topic, (int) topicOrNull->size(), (int) topicOrNull->size() - 1);
281
+ }
282
+
283
+ /* Subscribe always succeeds */
284
+ return true;
285
+ }
286
+
287
+ /* Unsubscribe from a topic, returns true if we were subscribed. */
288
+ bool unsubscribe(std::string_view topic, bool = false) {
289
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
290
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
291
+ );
292
+
293
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
294
+
295
+ if (!webSocketData->subscriber) { return false; }
296
+
297
+ /* Cannot return numSubscribers as this is only for this particular websocket context */
298
+ auto [ok, last, newCount] = webSocketContextData->topicTree->unsubscribe(webSocketData->subscriber, topic);
299
+ /* Emit subscription event if last */
300
+ if (ok && webSocketContextData->subscriptionHandler) {
301
+ webSocketContextData->subscriptionHandler(this, topic, newCount, newCount + 1);
302
+ }
303
+
304
+ /* Leave us as subscribers even if we subscribe to nothing (last unsubscribed topic might miss its message otherwise) */
305
+
306
+ return ok;
307
+ }
308
+
309
+ /* Returns whether this socket is subscribed to the specified topic */
310
+ bool isSubscribed(std::string_view topic) {
311
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
312
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
313
+ );
314
+
315
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
316
+ if (!webSocketData->subscriber) {
317
+ return false;
318
+ }
319
+
320
+ Topic *topicPtr = webSocketContextData->topicTree->lookupTopic(topic);
321
+ if (!topicPtr) {
322
+ return false;
323
+ }
324
+
325
+ return topicPtr->count(webSocketData->subscriber);
326
+ }
327
+
328
+ /* Iterates all topics of this WebSocket. Every topic is represented by its full name.
329
+ * Can be called in close handler. It is possible to modify the subscription list while
330
+ * inside the callback ONLY IF not modifying the topic passed to the callback.
331
+ * Topic names are valid only for the duration of the callback. */
332
+ void iterateTopics(MoveOnlyFunction<void(std::string_view)> cb) {
333
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
334
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
335
+ );
336
+
337
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
338
+ if (webSocketData->subscriber) {
339
+ /* Lock this subscriber for unsubscription / subscription */
340
+ webSocketContextData->topicTree->iteratingSubscriber = webSocketData->subscriber;
341
+
342
+ for (Topic *topicPtr : webSocketData->subscriber->topics) {
343
+ cb({topicPtr->name.data(), topicPtr->name.length()});
344
+ }
345
+
346
+ /* Unlock subscriber */
347
+ webSocketContextData->topicTree->iteratingSubscriber = nullptr;
348
+ }
349
+ }
350
+
351
+ /* Publish a message to a topic according to MQTT rules and syntax. Returns success.
352
+ * We, the WebSocket, must be subscribed to the topic itself and if so - no message will be sent to ourselves.
353
+ * Use App::publish for an unconditional publish that simply publishes to whomever might be subscribed. */
354
+ bool publish(std::string_view topic, std::string_view message, OpCode opCode = OpCode::TEXT, bool compress = false) {
355
+ WebSocketContextData<SSL, USERDATA> *webSocketContextData = (WebSocketContextData<SSL, USERDATA> *) us_socket_context_ext(SSL,
356
+ (us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
357
+ );
358
+
359
+ /* We cannot be a subscriber of this topic if we are not a subscriber of anything */
360
+ WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
361
+ if (!webSocketData->subscriber) {
362
+ /* Failure, but still do return the number of subscribers */
363
+ return false;
364
+ }
365
+
366
+ /* Publish as sender, does not receive its own messages even if subscribed to relevant topics */
367
+ if (message.length() >= LoopData::CORK_BUFFER_SIZE) {
368
+ return webSocketContextData->topicTree->publishBig(webSocketData->subscriber, topic, {message, opCode, compress}, [](Subscriber *s, TopicTreeBigMessage &message) {
369
+ auto *ws = (WebSocket<SSL, true, int> *) s->user;
370
+
371
+ ws->send(message.message, (OpCode)message.opCode, message.compress);
372
+ });
373
+ } else {
374
+ return webSocketContextData->topicTree->publish(webSocketData->subscriber, topic, {std::string(message), opCode, compress});
375
+ }
376
+ }
377
+ };
378
+
379
+ }
380
+
381
+ #endif // UWS_WEBSOCKET_H