iodine 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/LIMITS.md +25 -0
  4. data/README.md +39 -80
  5. data/SPEC-Websocket-Draft.md +129 -4
  6. data/bin/echo +2 -2
  7. data/bin/http-hello +1 -0
  8. data/bin/updated api +113 -0
  9. data/bin/ws-echo +0 -1
  10. data/examples/broadcast.ru +56 -0
  11. data/examples/echo.ru +57 -0
  12. data/examples/hello.ru +30 -0
  13. data/examples/redis.ru +69 -0
  14. data/examples/shootout.ru +53 -0
  15. data/exe/iodine +2 -80
  16. data/ext/iodine/defer.c +11 -5
  17. data/ext/iodine/empty.h +26 -0
  18. data/ext/iodine/evio.h +1 -1
  19. data/ext/iodine/facil.c +103 -61
  20. data/ext/iodine/facil.h +20 -12
  21. data/ext/iodine/fio_dict.c +446 -0
  22. data/ext/iodine/fio_dict.h +90 -0
  23. data/ext/iodine/fio_hash_table.h +370 -0
  24. data/ext/iodine/fio_list.h +30 -3
  25. data/ext/iodine/http.c +169 -37
  26. data/ext/iodine/http.h +33 -10
  27. data/ext/iodine/http1.c +78 -42
  28. data/ext/iodine/http_request.c +6 -0
  29. data/ext/iodine/http_request.h +3 -0
  30. data/ext/iodine/http_response.c +43 -11
  31. data/ext/iodine/iodine.c +380 -0
  32. data/ext/iodine/iodine.h +62 -0
  33. data/ext/iodine/iodine_helpers.c +235 -0
  34. data/ext/iodine/iodine_helpers.h +13 -0
  35. data/ext/iodine/iodine_http.c +409 -241
  36. data/ext/iodine/iodine_http.h +7 -14
  37. data/ext/iodine/iodine_protocol.c +626 -0
  38. data/ext/iodine/iodine_protocol.h +13 -0
  39. data/ext/iodine/iodine_pubsub.c +646 -0
  40. data/ext/iodine/iodine_pubsub.h +27 -0
  41. data/ext/iodine/iodine_websockets.c +796 -0
  42. data/ext/iodine/iodine_websockets.h +19 -0
  43. data/ext/iodine/pubsub.c +544 -0
  44. data/ext/iodine/pubsub.h +215 -0
  45. data/ext/iodine/random.c +4 -4
  46. data/ext/iodine/rb-call.c +1 -5
  47. data/ext/iodine/rb-defer.c +3 -20
  48. data/ext/iodine/rb-rack-io.c +22 -22
  49. data/ext/iodine/rb-rack-io.h +3 -4
  50. data/ext/iodine/rb-registry.c +111 -118
  51. data/ext/iodine/redis_connection.c +277 -0
  52. data/ext/iodine/redis_connection.h +77 -0
  53. data/ext/iodine/redis_engine.c +398 -0
  54. data/ext/iodine/redis_engine.h +68 -0
  55. data/ext/iodine/resp.c +842 -0
  56. data/ext/iodine/resp.h +253 -0
  57. data/ext/iodine/sock.c +26 -12
  58. data/ext/iodine/sock.h +14 -3
  59. data/ext/iodine/spnlock.inc +19 -2
  60. data/ext/iodine/websockets.c +299 -11
  61. data/ext/iodine/websockets.h +159 -6
  62. data/lib/iodine.rb +104 -1
  63. data/lib/iodine/cli.rb +106 -0
  64. data/lib/iodine/monkeypatch.rb +40 -0
  65. data/lib/iodine/pubsub.rb +70 -0
  66. data/lib/iodine/version.rb +1 -1
  67. data/lib/iodine/websocket.rb +12 -0
  68. data/lib/rack/handler/iodine.rb +33 -7
  69. metadata +35 -7
  70. data/ext/iodine/iodine_core.c +0 -760
  71. data/ext/iodine/iodine_core.h +0 -79
  72. data/ext/iodine/iodine_websocket.c +0 -551
  73. data/ext/iodine/iodine_websocket.h +0 -22
  74. data/lib/iodine/http.rb +0 -4
