iodine 0.7.60 → 0.8.0

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