isomorfeus-iodine 0.7.44

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