@@ -0,0 +1,27 @@
1
+ #ifndef H_IODINE_ENGINE_H
2
+ #define H_IODINE_ENGINE_H
3
+ /*
4
+ Copyright: Boaz segev, 2016-2017
5
+ License: MIT
6
+
7
+ Feel free to copy, use and enjoy according to the license provided.
8
+ */
9
+ #include "iodine.h"
10
+ #include "pubsub.h"
11
+
12
+ extern VALUE IodineEngine;
13
+
14
+ typedef struct {
15
+ pubsub_engine_s engine;
16
+ pubsub_engine_s *p;
17
+ VALUE handler;
18
+ void (*dealloc)(pubsub_engine_s *);
19
+ } iodine_engine_s;
20
+
21
+ void Iodine_init_pubsub(void);
22
+
23
+ typedef struct pubsub_engine_s pubsub_engine_s;
24
+
25
+ pubsub_engine_s *iodine_engine_ruby2facil(VALUE engine);
26
+
27
+ #endif
@@ -0,0 +1,796 @@
1
+ /*
2
+ Copyright: Boaz segev, 2016-2017
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #include "iodine_websockets.h"
8
+ #include "iodine_pubsub.h"
9
+
10
+ #include "pubsub.h"
11
+ #include "websockets.h"
12
+
13
+ #include <arpa/inet.h>
14
+ #include <ruby/io.h>
15
+
16
+ /* *****************************************************************************
17
+ Core helpers and data
18
+ ***************************************************************************** */
19
+
20
+ static VALUE IodineWebsocket; // The Iodine::Http::Websocket class
21
+ static ID ws_var_id; // id for websocket pointer
22
+ static ID dup_func_id; // id for the buffer.dup method
23
+
24
+ static VALUE force_var_id;
25
+ static VALUE channel_var_id;
26
+ static VALUE pattern_var_id;
27
+ static VALUE text_var_id;
28
+ static VALUE binary_var_id;
29
+ static VALUE engine_var_id;
30
+ static VALUE message_var_id;
31
+
32
+ #define set_uuid(object, request) \
33
+ rb_ivar_set((object), iodine_fd_var_id, ULONG2NUM((request)->fd))
34
+
35
+ inline static intptr_t get_uuid(VALUE obj) {
36
+ VALUE i = rb_ivar_get(obj, iodine_fd_var_id);
37
+ return i != Qnil ? (intptr_t)FIX2ULONG(i) : 0;
38
+ }
39
+
40
+ #define set_ws(object, ws) \
41
+ rb_ivar_set((object), ws_var_id, ULONG2NUM(((VALUE)(ws))))
42
+
43
+ inline static ws_s *get_ws(VALUE obj) {
44
+ VALUE i = rb_ivar_get(obj, ws_var_id);
45
+ if (i == Qnil)
46
+ return NULL;
47
+ return (ws_s *)FIX2ULONG(i);
48
+ }
49
+
50
+ #define set_handler(ws, handler) websocket_udata_set((ws), (VALUE)handler)
51
+ #define get_handler(ws) ((VALUE)websocket_udata((ws_s *)(ws)))
52
+
53
+ /* *****************************************************************************
54
+ Buffer management - Rubyfy the way the buffer is handled.
55
+ ***************************************************************************** */
56
+
57
+ struct buffer_s {
58
+ void *data;
59
+ size_t size;
60
+ };
61
+
62
+ /** returns a buffer_s struct, with a buffer (at least) `size` long. */
63
+ struct buffer_s create_ws_buffer(ws_s *owner);
64
+
65
+ /** returns a buffer_s struct, with a buffer (at least) `size` long. */
66
+ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
67
+
68
+ /** releases an existing buffer. */
69
+ void free_ws_buffer(ws_s *owner, struct buffer_s);
70
+
71
+ /** Sets the initial buffer size. (4Kb)*/
72
+ #define WS_INITIAL_BUFFER_SIZE 4096
73
+
74
+ // buffer increments by 4,096 Bytes (4Kb)
75
+ #define round_up_buffer_size(size) ((((size) >> 12) + 1) << 12)
76
+
77
+ struct buffer_args {
78
+ struct buffer_s buffer;
79
+ ws_s *ws;
80
+ };
81
+
82
+ void *ruby_land_buffer(void *_buf) {
83
+ #define args ((struct buffer_args *)(_buf))
84
+ if (args->buffer.data == NULL) {
85
+ VALUE rbbuff = rb_str_buf_new(WS_INITIAL_BUFFER_SIZE);
86
+ rb_ivar_set(get_handler(args->ws), iodine_buff_var_id, rbbuff);
87
+ rb_str_set_len(rbbuff, 0);
88
+ rb_enc_associate(rbbuff, IodineBinaryEncoding);
89
+ args->buffer.data = RSTRING_PTR(rbbuff);
90
+ args->buffer.size = WS_INITIAL_BUFFER_SIZE;
91
+
92
+ } else {
93
+ VALUE rbbuff = rb_ivar_get(get_handler(args->ws), iodine_buff_var_id);
94
+ rb_str_modify(rbbuff);
95
+ rb_str_resize(rbbuff, args->buffer.size);
96
+ args->buffer.data = RSTRING_PTR(rbbuff);
97
+ args->buffer.size = rb_str_capacity(rbbuff);
98
+ }
99
+ return NULL;
100
+ #undef args
101
+ }
102
+
103
+ struct buffer_s create_ws_buffer(ws_s *owner) {
104
+ struct buffer_args args = {.ws = owner};
105
+ RubyCaller.call_c(ruby_land_buffer, &args);
106
+ return args.buffer;
107
+ }
108
+
109
+ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buffer) {
110
+ buffer.size = round_up_buffer_size(buffer.size);
111
+ struct buffer_args args = {.ws = owner, .buffer = buffer};
112
+ RubyCaller.call_c(ruby_land_buffer, &args);
113
+ return args.buffer;
114
+ }
115
+ void free_ws_buffer(ws_s *owner, struct buffer_s buff) {
116
+ (void)(owner);
117
+ (void)(buff);
118
+ }
119
+
120
+ #undef round_up_buffer_size
121
+
122
+ /* *****************************************************************************
123
+ Websocket Ruby API
124
+ ***************************************************************************** */
125
+
126
+ /** Closes the websocket connection. The connection is only closed after
127
+ * existing data in the outgoing buffer is sent. */
128
+ static VALUE iodine_ws_close(VALUE self) {
129
+ ws_s *ws = get_ws(self);
130
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
131
+ return Qfalse;
132
+ websocket_close(ws);
133
+ return self;
134
+ }
135
+
136
+ /**
137
+ * Writes data to the websocket.
138
+ *
139
+ * Returns `true` on success or `false if the websocket was closed or an error
140
+ * occurred.
141
+ *
142
+ * `write` will return immediately UNLESS resources are insufficient. If the
143
+ * global `write` buffer is full, `write` will block until a buffer "packet"
144
+ * becomes available and can be assigned to the socket. */
145
+ static VALUE iodine_ws_write(VALUE self, VALUE data) {
146
+ Check_Type(data, T_STRING);
147
+ ws_s *ws = get_ws(self);
148
+ // if ((void *)ws == (void *)0x04 || (void *)data == (void *)0x04 ||
149
+ // RSTRING_PTR(data) == (void *)0x04)
150
+ // fprintf(stderr, "iodine_ws_write: self = %p ; data = %p\n"
151
+ // "\t\tString ptr: %p, String length: %lu\n",
152
+ // (void *)ws, (void *)data, RSTRING_PTR(data), RSTRING_LEN(data));
153
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
154
+ return Qfalse;
155
+ websocket_write(ws, RSTRING_PTR(data), RSTRING_LEN(data),
156
+ rb_enc_get(data) == IodineUTF8Encoding);
157
+ return Qtrue;
158
+ }
159
+
160
+ /** Returns the number of active websocket connections (including connections
161
+ * that are in the process of closing down). */
162
+ static VALUE iodine_ws_count(VALUE self) {
163
+ return LONG2FIX(websocket_count());
164
+ (void)self;
165
+ }
166
+
167
+ /**
168
+ Returns a weak indication as to the state of the socket's buffer. If the server
169
+ has data in the buffer that wasn't written to the socket, `has_pending?` will
170
+ return `true`, otherwise `false` will be returned.
171
+ */
172
+ static VALUE iodine_ws_has_pending(VALUE self) {
173
+ intptr_t uuid = get_uuid(self);
174
+ return sock_has_pending(uuid) ? Qtrue : Qfalse;
175
+ }
176
+
177
+ /**
178
+ Returns a connection's UUID which is valid for *this process* (not a machine
179
+ or internet unique value).
180
+
181
+ This can be used together with a true process wide UUID to uniquely identify a
182
+ connection across the internet.
183
+ */
184
+ static VALUE iodine_ws_uuid(VALUE self) {
185
+ intptr_t uuid = get_uuid(self);
186
+ return LONG2FIX(uuid);
187
+ }
188
+
189
+ /* *****************************************************************************
190
+ Websocket defer
191
+ ***************************************************************************** */
192
+
193
+ static void iodine_perform_defer(intptr_t uuid, protocol_s *protocol,
194
+ void *arg) {
195
+ (void)(uuid);
196
+ VALUE obj = protocol->service == WEBSOCKET_ID_STR ? get_handler(protocol)
197
+ : (VALUE)(protocol + 1);
198
+ RubyCaller.call2((VALUE)arg, iodine_call_proc_id, 1, &obj);
199
+ Registry.remove((VALUE)arg);
200
+ }
201
+
202
+ static void iodine_defer_fallback(intptr_t uuid, void *arg) {
203
+ (void)(uuid);
204
+ Registry.remove((VALUE)arg);
205
+ }
206
+
207
+ /**
208
+ Schedules a block of code to execute at a later time, **if** the connection is
209
+ still open and while preventing concurent code from running for the same
210
+ connection object.
211
+
212
+ An optional `conn_id` argument can be passed along, so that the block of code
213
+ will run for the requested connection rather then this connection.
214
+
215
+ **Careful**: as this might cause this connection's object to run code
216
+ concurrently when data owned by this connection is accessed from within the
217
+ block of code.
218
+
219
+ On success returns the block, otherwise (connection invalid) returns `false`. A
220
+ sucessful event registration doesn't guaranty that the block will be called (the
221
+ connection might close between the event registration and the execution).
222
+ */
223
+ static VALUE iodine_defer(int argc, VALUE *argv, VALUE self) {
224
+ intptr_t fd;
225
+ // check arguments.
226
+ if (argc > 1)
227
+ rb_raise(rb_eArgError, "this function expects no more then 1 (optional) "
228
+ "argument.");
229
+ else if (argc == 1) {
230
+ Check_Type(*argv, T_FIXNUM);
231
+ fd = FIX2LONG(*argv);
232
+ if (!sock_isvalid(fd))
233
+ return Qfalse;
234
+ } else
235
+ fd = iodine_get_fd(self);
236
+ if (!fd)
237
+ rb_raise(rb_eArgError, "This method requires a target connection.");
238
+ // requires a block to be passed
239
+ rb_need_block();
240
+ VALUE block = rb_block_proc();
241
+ if (block == Qnil)
242
+ return Qfalse;
243
+ Registry.add(block);
244
+
245
+ facil_defer(.uuid = fd, .task = iodine_perform_defer, .arg = (void *)block,
246
+ .fallback = iodine_defer_fallback);
247
+ return block;
248
+ }
249
+
250
+ /* *****************************************************************************
251
+ Websocket Pub/Sub API
252
+ ***************************************************************************** */
253
+
254
+ static void iodine_on_unsubscribe(void *u) {
255
+ if (u && (VALUE)u != Qnil && u != (VALUE)Qfalse)
256
+ Registry.remove((VALUE)u);
257
+ }
258
+
259
+ static void *on_pubsub_notificationinGVL(websocket_pubsub_notification_s *n) {
260
+ VALUE rbn[2];
261
+ rbn[0] = rb_str_new(n->channel.name, n->channel.len);
262
+ Registry.add(rbn[0]);
263
+ rbn[1] = rb_str_new(n->msg.data, n->msg.len);
264
+ Registry.add(rbn[1]);
265
+ RubyCaller.call2((VALUE)n->udata, iodine_call_proc_id, 2, rbn);
266
+ Registry.remove(rbn[0]);
267
+ Registry.remove(rbn[1]);
268
+ return NULL;
269
+ }
270
+
271
+ static void on_pubsub_notificationin(websocket_pubsub_notification_s n) {
272
+ RubyCaller.call_c((void *(*)(void *))on_pubsub_notificationinGVL, &n);
273
+ }
274
+
275
+ /**
276
+ Subscribes the websocket to a channel belonging to a specific pub/sub service
277
+ (using an {Iodine::PubSub::Engine} to connect Iodine to the service).
278
+
279
+ The function accepts a single argument (a Hash) and an optional block.
280
+
281
+ If no block is provided, the message is sent directly to the websocket client.
282
+
283
+ Accepts a single Hash argument with the following possible options:
284
+
285
+ :engine :: If provided, the engine to use for pub/sub. Otherwise the default
286
+ engine is used.
287
+
288
+ :channel :: Required (unless :pattern). The channel to subscribe to.
289
+
290
+ :pattern :: An alternative to the required :channel, subscribes to a pattern.
291
+
292
+ :force :: This can be set to either nil, :text or :binary and controls the way
293
+ the message will be forwarded to the websocket client. This is only valid if no
294
+ block was provided. Defaults to smart encoding based testing.
295
+
296
+
297
+ */
298
+ static VALUE iodine_ws_subscribe(VALUE self, VALUE args) {
299
+ Check_Type(args, T_HASH);
300
+ ws_s *ws = get_ws(self);
301
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
302
+ return Qfalse;
303
+ uint8_t use_pattern = 0, force_text = 0, force_binary = 0;
304
+
305
+ VALUE rb_ch = rb_hash_aref(args, channel_var_id);
306
+ if (rb_ch == Qnil || rb_ch == Qfalse) {
307
+ use_pattern = 1;
308
+ rb_ch = rb_hash_aref(args, pattern_var_id);
309
+ if (rb_ch == Qnil || rb_ch == Qfalse)
310
+ rb_raise(rb_eArgError, "channel is required for pub/sub methods.");
311
+ }
312
+ if (TYPE(rb_ch) == T_SYMBOL)
313
+ rb_ch = rb_sym2str(rb_ch);
314
+ Check_Type(rb_ch, T_STRING);
315
+
316
+ VALUE tmp = rb_hash_aref(args, force_var_id);
317
+ if (tmp == text_var_id)
318
+ force_text = 1;
319
+ else if (tmp == binary_var_id)
320
+ force_binary = 1;
321
+
322
+ VALUE block = 0;
323
+ if (rb_block_given_p()) {
324
+ block = rb_block_proc();
325
+ Registry.add(block);
326
+ }
327
+
328
+ pubsub_engine_s *engine =
329
+ iodine_engine_ruby2facil(rb_hash_aref(args, engine_var_id));
330
+
331
+ uintptr_t subid = websocket_subscribe(
332
+ ws, .channel.name = RSTRING_PTR(rb_ch), .channel.len = RSTRING_LEN(rb_ch),
333
+ .engine = engine, .use_pattern = use_pattern, .force_text = force_text,
334
+ .force_binary = force_binary,
335
+ .on_message = (block ? on_pubsub_notificationin : NULL),
336
+ .on_unsubscribe = (block ? iodine_on_unsubscribe : NULL),
337
+ .udata = (void *)block);
338
+ if (!subid)
339
+ return Qnil;
340
+ return ULL2NUM(subid);
341
+ }
342
+ /**
343
+ Searches for the subscription ID for the describes subscription.
344
+
345
+ Takes the same arguments as {subscribe}, a single Hash argument with the
346
+ following possible options:
347
+
348
+ :engine :: If provided, the engine to use for pub/sub. Otherwise the default
349
+ engine is used.
350
+
351
+ :channel :: The subscription's channel.
352
+
353
+ :pattern :: An alternative to the required :channel, subscribes to a pattern.
354
+
355
+ :force :: This can be set to either nil, :text or :binary and controls the way
356
+ the message will be forwarded to the websocket client. This is only valid if no
357
+ block was provided. Defaults to smart encoding based testing.
358
+
359
+ */
360
+ static VALUE iodine_ws_is_subscribed(VALUE self, VALUE args) {
361
+ Check_Type(args, T_HASH);
362
+ ws_s *ws = get_ws(self);
363
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
364
+ return Qfalse;
365
+ uint8_t use_pattern = 0, force_text = 0, force_binary = 0;
366
+
367
+ VALUE rb_ch = rb_hash_aref(args, channel_var_id);
368
+ if (rb_ch == Qnil || rb_ch == Qfalse) {
369
+ use_pattern = 1;
370
+ rb_ch = rb_hash_aref(args, pattern_var_id);
371
+ if (rb_ch == Qnil || rb_ch == Qfalse)
372
+ rb_raise(rb_eArgError, "channel is required for pub/sub methods.");
373
+ }
374
+ if (TYPE(rb_ch) == T_SYMBOL)
375
+ rb_ch = rb_sym2str(rb_ch);
376
+ Check_Type(rb_ch, T_STRING);
377
+
378
+ VALUE tmp = rb_hash_aref(args, force_var_id);
379
+ if (tmp == text_var_id)
380
+ force_text = 1;
381
+ else if (tmp == binary_var_id)
382
+ force_binary = 1;
383
+
384
+ VALUE block = 0;
385
+ if (rb_block_given_p()) {
386
+ block = rb_block_proc();
387
+ }
388
+
389
+ pubsub_engine_s *engine =
390
+ iodine_engine_ruby2facil(rb_hash_aref(args, engine_var_id));
391
+
392
+ uintptr_t subid = websocket_find_sub(
393
+ ws, .channel.name = RSTRING_PTR(rb_ch), .channel.len = RSTRING_LEN(rb_ch),
394
+ .engine = engine, .use_pattern = use_pattern, .force_text = force_text,
395
+ .force_binary = force_binary,
396
+ .on_message = (block ? on_pubsub_notificationin : NULL),
397
+ .udata = (void *)block);
398
+ if (!subid)
399
+ return Qnil;
400
+ return LONG2NUM(subid);
401
+ }
402
+
403
+ /**
404
+ Cancels the subscription matching `sub_id`.
405
+ */
406
+ static VALUE iodine_ws_unsubscribe(VALUE self, VALUE sub_id) {
407
+ if (sub_id == Qnil || sub_id == Qfalse)
408
+ return Qnil;
409
+ ws_s *ws = get_ws(self);
410
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
411
+ return Qfalse;
412
+ Check_Type(sub_id, T_FIXNUM);
413
+ websocket_unsubscribe(ws, NUM2LONG(sub_id));
414
+ return Qnil;
415
+ }
416
+
417
+ /**
418
+ Publishes a message to a channel.
419
+
420
+ Accepts a single Hash argument with the following possible options:
421
+
422
+ :engine :: If provided, the engine to use for pub/sub. Otherwise the default
423
+ engine is used.
424
+
425
+ :channel :: Required (unless :pattern). The channel to publish to.
426
+
427
+ :pattern :: An alternative to the required :channel, publishes to a pattern.
428
+ This is NOT supported by Redis and it's limited to the local process cluster.
429
+
430
+ :message :: REQUIRED. The message to be published.
431
+ :
432
+ */
433
+ static VALUE iodine_ws_publish(VALUE self, VALUE args) {
434
+ Check_Type(args, T_HASH);
435
+ uint8_t use_pattern = 0;
436
+
437
+ VALUE rb_ch = rb_hash_aref(args, channel_var_id);
438
+ if (rb_ch == Qnil || rb_ch == Qfalse) {
439
+ use_pattern = 1;
440
+ rb_ch = rb_hash_aref(args, pattern_var_id);
441
+ if (rb_ch == Qnil || rb_ch == Qfalse)
442
+ rb_raise(rb_eArgError, "channel is required for pub/sub methods.");
443
+ }
444
+ if (TYPE(rb_ch) == T_SYMBOL)
445
+ rb_ch = rb_sym2str(rb_ch);
446
+ Check_Type(rb_ch, T_STRING);
447
+
448
+ VALUE rb_msg = rb_hash_aref(args, message_var_id);
449
+ if (rb_msg == Qnil || rb_msg == Qfalse) {
450
+ rb_raise(rb_eArgError, "message is required for the :publish method.");
451
+ }
452
+ Check_Type(rb_msg, T_STRING);
453
+
454
+ pubsub_engine_s *engine =
455
+ iodine_engine_ruby2facil(rb_hash_aref(args, engine_var_id));
456
+
457
+ intptr_t subid =
458
+ pubsub_publish(.engine = engine, .channel.name = (RSTRING_PTR(rb_ch)),
459
+ .channel.len = (RSTRING_LEN(rb_ch)),
460
+ .msg.data = (RSTRING_PTR(rb_msg)),
461
+ .msg.len = (RSTRING_LEN(rb_msg)),
462
+ .use_pattern = use_pattern);
463
+ if (!subid)
464
+ return Qfalse;
465
+ return Qtrue;
466
+ (void)self;
467
+ }
468
+
469
+ /* *****************************************************************************
470
+ Websocket Multi-Write - Deprecated
471
+ ***************************************************************************** */
472
+
473
+ // static uint8_t iodine_ws_if_callback(ws_s *ws, void *block) {
474
+ // if (!ws)
475
+ // return 0;
476
+ // VALUE handler = get_handler(ws);
477
+ // uint8_t ret = 0;
478
+ // if (handler)
479
+ // ret = RubyCaller.call2((VALUE)block, iodine_call_proc_id, 1, &handler);
480
+ // return ret && ret != Qnil && ret != Qfalse;
481
+ // }
482
+ //
483
+ // static void iodine_ws_write_each_complete(ws_s *ws, void *block) {
484
+ // (void)ws;
485
+ // if ((VALUE)block != Qnil)
486
+ // Registry.remove((VALUE)block);
487
+ // }
488
+
489
+ /**
490
+ * Writes data to all the Websocket connections sharing the same process
491
+ * (worker) except `self`.
492
+ *
493
+ * If a block is given, it will be passed each Websocket connection in turn
494
+ * (much like `each`) and send the data only if the block returns a "truthy"
495
+ * value (i.e. NOT `false` or `nil`).
496
+ *
497
+ * See both {#write} and {#each} for more details.
498
+ */
499
+ // static VALUE iodine_ws_multiwrite(VALUE self, VALUE data) {
500
+ // Check_Type(data, T_STRING);
501
+ // ws_s *ws = get_ws(self);
502
+ // // if ((void *)ws == (void *)0x04 || (void *)data == (void *)0x04 ||
503
+ // // RSTRING_PTR(data) == (void *)0x04)
504
+ // // fprintf(stderr, "iodine_ws_write: self = %p ; data = %p\n"
505
+ // // "\t\tString ptr: %p, String length: %lu\n",
506
+ // // (void *)ws, (void *)data, RSTRING_PTR(data),
507
+ // RSTRING_LEN(data)); if (!ws || ((protocol_s *)ws)->service !=
508
+ // WEBSOCKET_ID_STR)
509
+ // ws = NULL;
510
+ //
511
+ // VALUE block = Qnil;
512
+ // if (rb_block_given_p())
513
+ // block = rb_block_proc();
514
+ // if (block != Qnil)
515
+ // Registry.add(block);
516
+ // websocket_write_each(.origin = ws, .data = RSTRING_PTR(data),
517
+ // .length = RSTRING_LEN(data),
518
+ // .is_text = (rb_enc_get(data) == IodineUTF8Encoding),
519
+ // .on_finished = iodine_ws_write_each_complete,
520
+ // .filter =
521
+ // ((block == Qnil) ? NULL : iodine_ws_if_callback),
522
+ // .arg = (void *)block);
523
+ // return Qtrue;
524
+ // }
525
+
526
+ /* *****************************************************************************
527
+ Websocket task performance
528
+ */
529
+
530
+ static void iodine_ws_perform_each_task(intptr_t fd, protocol_s *protocol,
531
+ void *data) {
532
+ (void)(fd);
533
+ VALUE handler = get_handler(protocol);
534
+ if (handler)
535
+ RubyCaller.call2((VALUE)data, iodine_call_proc_id, 1, &handler);
536
+ }
537
+ static void iodine_ws_finish_each_task(intptr_t fd, void *data) {
538
+ (void)(fd);
539
+ Registry.remove((VALUE)data);
540
+ }
541
+
542
+ inline static void iodine_ws_run_each(intptr_t origin, VALUE block) {
543
+ facil_each(.origin = origin, .service = WEBSOCKET_ID_STR,
544
+ .task = iodine_ws_perform_each_task, .arg = (void *)block,
545
+ .on_complete = iodine_ws_finish_each_task);
546
+ }
547
+
548
+ /** Performs a block of code for each websocket connection. The function returns
549
+ the block of code.
550
+
551
+ The block of code should accept a single variable which is the websocket
552
+ connection.
553
+
554
+ i.e.:
555
+
556
+ def on_message data
557
+ msg = data.dup; # data will be overwritten once the function exists.
558
+ each {|ws| ws.write msg}
559
+ end
560
+
561
+
562
+ The block of code will be executed asynchronously, to avoid having two blocks
563
+ of code running at the same time and minimizing race conditions when using
564
+ multilple threads.
565
+ */
566
+ static VALUE iodine_ws_each(VALUE self) {
567
+ // requires a block to be passed
568
+ rb_need_block();
569
+ VALUE block = rb_block_proc();
570
+ if (block == Qnil)
571
+ return Qnil;
572
+ Registry.add(block);
573
+ intptr_t fd = get_uuid(self);
574
+ iodine_ws_run_each(fd, block);
575
+ return block;
576
+ }
577
+
578
+ /**
579
+ Runs the required block for each websocket.
580
+
581
+ Tasks will be performed asynchronously, within each connection's lock, so no
582
+ connection will have more then one task being performed at the same time
583
+ (similar to {#defer}).
584
+
585
+ Also, unlike {Iodine.run}, the block will **not** be called unless the
586
+ websocket is still open at the time it's execution begins.
587
+
588
+ Always returns `self`.
589
+ */
590
+ static VALUE iodine_ws_class_each(VALUE self) {
591
+ // requires a block to be passed
592
+ rb_need_block();
593
+ VALUE block = rb_block_proc();
594
+ if (block == Qnil)
595
+ return Qfalse;
596
+ Registry.add(block);
597
+ iodine_ws_run_each(-1, block);
598
+ return self;
599
+ }
600
+
601
+ /**
602
+ Schedules a block of code to run for the specified websocket at a later time,
603
+ (**if** the connection is open). The block will run within the connection's
604
+ lock, offering a fast concurrency synchronizing tool.
605
+
606
+ The block of code will receive the websocket's callback object. i.e.
607
+
608
+ Iodine::Websocket.defer(uuid) {|ws| ws.write "I'm doing this" }
609
+
610
+ On success returns the block, otherwise (connection invalid) returns `false`.
611
+
612
+ A sucessful event registration doesn't guaranty that the block will be called
613
+ (the connection might close between the event registration and the execution).
614
+ */
615
+ static VALUE iodine_class_defer(VALUE self, VALUE ws_uuid) {
616
+ (void)(self);
617
+ intptr_t fd = FIX2LONG(ws_uuid);
618
+ if (!sock_isvalid(fd))
619
+ return Qfalse;
620
+ // requires a block to be passed
621
+ rb_need_block();
622
+ VALUE block = rb_block_proc();
623
+ if (block == Qnil)
624
+ return Qfalse;
625
+ Registry.add(block);
626
+
627
+ facil_defer(.uuid = fd, .task = iodine_perform_defer, .arg = (void *)block,
628
+ .fallback = iodine_defer_fallback);
629
+ return block;
630
+ }
631
+
632
+ //////////////////////////////////////
633
+ // Protocol functions
634
+ void ws_on_open(ws_s *ws) {
635
+ VALUE handler = get_handler(ws);
636
+ if (!handler)
637
+ return;
638
+ set_ws(handler, ws);
639
+ RubyCaller.call(handler, iodine_on_open_func_id);
640
+ }
641
+ void ws_on_close(ws_s *ws) {
642
+ VALUE handler = get_handler(ws);
643
+ if (!handler) {
644
+ fprintf(stderr, "Closing a handlerless websocket?!\n");
645
+ return;
646
+ }
647
+ RubyCaller.call(handler, iodine_on_close_func_id);
648
+ set_ws(handler, Qnil);
649
+ Registry.remove(handler);
650
+ }
651
+ void ws_on_shutdown(ws_s *ws) {
652
+ VALUE handler = get_handler(ws);
653
+ if (!handler)
654
+ return;
655
+ RubyCaller.call(handler, iodine_on_shutdown_func_id);
656
+ }
657
+ void ws_on_ready(ws_s *ws) {
658
+ VALUE handler = get_handler(ws);
659
+ if (!handler)
660
+ return;
661
+ RubyCaller.call(handler, iodine_on_ready_func_id);
662
+ }
663
+ void ws_on_data(ws_s *ws, char *data, size_t length, uint8_t is_text) {
664
+ (void)(data);
665
+ VALUE handler = get_handler(ws);
666
+ if (!handler)
667
+ return;
668
+ VALUE buffer = rb_ivar_get(handler, iodine_buff_var_id);
669
+ if (is_text)
670
+ rb_enc_associate(buffer, IodineUTF8Encoding);
671
+ else
672
+ rb_enc_associate(buffer, IodineBinaryEncoding);
673
+ rb_str_set_len(buffer, length);
674
+ RubyCaller.call2(handler, iodine_on_message_func_id, 1, &buffer);
675
+ }
676
+
677
+ //////////////
678
+ // Empty callbacks for default implementations.
679
+
680
+ /** Please implement your own callback for this event.
681
+ */
682
+ static VALUE empty_func(VALUE self) {
683
+ (void)(self);
684
+ return Qnil;
685
+ }
686
+ // /* The `on_message(data)` callback is the main method for any websocket
687
+ // implementation. It is the only required callback for a websocket handler
688
+ // (without this handler, errors will occur).
689
+ //
690
+ // <b>NOTICE</b>: the data passed to the `on_message` callback is the actual
691
+ // recycble network buffer, not a copy! <b>Use `data.dup` before moving the data
692
+ // out of the function's scope</b> to prevent data corruption (i.e. when
693
+ // using the data within an `each` block). For example (broadcasting):
694
+ //
695
+ // def on_message data
696
+ // msg = data.dup; # data will be overwritten once the function exists.
697
+ // each {|ws| ws.write msg}
698
+ // end
699
+ //
700
+ // Please override this method and implement your own callback.
701
+ // */
702
+ // static VALUE def_dyn_message(VALUE self, VALUE data) {
703
+ // fprintf(stderr,
704
+ // "WARNING: websocket handler on_message override missing or "
705
+ // "bypassed.\n");
706
+ // return Qnil;
707
+ // }
708
+
709
+ /* *****************************************************************************
710
+ Upgrading
711
+ ***************************************************************************** */
712
+
713
+ void iodine_websocket_upgrade(http_request_s *request,
714
+ http_response_s *response, VALUE handler,
715
+ size_t max_msg, uint8_t ping) {
716
+ // make sure we have a valid handler, with the Websocket Protocol mixin.
717
+ if (handler == Qnil || handler == Qfalse || TYPE(handler) == T_FIXNUM ||
718
+ TYPE(handler) == T_STRING || TYPE(handler) == T_SYMBOL)
719
+ goto failed;
720
+ if (TYPE(handler) == T_CLASS || TYPE(handler) == T_MODULE) {
721
+ // include the Protocol module
722
+ rb_include_module(handler, IodineWebsocket);
723
+ rb_extend_object(handler, IodineWebsocket);
724
+ handler = RubyCaller.call(handler, iodine_new_func_id);
725
+ if (handler == Qnil || handler == Qfalse)
726
+ goto failed;
727
+ // check that we created a handler
728
+ } else {
729
+ // include the Protocol module in the object's class
730
+ VALUE p_class = rb_obj_class(handler);
731
+ rb_include_module(p_class, IodineWebsocket);
732
+ rb_extend_object(p_class, IodineWebsocket);
733
+ }
734
+ // add the handler to the registry
735
+ Registry.add(handler);
736
+ // set the UUID for the connection
737
+ set_uuid(handler, request);
738
+ // send upgrade response and set new protocol
739
+ websocket_upgrade(.request = request, .response = response,
740
+ .udata = (void *)handler, .on_close = ws_on_close,
741
+ .on_open = ws_on_open, .on_shutdown = ws_on_shutdown,
742
+ .on_ready = ws_on_ready, .on_message = ws_on_data,
743
+ .max_msg_size = max_msg, .timeout = ping);
744
+ return;
745
+ failed:
746
+ response->status = 400;
747
+ http_response_finish(response);
748
+ return;
749
+ }
750
+
751
+ /* *****************************************************************************
752
+ Initialization
753
+ ***************************************************************************** */
754
+
755
+ void Iodine_init_websocket(void) {
756
+ // get IDs and data that's used often
757
+ ws_var_id = rb_intern("iodine_ws_ptr"); // when upgrading
758
+ dup_func_id = rb_intern("dup"); // when upgrading
759
+
760
+ force_var_id = ID2SYM(rb_intern("fource"));
761
+ channel_var_id = ID2SYM(rb_intern("channel"));
762
+ pattern_var_id = ID2SYM(rb_intern("pattern"));
763
+ message_var_id = ID2SYM(rb_intern("message"));
764
+ engine_var_id = ID2SYM(rb_intern("engine"));
765
+ text_var_id = ID2SYM(rb_intern("text"));
766
+ binary_var_id = ID2SYM(rb_intern("binary"));
767
+
768
+ // the Ruby websockets protocol class.
769
+ IodineWebsocket = rb_define_module_under(Iodine, "Websocket");
770
+ if (IodineWebsocket == Qfalse)
771
+ fprintf(stderr, "WTF?!\n"), exit(-1);
772
+ // // callbacks and handlers
773
+ rb_define_method(IodineWebsocket, "on_open", empty_func, 0);
774
+ // rb_define_method(IodineWebsocket, "on_message", def_dyn_message, 1);
775
+ rb_define_method(IodineWebsocket, "on_shutdown", empty_func, 0);
776
+ rb_define_method(IodineWebsocket, "on_close", empty_func, 0);
777
+ rb_define_method(IodineWebsocket, "on_ready", empty_func, 0);
778
+ rb_define_method(IodineWebsocket, "write", iodine_ws_write, 1);
779
+ rb_define_method(IodineWebsocket, "close", iodine_ws_close, 0);
780
+
781
+ rb_define_method(IodineWebsocket, "conn_id", iodine_ws_uuid, 0);
782
+ rb_define_method(IodineWebsocket, "has_pending?", iodine_ws_has_pending, 0);
783
+ rb_define_method(IodineWebsocket, "defer", iodine_defer, -1);
784
+ // rb_define_method(IodineWebsocket, "each", iodine_ws_each, 0);
785
+
786
+ rb_define_method(IodineWebsocket, "subscribe", iodine_ws_subscribe, 1);
787
+ rb_define_method(IodineWebsocket, "unsubscribe", iodine_ws_unsubscribe, 1);
788
+ rb_define_method(IodineWebsocket, "subscribed?", iodine_ws_is_subscribed, 1);
789
+ rb_define_method(IodineWebsocket, "publish", iodine_ws_publish, 1);
790
+
791
+ rb_define_singleton_method(IodineWebsocket, "each", iodine_ws_class_each, 0);
792
+ rb_define_singleton_method(IodineWebsocket, "defer", iodine_class_defer, 1);
793
+ rb_define_singleton_method(IodineWebsocket, "count", iodine_ws_count, 0);
794
+ // rb_define_singleton_method(IodineWebsocket, "publish", iodine_ws_publish,
795
+ // 1);
796
+ }