rage-iodine 1.7.58

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.github/workflows/ruby.yml +42 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +2 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1098 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +23 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/bates/README.md +3 -0
  25. data/examples/bates/config.ru +342 -0
  26. data/examples/bates/david+bold.pdf +0 -0
  27. data/examples/bates/public/drop-pdf.png +0 -0
  28. data/examples/bates/public/index.html +600 -0
  29. data/examples/config.ru +59 -0
  30. data/examples/echo.ru +59 -0
  31. data/examples/etag.ru +16 -0
  32. data/examples/hello.ru +29 -0
  33. data/examples/pubsub_engine.ru +81 -0
  34. data/examples/rack3.ru +12 -0
  35. data/examples/redis.ru +70 -0
  36. data/examples/shootout.ru +73 -0
  37. data/examples/sub-protocols.ru +90 -0
  38. data/examples/tcp_client.rb +66 -0
  39. data/examples/x-sendfile.ru +14 -0
  40. data/exe/iodine +280 -0
  41. data/ext/iodine/extconf.rb +110 -0
  42. data/ext/iodine/fio.c +12096 -0
  43. data/ext/iodine/fio.h +6390 -0
  44. data/ext/iodine/fio_cli.c +431 -0
  45. data/ext/iodine/fio_cli.h +189 -0
  46. data/ext/iodine/fio_json_parser.h +687 -0
  47. data/ext/iodine/fio_siphash.c +157 -0
  48. data/ext/iodine/fio_siphash.h +37 -0
  49. data/ext/iodine/fio_tls.h +129 -0
  50. data/ext/iodine/fio_tls_missing.c +649 -0
  51. data/ext/iodine/fio_tls_openssl.c +1056 -0
  52. data/ext/iodine/fio_tmpfile.h +50 -0
  53. data/ext/iodine/fiobj.h +44 -0
  54. data/ext/iodine/fiobj4fio.h +21 -0
  55. data/ext/iodine/fiobj_ary.c +333 -0
  56. data/ext/iodine/fiobj_ary.h +139 -0
  57. data/ext/iodine/fiobj_data.c +1185 -0
  58. data/ext/iodine/fiobj_data.h +167 -0
  59. data/ext/iodine/fiobj_hash.c +409 -0
  60. data/ext/iodine/fiobj_hash.h +176 -0
  61. data/ext/iodine/fiobj_json.c +622 -0
  62. data/ext/iodine/fiobj_json.h +68 -0
  63. data/ext/iodine/fiobj_mem.h +71 -0
  64. data/ext/iodine/fiobj_mustache.c +317 -0
  65. data/ext/iodine/fiobj_mustache.h +62 -0
  66. data/ext/iodine/fiobj_numbers.c +344 -0
  67. data/ext/iodine/fiobj_numbers.h +127 -0
  68. data/ext/iodine/fiobj_str.c +433 -0
  69. data/ext/iodine/fiobj_str.h +172 -0
  70. data/ext/iodine/fiobject.c +620 -0
  71. data/ext/iodine/fiobject.h +654 -0
  72. data/ext/iodine/hpack.h +1923 -0
  73. data/ext/iodine/http.c +2736 -0
  74. data/ext/iodine/http.h +1019 -0
  75. data/ext/iodine/http1.c +825 -0
  76. data/ext/iodine/http1.h +29 -0
  77. data/ext/iodine/http1_parser.h +1835 -0
  78. data/ext/iodine/http_internal.c +1279 -0
  79. data/ext/iodine/http_internal.h +248 -0
  80. data/ext/iodine/http_mime_parser.h +350 -0
  81. data/ext/iodine/iodine.c +1433 -0
  82. data/ext/iodine/iodine.h +64 -0
  83. data/ext/iodine/iodine_caller.c +218 -0
  84. data/ext/iodine/iodine_caller.h +27 -0
  85. data/ext/iodine/iodine_connection.c +941 -0
  86. data/ext/iodine/iodine_connection.h +55 -0
  87. data/ext/iodine/iodine_defer.c +420 -0
  88. data/ext/iodine/iodine_defer.h +6 -0
  89. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  90. data/ext/iodine/iodine_helpers.c +282 -0
  91. data/ext/iodine/iodine_helpers.h +12 -0
  92. data/ext/iodine/iodine_http.c +1280 -0
  93. data/ext/iodine/iodine_http.h +23 -0
  94. data/ext/iodine/iodine_json.c +302 -0
  95. data/ext/iodine/iodine_json.h +6 -0
  96. data/ext/iodine/iodine_mustache.c +567 -0
  97. data/ext/iodine/iodine_mustache.h +6 -0
  98. data/ext/iodine/iodine_pubsub.c +580 -0
  99. data/ext/iodine/iodine_pubsub.h +26 -0
  100. data/ext/iodine/iodine_rack_io.c +273 -0
  101. data/ext/iodine/iodine_rack_io.h +20 -0
  102. data/ext/iodine/iodine_store.c +142 -0
  103. data/ext/iodine/iodine_store.h +20 -0
  104. data/ext/iodine/iodine_tcp.c +346 -0
  105. data/ext/iodine/iodine_tcp.h +13 -0
  106. data/ext/iodine/iodine_tls.c +261 -0
  107. data/ext/iodine/iodine_tls.h +13 -0
  108. data/ext/iodine/mustache_parser.h +1546 -0
  109. data/ext/iodine/redis_engine.c +957 -0
  110. data/ext/iodine/redis_engine.h +79 -0
  111. data/ext/iodine/resp_parser.h +317 -0
  112. data/ext/iodine/scheduler.c +173 -0
  113. data/ext/iodine/scheduler.h +6 -0
  114. data/ext/iodine/websocket_parser.h +506 -0
  115. data/ext/iodine/websockets.c +752 -0
  116. data/ext/iodine/websockets.h +185 -0
  117. data/iodine.gemspec +50 -0
  118. data/lib/iodine/connection.rb +61 -0
  119. data/lib/iodine/json.rb +42 -0
  120. data/lib/iodine/mustache.rb +113 -0
  121. data/lib/iodine/pubsub.rb +55 -0
  122. data/lib/iodine/rack_utils.rb +43 -0
  123. data/lib/iodine/tls.rb +16 -0
  124. data/lib/iodine/version.rb +3 -0
  125. data/lib/iodine.rb +274 -0
  126. data/lib/rack/handler/iodine.rb +33 -0
  127. data/logo.png +0 -0
  128. metadata +284 -0
@@ -0,0 +1,752 @@
1
+ /*
2
+ copyright: Boaz Segev, 2016-2019
3
+ license: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #define FIO_INCLUDE_STR
8
+ #include <fio.h>
9
+
10
+ /* subscription lists have a long lifetime */
11
+ #define FIO_FORCE_MALLOC_TMP 1
12
+ #define FIO_INCLUDE_LINKED_LIST
13
+ #include <fio.h>
14
+
15
+ #include <fiobj.h>
16
+
17
+ #include <http.h>
18
+ #include <http_internal.h>
19
+
20
+ #ifndef __MINGW32__
21
+ #include <arpa/inet.h>
22
+ #endif
23
+ #include <errno.h>
24
+ #include <stdio.h>
25
+ #include <stdlib.h>
26
+ #include <string.h>
27
+ #include <strings.h>
28
+
29
+ #include <websocket_parser.h>
30
+
31
+ #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
32
+ !defined(__MINGW32__)
33
+ #include <endian.h>
34
+ #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
35
+ __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
36
+ #define __BIG_ENDIAN__
37
+ #endif
38
+ #endif
39
+
40
+ /*******************************************************************************
41
+ Buffer management - update to change the way the buffer is handled.
42
+ */
43
+ struct buffer_s {
44
+ void *data;
45
+ size_t size;
46
+ };
47
+
48
+ #pragma weak create_ws_buffer
49
+ /** returns a buffer_s struct, with a buffer (at least) `size` long. */
50
+ struct buffer_s create_ws_buffer(ws_s *owner);
51
+
52
+ #pragma weak resize_ws_buffer
53
+ /** returns a buffer_s struct, with a buffer (at least) `size` long. */
54
+ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
55
+
56
+ #pragma weak free_ws_buffer
57
+ /** releases an existing buffer. */
58
+ void free_ws_buffer(ws_s *owner, struct buffer_s);
59
+
60
+ /** Sets the initial buffer size. (4Kb)*/
61
+ #define WS_INITIAL_BUFFER_SIZE 4096UL
62
+
63
+ /*******************************************************************************
64
+ Buffer management - simple implementation...
65
+ Since Websocket connections have a long life expectancy, optimizing this part of
66
+ the code probably wouldn't offer a high performance boost.
67
+ */
68
+
69
+ // buffer increments by 4,096 Bytes (4Kb)
70
+ #define round_up_buffer_size(size) (((size) >> 12) + 1) << 12
71
+
72
+ struct buffer_s create_ws_buffer(ws_s *owner) {
73
+ (void)(owner);
74
+ struct buffer_s buff;
75
+ buff.size = WS_INITIAL_BUFFER_SIZE;
76
+ buff.data = malloc(buff.size);
77
+ FIO_ASSERT_ALLOC(buff.data);
78
+ return buff;
79
+ }
80
+
81
+ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buff) {
82
+ buff.size = round_up_buffer_size(buff.size);
83
+ void *tmp = realloc(buff.data, buff.size);
84
+ if (!tmp) {
85
+ free_ws_buffer(owner, buff);
86
+ buff.size = 0;
87
+ }
88
+ buff.data = tmp;
89
+ return buff;
90
+ }
91
+ void free_ws_buffer(ws_s *owner, struct buffer_s buff) {
92
+ (void)(owner);
93
+ free(buff.data);
94
+ }
95
+
96
+ #undef round_up_buffer_size
97
+
98
+ /*******************************************************************************
99
+ Create/Destroy the websocket object (prototypes)
100
+ */
101
+
102
+ static ws_s *new_websocket();
103
+ static void destroy_ws(ws_s *ws);
104
+
105
+ /*******************************************************************************
106
+ The Websocket object (protocol + parser)
107
+ */
108
+ struct ws_s {
109
+ /** The Websocket protocol */
110
+ fio_protocol_s protocol;
111
+ /** connection data */
112
+ intptr_t fd;
113
+ /** callbacks */
114
+ void (*on_message)(ws_s *ws, fio_str_info_s msg, uint8_t is_text);
115
+ void (*on_shutdown)(ws_s *ws);
116
+ void (*on_ready)(ws_s *ws);
117
+ void (*on_open)(ws_s *ws);
118
+ void (*on_close)(intptr_t uuid, void *udata);
119
+ /** Opaque user data. */
120
+ void *udata;
121
+ /** The maximum websocket message size */
122
+ size_t max_msg_size;
123
+ /** active pub/sub subscriptions */
124
+ fio_ls_s subscriptions;
125
+ fio_lock_i sub_lock;
126
+ /** socket buffer. */
127
+ struct buffer_s buffer;
128
+ /** data length (how much of the buffer actually used). */
129
+ size_t length;
130
+ /** total data length (including continuation frames). */
131
+ size_t total_length;
132
+ /** message buffer. */
133
+ FIOBJ msg;
134
+ /** latest text state. */
135
+ uint8_t is_text;
136
+ /** websocket connection type. */
137
+ uint8_t is_client;
138
+ };
139
+
140
+ /* *****************************************************************************
141
+ Create/Destroy the websocket subscription objects
142
+ ***************************************************************************** */
143
+
144
+ static inline void clear_subscriptions(ws_s *ws) {
145
+ fio_lock(&ws->sub_lock);
146
+ while (fio_ls_any(&ws->subscriptions)) {
147
+ fio_unsubscribe(fio_ls_pop(&ws->subscriptions));
148
+ }
149
+ fio_unlock(&ws->sub_lock);
150
+ }
151
+
152
+ /* *****************************************************************************
153
+ Callbacks - Required functions for websocket_parser.h
154
+ ***************************************************************************** */
155
+
156
+ static void websocket_on_unwrapped(void *ws_p, void *msg, uint64_t len,
157
+ char first, char last, char text,
158
+ unsigned char rsv) {
159
+ ws_s *ws = ws_p;
160
+ if (!ws)
161
+ return;
162
+ if (last && first) {
163
+ ws->on_message(ws, (fio_str_info_s){.data = msg, .len = len},
164
+ (uint8_t)text);
165
+ return;
166
+ }
167
+ if (ws->msg == FIOBJ_INVALID)
168
+ ws->msg = fiobj_str_buf(len);
169
+ ws->total_length += len;
170
+ if (first) {
171
+ ws->is_text = (uint8_t)text;
172
+ }
173
+ fiobj_str_write(ws->msg, msg, len);
174
+ if (last) {
175
+ ws->on_message(ws, fiobj_obj2cstr(ws->msg), ws->is_text);
176
+ fiobj_str_resize(ws->msg, 0);
177
+ ws->total_length = 0;
178
+ }
179
+
180
+ (void)rsv;
181
+ }
182
+ static void websocket_on_protocol_ping(void *ws_p, void *msg_, uint64_t len) {
183
+ ws_s *ws = ws_p;
184
+ if (msg_) {
185
+ void *buff = malloc(len + 16);
186
+ FIO_ASSERT_ALLOC(buff);
187
+ len = (((ws_s *)ws)->is_client
188
+ ? websocket_client_wrap(buff, msg_, len, 10, 1, 1, 0)
189
+ : websocket_server_wrap(buff, msg_, len, 10, 1, 1, 0));
190
+ fio_write2(ws->fd, .data.buffer = buff, .length = len);
191
+ } else {
192
+ if (((ws_s *)ws)->is_client) {
193
+ fio_write2(ws->fd, .data.buffer = "\x8a\x80mask", .length = 6,
194
+ .after.dealloc = FIO_DEALLOC_NOOP);
195
+ } else {
196
+ fio_write2(ws->fd, .data.buffer = "\x8a\x00", .length = 2,
197
+ .after.dealloc = FIO_DEALLOC_NOOP);
198
+ }
199
+ }
200
+ FIO_LOG_DEBUG("Received ping and sent pong for Websocket %p (%d)", ws_p,
201
+ (int)(((ws_s *)ws_p)->fd));
202
+ }
203
+ static void websocket_on_protocol_pong(void *ws_p, void *msg, uint64_t len) {
204
+ FIO_LOG_DEBUG("Received pong for Websocket %p (%d)", ws_p,
205
+ (int)(((ws_s *)ws_p)->fd));
206
+ (void)len;
207
+ (void)msg;
208
+ (void)ws_p;
209
+ }
210
+ static void websocket_on_protocol_close(void *ws_p) {
211
+ ws_s *ws = ws_p;
212
+ fio_close(ws->fd);
213
+ }
214
+ static void websocket_on_protocol_error(void *ws_p) {
215
+ ws_s *ws = ws_p;
216
+ fio_close(ws->fd);
217
+ }
218
+
219
+ /*******************************************************************************
220
+ The Websocket Protocol implementation
221
+ */
222
+
223
+ #define ws_protocol(fd) ((ws_s *)(server_get_protocol(fd)))
224
+
225
+ static void ws_ping(intptr_t fd, fio_protocol_s *ws) {
226
+ (void)(ws);
227
+ if (((ws_s *)ws)->is_client) {
228
+ fio_write2(fd, .data.buffer = "\x89\x80MASK", .length = 6,
229
+ .after.dealloc = FIO_DEALLOC_NOOP);
230
+ } else {
231
+ fio_write2(fd, .data.buffer = "\x89\x00", .length = 2,
232
+ .after.dealloc = FIO_DEALLOC_NOOP);
233
+ }
234
+ FIO_LOG_DEBUG("Sent ping for Websocket %p (%d)", (void *)ws, (int)fd);
235
+ }
236
+
237
+ static void on_close(intptr_t uuid, fio_protocol_s *_ws) {
238
+ destroy_ws((ws_s *)_ws);
239
+ (void)uuid;
240
+ }
241
+
242
+ static void on_ready(intptr_t fduuid, fio_protocol_s *ws) {
243
+ (void)(fduuid);
244
+ if (((ws_s *)ws)->on_ready)
245
+ ((ws_s *)ws)->on_ready((ws_s *)ws);
246
+ }
247
+
248
+ static uint8_t on_shutdown(intptr_t fd, fio_protocol_s *ws) {
249
+ (void)(fd);
250
+ if (ws && ((ws_s *)ws)->on_shutdown)
251
+ ((ws_s *)ws)->on_shutdown((ws_s *)ws);
252
+ if (((ws_s *)ws)->is_client) {
253
+ fio_write2(fd, .data.buffer = "\x88\x80MASK", .length = 6,
254
+ .after.dealloc = FIO_DEALLOC_NOOP);
255
+ } else {
256
+ fio_write2(fd, .data.buffer = "\x88\x00", .length = 2,
257
+ .after.dealloc = FIO_DEALLOC_NOOP);
258
+ }
259
+ return 0;
260
+ }
261
+
262
+ static void on_data(intptr_t sockfd, fio_protocol_s *ws_) {
263
+ ws_s *const ws = (ws_s *)ws_;
264
+ if (ws == NULL)
265
+ return;
266
+ struct websocket_packet_info_s info =
267
+ websocket_buffer_peek(ws->buffer.data, ws->length);
268
+ const uint64_t raw_length = info.packet_length + info.head_length;
269
+ /* test expected data amount */
270
+ if (ws->max_msg_size < raw_length + ws->total_length) {
271
+ /* too big */
272
+ websocket_close(ws);
273
+ return;
274
+ }
275
+ /* test buffer capacity */
276
+ if (raw_length > ws->buffer.size) {
277
+ ws->buffer.size = (size_t)raw_length;
278
+ ws->buffer = resize_ws_buffer(ws, ws->buffer);
279
+ if (!ws->buffer.data) {
280
+ // no memory.
281
+ websocket_close(ws);
282
+ return;
283
+ }
284
+ }
285
+
286
+ const ssize_t len = fio_read(sockfd, (uint8_t *)ws->buffer.data + ws->length,
287
+ ws->buffer.size - ws->length);
288
+ if (len <= 0) {
289
+ return;
290
+ }
291
+ ws->length = websocket_consume(ws->buffer.data, ws->length + len, ws,
292
+ (~(ws->is_client) & 1));
293
+
294
+ fio_force_event(sockfd, FIO_EVENT_ON_DATA);
295
+ }
296
+
297
+ static void on_data_first(intptr_t sockfd, fio_protocol_s *ws_) {
298
+ ws_s *const ws = (ws_s *)ws_;
299
+ if (ws->on_open)
300
+ ws->on_open(ws);
301
+ ws->protocol.on_data = on_data;
302
+ ws->protocol.on_ready = on_ready;
303
+
304
+ if (ws->length) {
305
+ ws->length = websocket_consume(ws->buffer.data, ws->length, ws,
306
+ (~(ws->is_client) & 1));
307
+ }
308
+ fio_force_event(sockfd, FIO_EVENT_ON_DATA);
309
+ fio_force_event(sockfd, FIO_EVENT_ON_READY);
310
+ }
311
+
312
+ /* later */
313
+ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
314
+ char first, char last, char client);
315
+
316
+ /*******************************************************************************
317
+ Create/Destroy the websocket object
318
+ */
319
+
320
+ static ws_s *new_websocket(intptr_t uuid) {
321
+ // allocate the protocol object
322
+ ws_s *ws = malloc(sizeof(*ws));
323
+ FIO_ASSERT_ALLOC(ws);
324
+ *ws = (ws_s){
325
+ .protocol.ping = ws_ping,
326
+ .protocol.on_data = on_data_first,
327
+ .protocol.on_close = on_close,
328
+ .protocol.on_ready = NULL /* filled in after `on_open` */,
329
+ .protocol.on_shutdown = on_shutdown,
330
+ .subscriptions = FIO_LS_INIT(ws->subscriptions),
331
+ .is_client = 0,
332
+ .fd = uuid,
333
+ };
334
+ return ws;
335
+ }
336
+ static void destroy_ws(ws_s *ws) {
337
+ if (ws->on_close)
338
+ ws->on_close(ws->fd, ws->udata);
339
+ if (ws->msg)
340
+ fiobj_free(ws->msg);
341
+ clear_subscriptions(ws);
342
+ free_ws_buffer(ws, ws->buffer);
343
+ free(ws);
344
+ }
345
+
346
+ void websocket_attach(intptr_t uuid, http_settings_s *http_settings,
347
+ websocket_settings_s *args, void *data, size_t length) {
348
+ ws_s *ws = new_websocket(uuid);
349
+ // we have an active websocket connection - prep the connection buffer
350
+ ws->buffer = create_ws_buffer(ws);
351
+ // Setup ws callbacks
352
+ ws->on_open = args->on_open;
353
+ ws->on_close = args->on_close;
354
+ ws->on_message = args->on_message;
355
+ ws->on_ready = args->on_ready;
356
+ ws->on_shutdown = args->on_shutdown;
357
+ // setup any user data
358
+ ws->udata = args->udata;
359
+ if (http_settings) {
360
+ // client mode?
361
+ ws->is_client = http_settings->is_client;
362
+ // buffer limits
363
+ ws->max_msg_size = http_settings->ws_max_msg_size;
364
+ // update the timeout
365
+ fio_timeout_set(uuid, http_settings->ws_timeout);
366
+ } else {
367
+ ws->max_msg_size = (1024 * 256);
368
+ fio_timeout_set(uuid, 40);
369
+ }
370
+
371
+ if (data && length) {
372
+ if (length > ws->buffer.size) {
373
+ ws->buffer.size = length;
374
+ ws->buffer = resize_ws_buffer(ws, ws->buffer);
375
+ if (!ws->buffer.data) {
376
+ // no memory.
377
+ fio_attach(uuid, (fio_protocol_s *)ws);
378
+ websocket_close(ws);
379
+ return;
380
+ }
381
+ }
382
+ memcpy(ws->buffer.data, data, length);
383
+ ws->length = length;
384
+ }
385
+ // update the protocol object, cleaning up the old one
386
+ fio_attach(uuid, (fio_protocol_s *)ws);
387
+ // allow the on_open and on_data to take over the control.
388
+ fio_force_event(uuid, FIO_EVENT_ON_DATA);
389
+ }
390
+
391
+ /*******************************************************************************
392
+ Writing to the Websocket
393
+ */
394
+ #define WS_MAX_FRAME_SIZE \
395
+ (FIO_MEMORY_BLOCK_ALLOC_LIMIT - 4096) // should be less then `unsigned short`
396
+
397
+ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
398
+ char first, char last, char client) {
399
+ if (len <= WS_MAX_FRAME_SIZE) {
400
+ void *buff = fio_malloc(len + 16);
401
+ FIO_ASSERT_ALLOC(buff);
402
+ len = (client ? websocket_client_wrap(buff, data, len, (text ? 1 : 2),
403
+ first, last, 0)
404
+ : websocket_server_wrap(buff, data, len, (text ? 1 : 2),
405
+ first, last, 0));
406
+ fio_write2(fd, .data.buffer = buff, .length = len,
407
+ .after.dealloc = fio_free);
408
+ } else {
409
+ /* frame fragmentation is better for large data then large frames */
410
+ while (len > WS_MAX_FRAME_SIZE) {
411
+ websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client);
412
+ data = ((uint8_t *)data) + WS_MAX_FRAME_SIZE;
413
+ first = 0;
414
+ len -= WS_MAX_FRAME_SIZE;
415
+ }
416
+ websocket_write_impl(fd, data, len, text, first, 1, client);
417
+ }
418
+ return;
419
+ }
420
+
421
+ /* *****************************************************************************
422
+ Multi-client broadcast optimizations
423
+ ***************************************************************************** */
424
+
425
+ static void websocket_optimize_free(fio_msg_s *msg, void *metadata) {
426
+ fiobj_free((FIOBJ)metadata);
427
+ (void)msg;
428
+ }
429
+
430
+ static inline fio_msg_metadata_s websocket_optimize(fio_str_info_s msg,
431
+ unsigned char opcode) {
432
+ FIOBJ out = fiobj_str_buf(msg.len + 10);
433
+ fiobj_str_resize(out,
434
+ websocket_server_wrap(fiobj_obj2cstr(out).data, msg.data,
435
+ msg.len, opcode, 1, 1, 0));
436
+ fio_msg_metadata_s ret = {
437
+ .on_finish = websocket_optimize_free,
438
+ .metadata = (void *)out,
439
+ };
440
+ return ret;
441
+ }
442
+ static fio_msg_metadata_s websocket_optimize_generic(fio_str_info_s ch,
443
+ fio_str_info_s msg,
444
+ uint8_t is_json) {
445
+ fio_str_s tmp = FIO_STR_INIT_EXISTING(ch.data, ch.len, 0); // don't free
446
+ tmp.dealloc = NULL;
447
+ unsigned char opcode = 2;
448
+ if (tmp.len <= (2 << 19) && fio_str_utf8_valid(&tmp)) {
449
+ opcode = 1;
450
+ }
451
+ fio_msg_metadata_s ret = websocket_optimize(msg, opcode);
452
+ ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB;
453
+ return ret;
454
+ (void)ch;
455
+ (void)is_json;
456
+ }
457
+
458
+ static fio_msg_metadata_s websocket_optimize_text(fio_str_info_s ch,
459
+ fio_str_info_s msg,
460
+ uint8_t is_json) {
461
+ fio_msg_metadata_s ret = websocket_optimize(msg, 1);
462
+ ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB_TEXT;
463
+ return ret;
464
+ (void)ch;
465
+ (void)is_json;
466
+ }
467
+
468
+ static fio_msg_metadata_s websocket_optimize_binary(fio_str_info_s ch,
469
+ fio_str_info_s msg,
470
+ uint8_t is_json) {
471
+ fio_msg_metadata_s ret = websocket_optimize(msg, 2);
472
+ ret.type_id = WEBSOCKET_OPTIMIZE_PUBSUB_BINARY;
473
+ return ret;
474
+ (void)ch;
475
+ (void)is_json;
476
+ }
477
+
478
+ /**
479
+ * Enables (or disables) broadcast optimizations.
480
+ *
481
+ * When using WebSocket pub/sub system is originally optimized for either
482
+ * non-direct transmission (messages are handled by callbacks) or direct
483
+ * transmission to 1-3 clients per channel (on average), meaning that the
484
+ * majority of the messages are meant for a single recipient (or multiple
485
+ * callback recipients) and only some are expected to be directly transmitted to
486
+ * a group.
487
+ *
488
+ * However, when most messages are intended for direct transmission to more than
489
+ * 3 clients (on average), certain optimizations can be made to improve memory
490
+ * consumption (minimize duplication or WebSocket network data).
491
+ *
492
+ * This function allows enablement (or disablement) of these optimizations.
493
+ * These optimizations include:
494
+ *
495
+ * * WEBSOCKET_OPTIMIZE_PUBSUB - optimize all direct transmission messages,
496
+ * best attempt to detect Text vs. Binary data.
497
+ * * WEBSOCKET_OPTIMIZE_PUBSUB_TEXT - optimize direct pub/sub text messages.
498
+ * * WEBSOCKET_OPTIMIZE_PUBSUB_BINARY - optimize direct pub/sub binary messages.
499
+ *
500
+ * Note: to disable an optimization it should be disabled the same amount of
501
+ * times it was enabled - multiple optimization enablements for the same type
502
+ * are merged, but reference counted (disabled when reference is zero).
503
+ */
504
+ void websocket_optimize4broadcasts(intptr_t type, int enable) {
505
+ static intptr_t generic = 0;
506
+ static intptr_t text = 0;
507
+ static intptr_t binary = 0;
508
+ fio_msg_metadata_s (*callback)(fio_str_info_s, fio_str_info_s, uint8_t);
509
+ intptr_t *counter;
510
+ switch ((0 - type)) {
511
+ case (0 - WEBSOCKET_OPTIMIZE_PUBSUB):
512
+ counter = &generic;
513
+ callback = websocket_optimize_generic;
514
+ break;
515
+ case (0 - WEBSOCKET_OPTIMIZE_PUBSUB_TEXT):
516
+ counter = &text;
517
+ callback = websocket_optimize_text;
518
+ break;
519
+ case (0 - WEBSOCKET_OPTIMIZE_PUBSUB_BINARY):
520
+ counter = &binary;
521
+ callback = websocket_optimize_binary;
522
+ break;
523
+ default:
524
+ return;
525
+ }
526
+ if (enable) {
527
+ if (fio_atomic_add(counter, 1) == 1) {
528
+ fio_message_metadata_callback_set(callback, 1);
529
+ }
530
+ } else {
531
+ if (fio_atomic_sub(counter, 1) == 0) {
532
+ fio_message_metadata_callback_set(callback, 0);
533
+ }
534
+ }
535
+ }
536
+
537
+ /* *****************************************************************************
538
+ Subscription handling
539
+ ***************************************************************************** */
540
+
541
+ typedef struct {
542
+ void (*on_message)(ws_s *ws, fio_str_info_s channel, fio_str_info_s msg,
543
+ void *udata);
544
+ void (*on_unsubscribe)(void *udata);
545
+ void *udata;
546
+ } websocket_sub_data_s;
547
+
548
+ static inline void websocket_on_pubsub_message_direct_internal(fio_msg_s *msg,
549
+ uint8_t txt) {
550
+ fio_protocol_s *pr =
551
+ fio_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
552
+ if (!pr) {
553
+ if (errno == EBADF)
554
+ return;
555
+ fio_message_defer(msg);
556
+ return;
557
+ }
558
+ FIOBJ message = FIOBJ_INVALID;
559
+ FIOBJ pre_wrapped = FIOBJ_INVALID;
560
+ if (!((ws_s *)pr)->is_client) {
561
+ /* pre-wrapping is only for client data */
562
+ switch (txt) {
563
+ case 0:
564
+ pre_wrapped =
565
+ (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB_BINARY);
566
+ break;
567
+ case 1:
568
+ pre_wrapped =
569
+ (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB_TEXT);
570
+ break;
571
+ case 2:
572
+ pre_wrapped = (FIOBJ)fio_message_metadata(msg, WEBSOCKET_OPTIMIZE_PUBSUB);
573
+ break;
574
+ default:
575
+ break;
576
+ }
577
+ if (pre_wrapped) {
578
+ // FIO_LOG_DEBUG(
579
+ // "pub/sub WebSocket optimization route for pre-wrapped message.");
580
+ fiobj_send_free((intptr_t)msg->udata1, fiobj_dup(pre_wrapped));
581
+ goto finish;
582
+ }
583
+ }
584
+ if (txt == 2) {
585
+ /* unknown text state */
586
+ fio_str_s tmp =
587
+ FIO_STR_INIT_STATIC2(msg->msg.data, msg->msg.len); // don't free
588
+ txt = (tmp.len >= (2 << 14) ? 0 : fio_str_utf8_valid(&tmp));
589
+ }
590
+ websocket_write((ws_s *)pr, msg->msg, txt & 1);
591
+ fiobj_free(message);
592
+ finish:
593
+ fio_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
594
+ }
595
+
596
+ static void websocket_on_pubsub_message_direct(fio_msg_s *msg) {
597
+ websocket_on_pubsub_message_direct_internal(msg, 2);
598
+ }
599
+
600
+ static void websocket_on_pubsub_message_direct_txt(fio_msg_s *msg) {
601
+ websocket_on_pubsub_message_direct_internal(msg, 1);
602
+ }
603
+
604
+ static void websocket_on_pubsub_message_direct_bin(fio_msg_s *msg) {
605
+ websocket_on_pubsub_message_direct_internal(msg, 0);
606
+ }
607
+
608
+ static void websocket_on_pubsub_message(fio_msg_s *msg) {
609
+ fio_protocol_s *pr =
610
+ fio_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_TASK);
611
+ if (!pr) {
612
+ if (errno == EBADF)
613
+ return;
614
+ fio_message_defer(msg);
615
+ return;
616
+ }
617
+ websocket_sub_data_s *d = msg->udata2;
618
+
619
+ if (d->on_message)
620
+ d->on_message((ws_s *)pr, msg->channel, msg->msg, d->udata);
621
+ fio_protocol_unlock(pr, FIO_PR_LOCK_TASK);
622
+ }
623
+
624
+ static void websocket_on_unsubscribe(void *u1, void *u2) {
625
+ websocket_sub_data_s *d = u2;
626
+ if (d->on_unsubscribe) {
627
+ d->on_unsubscribe(d->udata);
628
+ }
629
+
630
+ if ((intptr_t)d->on_message == (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB) {
631
+ websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB, 0);
632
+ } else if ((intptr_t)d->on_message ==
633
+ (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB_TEXT) {
634
+ websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_TEXT, 0);
635
+ } else if ((intptr_t)d->on_message ==
636
+ (intptr_t)WEBSOCKET_OPTIMIZE_PUBSUB_BINARY) {
637
+ websocket_optimize4broadcasts(WEBSOCKET_OPTIMIZE_PUBSUB_BINARY, 0);
638
+ }
639
+ free(d);
640
+ (void)u1;
641
+ }
642
+
643
+ /**
644
+ * Returns a subscription ID on success and 0 on failure.
645
+ */
646
+ #undef websocket_subscribe
647
+ uintptr_t websocket_subscribe(struct websocket_subscribe_s args) {
648
+ if (!args.ws || !fio_is_valid(args.ws->fd))
649
+ goto error;
650
+ websocket_sub_data_s *d = malloc(sizeof(*d));
651
+ FIO_ASSERT_ALLOC(d);
652
+ *d = (websocket_sub_data_s){
653
+ .udata = args.udata,
654
+ .on_message = args.on_message,
655
+ .on_unsubscribe = args.on_unsubscribe,
656
+ };
657
+ void (*handler)(fio_msg_s *) = websocket_on_pubsub_message;
658
+ if (!args.on_message) {
659
+ intptr_t br_type;
660
+ if (args.force_binary) {
661
+ br_type = WEBSOCKET_OPTIMIZE_PUBSUB_BINARY;
662
+ handler = websocket_on_pubsub_message_direct_bin;
663
+ } else if (args.force_text) {
664
+ br_type = WEBSOCKET_OPTIMIZE_PUBSUB_TEXT;
665
+ handler = websocket_on_pubsub_message_direct_txt;
666
+ } else {
667
+ br_type = WEBSOCKET_OPTIMIZE_PUBSUB;
668
+ handler = websocket_on_pubsub_message_direct;
669
+ }
670
+ websocket_optimize4broadcasts(br_type, 1);
671
+ d->on_message =
672
+ (void (*)(ws_s *, fio_str_info_s, fio_str_info_s, void *))br_type;
673
+ }
674
+ subscription_s *sub =
675
+ fio_subscribe(.channel = args.channel, .match = args.match,
676
+ .on_unsubscribe = websocket_on_unsubscribe,
677
+ .on_message = handler, .udata1 = (void *)args.ws->fd,
678
+ .udata2 = d);
679
+ if (!sub) {
680
+ /* don't free `d`, return (`d` freed by fio_subscribe) */
681
+ return 0;
682
+ }
683
+ fio_ls_s *pos;
684
+ fio_lock(&args.ws->sub_lock);
685
+ pos = fio_ls_push(&args.ws->subscriptions, sub);
686
+ fio_unlock(&args.ws->sub_lock);
687
+
688
+ return (uintptr_t)pos;
689
+ error:
690
+ if (args.on_unsubscribe)
691
+ args.on_unsubscribe(args.udata);
692
+ return 0;
693
+ }
694
+
695
+ /**
696
+ * Unsubscribes from a channel.
697
+ */
698
+ void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id) {
699
+ fio_unsubscribe((subscription_s *)((fio_ls_s *)subscription_id)->obj);
700
+ fio_lock(&ws->sub_lock);
701
+ fio_ls_remove((fio_ls_s *)subscription_id);
702
+ fio_unlock(&ws->sub_lock);
703
+
704
+ (void)ws;
705
+ }
706
+
707
+ /*******************************************************************************
708
+ The API implementation
709
+ */
710
+
711
+ /** Returns the opaque user data associated with the websocket. */
712
+ void *websocket_udata_get(ws_s *ws) { return ws->udata; }
713
+
714
+ /** Returns the the process specific connection's UUID (see `libsock`). */
715
+ intptr_t websocket_uuid(ws_s *ws) { return ws->fd; }
716
+
717
+ /** Sets the opaque user data associated with the websocket.
718
+ * Returns the old value, if any. */
719
+ void *websocket_udata_set(ws_s *ws, void *udata) {
720
+ void *old = ws->udata;
721
+ ws->udata = udata;
722
+ return old;
723
+ }
724
+
725
+ /**
726
+ * Returns 1 if the WebSocket connection is in Client mode (connected to a
727
+ * remote server) and 0 if the connection is in Server mode (a connection
728
+ * established using facil.io's HTTP server).
729
+ */
730
+ uint8_t websocket_is_client(ws_s *ws) { return ws->is_client; }
731
+
732
+ /** Writes data to the websocket. Returns -1 on failure (0 on success). */
733
+ int websocket_write(ws_s *ws, fio_str_info_s msg, uint8_t is_text) {
734
+ if (fio_is_valid(ws->fd)) {
735
+ websocket_write_impl(ws->fd, msg.data, msg.len, is_text, 1, 1,
736
+ ws->is_client);
737
+ return 0;
738
+ }
739
+ return -1;
740
+ }
741
+ /** Closes a websocket connection. */
742
+ void websocket_close(ws_s *ws) {
743
+ if (ws->is_client) {
744
+ fio_write2(ws->fd, .data.buffer = "\x88\x80MASK", .length = 6,
745
+ .after.dealloc = FIO_DEALLOC_NOOP);
746
+ } else {
747
+ fio_write2(ws->fd, .data.buffer = "\x88\x00", .length = 2,
748
+ .after.dealloc = FIO_DEALLOC_NOOP);
749
+ }
750
+ fio_close(ws->fd);
751
+ return;
752
+ }