iodine 0.5.2 → 0.6.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +63 -100
  4. data/bin/raw-rbhttp +12 -7
  5. data/examples/config.ru +8 -7
  6. data/examples/echo.ru +8 -7
  7. data/examples/info.md +41 -35
  8. data/examples/pubsub_engine.ru +12 -12
  9. data/examples/redis.ru +10 -12
  10. data/examples/shootout.ru +19 -42
  11. data/exe/iodine +116 -1
  12. data/ext/iodine/defer.c +1 -1
  13. data/ext/iodine/facil.c +12 -8
  14. data/ext/iodine/facil.h +2 -2
  15. data/ext/iodine/iodine.c +177 -343
  16. data/ext/iodine/iodine.h +18 -72
  17. data/ext/iodine/iodine_caller.c +132 -0
  18. data/ext/iodine/iodine_caller.h +21 -0
  19. data/ext/iodine/iodine_connection.c +841 -0
  20. data/ext/iodine/iodine_connection.h +55 -0
  21. data/ext/iodine/iodine_defer.c +391 -0
  22. data/ext/iodine/iodine_defer.h +7 -0
  23. data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
  24. data/ext/iodine/iodine_helpers.c +51 -5
  25. data/ext/iodine/iodine_helpers.h +2 -3
  26. data/ext/iodine/iodine_http.c +284 -141
  27. data/ext/iodine/iodine_http.h +2 -2
  28. data/ext/iodine/iodine_json.c +13 -13
  29. data/ext/iodine/iodine_json.h +1 -1
  30. data/ext/iodine/iodine_pubsub.c +573 -823
  31. data/ext/iodine/iodine_pubsub.h +15 -27
  32. data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
  33. data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
  34. data/ext/iodine/iodine_store.c +136 -0
  35. data/ext/iodine/iodine_store.h +20 -0
  36. data/ext/iodine/iodine_tcp.c +385 -0
  37. data/ext/iodine/iodine_tcp.h +9 -0
  38. data/lib/iodine.rb +73 -171
  39. data/lib/iodine/connection.rb +34 -0
  40. data/lib/iodine/pubsub.rb +5 -18
  41. data/lib/iodine/rack_utils.rb +43 -0
  42. data/lib/iodine/version.rb +1 -1
  43. data/lib/rack/handler/iodine.rb +1 -182
  44. metadata +17 -18
  45. data/ext/iodine/iodine_protocol.c +0 -689
  46. data/ext/iodine/iodine_protocol.h +0 -13
  47. data/ext/iodine/iodine_websockets.c +0 -550
  48. data/ext/iodine/iodine_websockets.h +0 -17
  49. data/ext/iodine/rb-call.c +0 -156
  50. data/ext/iodine/rb-call.h +0 -70
  51. data/ext/iodine/rb-defer.c +0 -124
  52. data/ext/iodine/rb-registry.c +0 -150
  53. data/ext/iodine/rb-registry.h +0 -34
  54. data/lib/iodine/cli.rb +0 -89
  55. data/lib/iodine/monkeypatch.rb +0 -46
  56. data/lib/iodine/protocol.rb +0 -42
  57. data/lib/iodine/websocket.rb +0 -16
@@ -1,37 +1,25 @@
1
- #ifndef H_IODINE_ENGINE_H
2
- #define H_IODINE_ENGINE_H
3
- /*
4
- Copyright: Boaz segev, 2016-2017
5
- License: MIT
1
+ #ifndef H_IODINE_PUBSUB_H
2
+ #define H_IODINE_PUBSUB_H
6
3
 
7
- Feel free to copy, use and enjoy according to the license provided.
8
- */
9
4
  #include "iodine.h"
10
5
  #include "pubsub.h"
11
6
 
12
- typedef enum {
13
- IODINE_PUBSUB_GLOBAL,
14
- IODINE_PUBSUB_WEBSOCKET,
15
- IODINE_PUBSUB_SSE
16
- } iodine_pubsub_type_e;
7
+ /** Initializes the PubSub::Engine Ruby class. */
8
+ void iodine_pubsub_init(void);
17
9
 
18
- extern VALUE IodineEngine;
19
- extern ID iodine_engine_pubid;
20
- VALUE iodine_publish(int argc, VALUE *argv, VALUE self);
21
- VALUE iodine_subscribe(int argc, VALUE *argv, void *owner,
22
- iodine_pubsub_type_e type);
10
+ extern const rb_data_type_t iodine_pubsub_data_type;
23
11
 
24
12
  typedef struct {
25
- pubsub_engine_s engine;
26
- pubsub_engine_s *p;
13
+ pubsub_engine_s do_not_touch;
27
14
  VALUE handler;
28
- void (*dealloc)(pubsub_engine_s *);
29
- } iodine_engine_s;
30
-
31
- void Iodine_init_pubsub(void);
32
-
33
- typedef struct pubsub_engine_s pubsub_engine_s;
34
-
35
- pubsub_engine_s *iodine_engine_ruby2facil(VALUE engine);
15
+ pubsub_engine_s *engine;
16
+ void (*dealloc)(pubsub_engine_s *engine);
17
+ } iodine_pubsub_s;
18
+
19
+ static inline iodine_pubsub_s *iodine_pubsub_CData(VALUE obj) {
20
+ iodine_pubsub_s *c = NULL;
21
+ TypedData_Get_Struct(obj, iodine_pubsub_s, &iodine_pubsub_data_type, c);
22
+ return c;
23
+ }
36
24
 
37
25
  #endif
@@ -4,7 +4,7 @@ License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
- #include "rb-rack-io.h"
7
+ #include "iodine_rack_io.h"
8
8
 
9
9
  #include "iodine.h"
10
10
 
@@ -15,7 +15,6 @@ Feel free to copy, use and enjoy according to the license provided.
15
15
  #ifndef _GNU_SOURCE
16
16
  #define _GNU_SOURCE
17
17
  #endif
18
- #include "rb-call.h"
19
18
 
20
19
  /* IodineRackIO manages a minimal interface to act as an IO wrapper according to
21
20
  these Rack specifications:
@@ -56,8 +55,14 @@ static VALUE rRackIO;
56
55
  static ID env_id;
57
56
  static ID io_id;
58
57
 
58
+ static VALUE R_INPUT; /* rack.input */
59
+ static VALUE hijack_func_sym;
59
60
  static VALUE TCPSOCKET_CLASS;
60
61
  static ID for_fd_id;
62
+ static ID iodine_fd_var_id;
63
+ static ID iodine_new_func_id;
64
+ static rb_encoding *IodineUTF8Encoding;
65
+ static rb_encoding *IodineBinaryEncoding;
61
66
 
62
67
  #define set_handle(object, handle) \
63
68
  rb_ivar_set((object), iodine_fd_var_id, ULL2NUM((uintptr_t)handle))
@@ -152,8 +157,8 @@ static VALUE rio_read(int argc, VALUE *argv, VALUE self) {
152
157
 
153
158
  // Does nothing - this is controlled by the server.
154
159
  static VALUE rio_close(VALUE self) {
155
- FIOBJ io = get_data(self);
156
- fiobj_free(io);
160
+ // FIOBJ io = get_data(self);
161
+ // fiobj_free(io); // we don't call fiobj_dup, do we?
157
162
  rb_ivar_set(self, io_id, INT2NUM(0));
158
163
  (void)self;
159
164
  return Qnil;
@@ -195,8 +200,8 @@ static VALUE rio_get_io(int argc, VALUE *argv, VALUE self) {
195
200
  intptr_t uuid = http_hijack(h, NULL);
196
201
  VALUE fd = INT2FIX(sock_uuid2fd(uuid));
197
202
  // VALUE new_io = how the fuck do we create a new IO from the fd?
198
- VALUE new_io = RubyCaller.call2(TCPSOCKET_CLASS, for_fd_id, 1,
199
- &fd); // TCPSocket.for_fd(fd) ... cool...
203
+ VALUE new_io = IodineCaller.call2(TCPSOCKET_CLASS, for_fd_id, 1,
204
+ &fd); // TCPSocket.for_fd(fd) ... cool...
200
205
  rb_hash_aset(env, IODINE_R_HIJACK_IO, new_io);
201
206
  if (argc)
202
207
  rb_hash_aset(env, IODINE_R_HIJACK_CB, *argv);
@@ -213,16 +218,33 @@ static VALUE new_rack_io(http_s *h, VALUE env) {
213
218
  rb_ivar_set(rack_io, io_id, ULL2NUM(h->body));
214
219
  set_handle(rack_io, h);
215
220
  rb_ivar_set(rack_io, env_id, env);
221
+ rb_hash_aset(env, R_INPUT, rack_io);
222
+ rb_hash_aset(env, IODINE_R_HIJACK, rb_obj_method(rack_io, hijack_func_sym));
216
223
  return rack_io;
217
224
  }
218
225
 
226
+ static void close_rack_io(VALUE rack_io) {
227
+ // rio_close(rack_io);
228
+ rb_ivar_set(rack_io, io_id, INT2NUM(0));
229
+ set_handle(rack_io, NULL); /* this disables hijacking. */
230
+ }
231
+
219
232
  // initialize library
220
233
  static void init_rack_io(void) {
221
- rRackIO = rb_define_class_under(IodineBase, "RackIO", rb_cObject);
234
+ IodineUTF8Encoding = rb_enc_find("UTF-8");
235
+ IodineBinaryEncoding = rb_enc_find("binary");
236
+ rRackIO = rb_define_class_under(IodineBaseModule, "RackIO", rb_cObject);
222
237
 
223
238
  io_id = rb_intern("rack_io");
224
239
  env_id = rb_intern("env");
225
240
  for_fd_id = rb_intern("for_fd");
241
+ iodine_fd_var_id = rb_intern("fd");
242
+ iodine_new_func_id = rb_intern("new");
243
+ hijack_func_sym = ID2SYM(rb_intern("_hijack"));
244
+
245
+ R_INPUT = rb_enc_str_new("rack.input", 10, rb_ascii8bit_encoding());
246
+ rb_obj_freeze(R_INPUT);
247
+ IodineStore.add(R_INPUT);
226
248
 
227
249
  TCPSOCKET_CLASS = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
228
250
  // IO methods
@@ -238,5 +260,5 @@ static void init_rack_io(void) {
238
260
  ////////////////////////////////////////////////////////////////////////////
239
261
  // the API interface
240
262
  struct IodineRackIO IodineRackIO = {
241
- .create = new_rack_io, .init = init_rack_io,
263
+ .create = new_rack_io, .close = close_rack_io, .init = init_rack_io,
242
264
  };
@@ -13,6 +13,7 @@ Feel free to copy, use and enjoy according to the license provided.
13
13
 
14
14
  extern struct IodineRackIO {
15
15
  VALUE (*create)(http_s *h, VALUE env);
16
+ void (*close)(VALUE rack_io);
16
17
  void (*init)(void);
17
18
  } IodineRackIO;
18
19
 
@@ -0,0 +1,136 @@
1
+ #include "iodine.h"
2
+
3
+ #include "fio_hashmap.h"
4
+ #include "iodine_store.h"
5
+ #include "spnlock.inc"
6
+
7
+ spn_lock_i lock = SPN_LOCK_INIT;
8
+ fio_hash_s storage = FIO_HASH_INIT;
9
+
10
+ #ifndef IODINE_DEBUG
11
+ #define IODINE_DEBUG 0
12
+ #endif
13
+
14
+ /* *****************************************************************************
15
+ API
16
+ ***************************************************************************** */
17
+
18
+ /** Adds an object to the storage (or increases it's reference count). */
19
+ static VALUE storage_add(VALUE obj) {
20
+ if (obj == Qnil || obj == Qtrue || obj == Qfalse)
21
+ return obj;
22
+ spn_lock(&lock);
23
+ uintptr_t val = (uintptr_t)fio_hash_insert(&storage, obj, (void *)1);
24
+ if (val) {
25
+ fio_hash_insert(&storage, obj, (void *)(val + 1));
26
+ }
27
+ spn_unlock(&lock);
28
+ return obj;
29
+ }
30
+ /** Removes an object from the storage (or decreases it's reference count). */
31
+ static VALUE storage_remove(VALUE obj) {
32
+ if (obj == Qnil || obj == Qtrue || obj == Qfalse || storage.map == NULL)
33
+ return obj;
34
+ spn_lock(&lock);
35
+ uintptr_t val = (uintptr_t)fio_hash_insert(&storage, obj, NULL);
36
+ if (val > 1) {
37
+ fio_hash_insert(&storage, obj, (void *)(val - 1));
38
+ }
39
+ if ((storage.count << 1) <= storage.pos &&
40
+ (storage.pos << 1) > storage.capa) {
41
+ fio_hash_compact(&storage);
42
+ }
43
+ spn_unlock(&lock);
44
+ return obj;
45
+ }
46
+ /** Should be called after forking to reset locks */
47
+ static void storage_after_fork(void) { lock = SPN_LOCK_INIT; }
48
+
49
+ /** Prints debugging information to the console. */
50
+ static void storage_print(void) {
51
+ fprintf(stderr, "Ruby <=> C Memory storage stats (pid: %d):\n", getpid());
52
+ spn_lock(&lock);
53
+ uintptr_t index = 0;
54
+ FIO_HASH_FOR_LOOP(&storage, pos) {
55
+ if (pos->obj) {
56
+ fprintf(stderr, "[%" PRIuPTR "] => %" PRIuPTR " X obj %p type %d\n",
57
+ index++, (uintptr_t)pos->obj, (void *)pos->key, TYPE(pos->key));
58
+ }
59
+ }
60
+ fprintf(stderr, "Total of %" PRIuPTR " objects protected form GC\n", index);
61
+ fprintf(stderr,
62
+ "Storage uses %" PRIuPTR " Hash bins for %" PRIuPTR " objects\n",
63
+ storage.capa, storage.count);
64
+ spn_unlock(&lock);
65
+ }
66
+
67
+ static VALUE storage_print_rb(VALUE self) {
68
+ storage_print();
69
+ return Qnil;
70
+ (void)self;
71
+ }
72
+ /* *****************************************************************************
73
+ GC protection
74
+ ***************************************************************************** */
75
+
76
+ /* a callback for the GC (marking active objects) */
77
+ static void storage_mark(void *ignore) {
78
+ (void)ignore;
79
+ #if IODINE_DEBUG
80
+ storage_print();
81
+ #endif
82
+ spn_lock(&lock);
83
+ // fio_hash_compact(&storage);
84
+ FIO_HASH_FOR_LOOP(&storage, pos) {
85
+ if (pos->obj) {
86
+ rb_gc_mark((VALUE)pos->key);
87
+ }
88
+ }
89
+ spn_unlock(&lock);
90
+ }
91
+
92
+ /* clear the registry (end of lifetime) */
93
+ static void storage_clear(void *ignore) {
94
+ (void)ignore;
95
+ #if IODINE_DEBUG == 1
96
+ fprintf(stderr, "* INFO: Ruby<=>C Storage cleared.\n");
97
+ #endif
98
+ spn_lock(&lock);
99
+ fio_hash_free(&storage);
100
+ storage = (fio_hash_s)FIO_HASH_INIT;
101
+ spn_unlock(&lock);
102
+ }
103
+
104
+ /*
105
+ the data-type used to identify the registry
106
+ this sets the callbacks.
107
+ */
108
+ static struct rb_data_type_struct storage_type_struct = {
109
+ .wrap_struct_name = "RubyReferencesIn_C_Land",
110
+ .function.dfree = (void (*)(void *))storage_clear,
111
+ .function.dmark = (void (*)(void *))storage_mark,
112
+ };
113
+
114
+ /* *****************************************************************************
115
+ Initialization
116
+ ***************************************************************************** */
117
+
118
+ struct IodineStorage_s IodineStore = {
119
+ .add = storage_add,
120
+ .remove = storage_remove,
121
+ .after_fork = storage_after_fork,
122
+ .print = storage_print,
123
+ };
124
+
125
+ /** Initializes the storage unit for first use. */
126
+ void iodine_storage_init(void) {
127
+ fio_hash_new2(&storage, 512);
128
+ VALUE tmp =
129
+ rb_define_class_under(rb_cObject, "IodineObjectStorage", rb_cData);
130
+ VALUE storage_obj =
131
+ TypedData_Wrap_Struct(tmp, &storage_type_struct, &storage);
132
+ // rb_global_variable(&storage_obj);
133
+ rb_ivar_set(IodineModule, rb_intern2("storage", 7), storage_obj);
134
+ rb_define_module_function(IodineBaseModule, "db_print_protected_objects",
135
+ storage_print_rb, 0);
136
+ }
@@ -0,0 +1,20 @@
1
+ #ifndef H_IODINE_STORAGE_H
2
+ #define H_IODINE_STORAGE_H
3
+
4
+ #include "ruby.h"
5
+
6
+ extern struct IodineStorage_s {
7
+ /** Adds an object to the storage (or increases it's reference count). */
8
+ VALUE (*add)(VALUE);
9
+ /** Removes an object from the storage (or decreases it's reference count). */
10
+ VALUE (*remove)(VALUE);
11
+ /** Should be called after forking to reset locks */
12
+ void (*after_fork)(void);
13
+ /** Prints debugging information to the console. */
14
+ void (*print)(void);
15
+ } IodineStore;
16
+
17
+ /** Initializes the storage unit for first use. */
18
+ void iodine_storage_init(void);
19
+
20
+ #endif
@@ -0,0 +1,385 @@
1
+ #include "iodine.h"
2
+ #include <ruby/encoding.h>
3
+ #include <ruby/io.h>
4
+
5
+ #include "evio.h"
6
+ #include "facil.h"
7
+
8
+ /* *****************************************************************************
9
+ Static stuff
10
+ ***************************************************************************** */
11
+ static ID call_id;
12
+ static ID on_closed_id;
13
+ static rb_encoding *IodineBinaryEncoding;
14
+
15
+ static VALUE port_id;
16
+ static VALUE address_id;
17
+ static VALUE handler_id;
18
+ static VALUE timeout_id;
19
+
20
+ /* *****************************************************************************
21
+ Raw TCP/IP Protocol
22
+ ***************************************************************************** */
23
+
24
+ #define IODINE_MAX_READ 8192
25
+
26
+ typedef struct {
27
+ protocol_s p;
28
+ VALUE io;
29
+ } iodine_protocol_s;
30
+
31
+ typedef struct {
32
+ VALUE io;
33
+ ssize_t len;
34
+ char buffer[IODINE_MAX_READ];
35
+ } iodine_buffer_s;
36
+
37
+ /**
38
+ * A string to identify the protocol's service.
39
+ */
40
+ static const char *iodine_tcp_service = "iodine TCP/IP raw connection";
41
+
42
+ /**
43
+ * Converts an iodine_buffer_s pointer to a Ruby string.
44
+ */
45
+ static void *iodine_tcp_on_data_in_GIL(void *b_) {
46
+ iodine_buffer_s *b = b_;
47
+ if (!b) {
48
+ fprintf(stderr, "FATAL ERROR: (iodine->tcp/ip->on_data->GIL) WTF?!\n");
49
+ }
50
+ VALUE data = IodineStore.add(rb_str_new(b->buffer, b->len));
51
+ rb_enc_associate(data, IodineBinaryEncoding);
52
+ iodine_connection_fire_event(b->io, IODINE_CONNECTION_ON_MESSAGE, data);
53
+ IodineStore.remove(data);
54
+ return NULL;
55
+ // return (void *)IodineStore.add(rb_usascii_str_new((const char *)b->buffer,
56
+ // b->len));
57
+ }
58
+
59
+ /** Called when a data is available, but will not run concurrently */
60
+ static void iodine_tcp_on_data(intptr_t uuid, protocol_s *protocol) {
61
+ iodine_buffer_s buffer;
62
+ buffer.len = sock_read(uuid, buffer.buffer, IODINE_MAX_READ);
63
+ if (buffer.len <= 0) {
64
+ return;
65
+ }
66
+ buffer.io = ((iodine_protocol_s *)protocol)->io;
67
+ IodineCaller.enterGVL(iodine_tcp_on_data_in_GIL, &buffer);
68
+ if (buffer.len == IODINE_MAX_READ) {
69
+ facil_force_event(uuid, FIO_EVENT_ON_DATA);
70
+ }
71
+ }
72
+
73
+ /** called when the socket is ready to be written to. */
74
+ static void iodine_tcp_on_ready(intptr_t uuid, protocol_s *protocol) {
75
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
76
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_DRAINED, Qnil);
77
+ (void)uuid;
78
+ }
79
+
80
+ /**
81
+ * Called when the server is shutting down, immediately before closing the
82
+ * connection.
83
+ */
84
+ static void iodine_tcp_on_shutdown(intptr_t uuid, protocol_s *protocol) {
85
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
86
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_SHUTDOWN, Qnil);
87
+ (void)uuid;
88
+ }
89
+
90
+ /** Called when the connection was closed, but will not run concurrently */
91
+
92
+ static void iodine_tcp_on_close(intptr_t uuid, protocol_s *protocol) {
93
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
94
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_CLOSE, Qnil);
95
+ free(p);
96
+ (void)uuid;
97
+ }
98
+
99
+ /** called when a connection's timeout was reached */
100
+ static void iodine_tcp_ping(intptr_t uuid, protocol_s *protocol) {
101
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
102
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_PING, Qnil);
103
+ (void)uuid;
104
+ }
105
+
106
+ /** called when a connection opens */
107
+ static void iodine_tcp_on_open(intptr_t uuid, void *udata) {
108
+ VALUE handler = IodineCaller.call((VALUE)udata, call_id);
109
+ IodineStore.add(handler);
110
+ iodine_tcp_attch_uuid(uuid, handler);
111
+ IodineStore.remove(handler);
112
+ }
113
+
114
+ /** called when the listening socket is destroyed */
115
+ static void iodine_tcp_on_finish(intptr_t uuid, void *udata) {
116
+ IodineStore.remove((VALUE)udata);
117
+ (void)uuid;
118
+ }
119
+
120
+ /**
121
+ * The `on_connect` callback should return a pointer to a protocol object
122
+ * that will handle any connection related events.
123
+ *
124
+ * Should either call `facil_attach` or close the connection.
125
+ */
126
+ static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
127
+ VALUE handler = (VALUE)udata;
128
+ iodine_tcp_attch_uuid(uuid, handler);
129
+ IodineStore.remove(handler);
130
+ }
131
+
132
+ /**
133
+ * The `on_fail` is called when a socket fails to connect. The old sock UUID
134
+ * is passed along.
135
+ */
136
+ static void iodine_tcp_on_fail(intptr_t uuid, void *udata) {
137
+ VALUE handler = (VALUE)udata;
138
+ if (rb_respond_to(handler, on_closed_id)) {
139
+ VALUE client = Qnil;
140
+ IodineCaller.call2(handler, on_closed_id, 1, &client);
141
+ }
142
+ IodineStore.remove(handler);
143
+ (void)uuid;
144
+ }
145
+
146
+ /* *****************************************************************************
147
+ The Ruby API implementation
148
+ ***************************************************************************** */
149
+
150
+ // clang-format off
151
+ /**
152
+ The {listen} method instructs iodine to listen to incoming connections using either TCP/IP or Unix sockets.
153
+
154
+ The method accepts a single Hash argument with the following optional keys:
155
+
156
+ :port :: The port to listen to, deafults to 0 (using a Unix socket)
157
+ :address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant).
158
+ :handler :: An object that answers the `call` method (i.e., a Proc).
159
+
160
+ The method also accepts an optional block.
161
+
162
+ Either a block or the :handler key MUST be present.
163
+
164
+ The handler Proc (or object) should return a connection callback object that supports the following callbacks (see also {Iodine::Connection}):
165
+
166
+ on_open(client) :: called after a connection was established
167
+ on_message(client, data) :: called when incoming data is available. Data may be fragmented.
168
+ on_drained(client) :: called when all the pending `client.write` events have been processed (see {Iodine::Connection#pending}).
169
+ ping(client) :: called whenever a timeout has occured (see {Iodine::Connection#timeout=}).
170
+ on_shutdown(client) :: called if the server is shutting down. This is called before the connection is closed.
171
+ on_close(client) :: called when the connection with the client was closed.
172
+
173
+ The `client` argument is an {Iodine::Connection} instance that represents the connection / the client.
174
+
175
+ Here's a telnet based chat-room example:
176
+
177
+ require 'iodine'
178
+ # define the protocol for our service
179
+ module ChatHandler
180
+ def self.on_open(client)
181
+ # Set a connection timeout
182
+ client.timeout = 10
183
+ # subscribe to the chat channel.
184
+ client.subscribe :chat
185
+ # Write a welcome message
186
+ client.publish :chat, "new member entered the chat\r\n"
187
+ end
188
+ # this is called for incoming data - note data might be fragmented.
189
+ def self.on_message(client, data)
190
+ # publish the data we received
191
+ client.publish :chat, data
192
+ # close the connection when the time comes
193
+ client.close if data =~ /^bye[\n\r]/
194
+ end
195
+ # called whenever timeout occurs.
196
+ def self.ping(client)
197
+ client.write "System: quite, isn't it...?\r\n"
198
+ end
199
+ # called if the connection is still open and the server is shutting down.
200
+ def self.on_shutdown(client)
201
+ # write the data we received
202
+ client.write "Chat server going away. Try again later.\r\n"
203
+ end
204
+ # returns the callback object (self).
205
+ def self.call
206
+ self
207
+ end
208
+ end
209
+ # we can bothe the `handler` keuword or a block, anything that answers #call.
210
+ Iodine.listen(port: "3000", handler: ChatHandler)
211
+ # start the service
212
+ Iodine.threads = 1
213
+ Iodine.start
214
+
215
+
216
+
217
+ Returns the handler object used.
218
+ */
219
+ static VALUE iodine_tcp_listen(VALUE self, VALUE args) {
220
+ // clang-format on
221
+ Check_Type(args, T_HASH);
222
+ VALUE rb_port = rb_hash_aref(args, port_id);
223
+ VALUE rb_address = rb_hash_aref(args, address_id);
224
+ VALUE rb_handler = rb_hash_aref(args, handler_id);
225
+ if (rb_handler == Qnil || rb_handler == Qfalse || rb_handler == Qtrue) {
226
+ rb_need_block();
227
+ rb_handler = rb_block_proc();
228
+ }
229
+ IodineStore.add(rb_handler);
230
+ if (rb_address != Qnil) {
231
+ Check_Type(rb_address, T_STRING);
232
+ }
233
+ if (rb_port != Qnil) {
234
+ Check_Type(rb_port, T_STRING);
235
+ }
236
+ if (facil_listen(.port = (rb_port == Qnil ? NULL : StringValueCStr(rb_port)),
237
+ .address =
238
+ (rb_address == Qnil ? NULL
239
+ : StringValueCStr(rb_address)),
240
+ .on_open = iodine_tcp_on_open,
241
+ .on_finish = iodine_tcp_on_finish,
242
+ .udata = (void *)rb_handler) == -1) {
243
+ IodineStore.remove(rb_handler);
244
+ rb_raise(rb_eRuntimeError,
245
+ "failed to listen to requested address, unknown error.");
246
+ }
247
+ return rb_handler;
248
+ (void)self;
249
+ }
250
+
251
+ // clang-format off
252
+ /**
253
+ The {connect} method instructs iodine to connect to a server using either TCP/IP or Unix sockets.
254
+
255
+ The method accepts a single Hash argument with the following optional keys:
256
+
257
+ :port :: The port to listen to, deafults to 0 (using a Unix socket)
258
+ :address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant).
259
+ :handler :: A connection callback object that supports the following same callbacks listen in the {listen} method's documentation.
260
+ :timeout :: An integer timeout for connection establishment (doen't effect the new connection's timeout. Should be in the rand of 0..255.
261
+
262
+ The method also accepts an optional block.
263
+
264
+ Either a block or the :handler key MUST be present.
265
+
266
+ If the connection fails, only the `on_close` callback will be called (with a `nil` client).
267
+
268
+ Returns the handler object used.
269
+ */
270
+ static VALUE iodine_tcp_connect(VALUE self, VALUE args) {
271
+ // clang-format on
272
+ Check_Type(args, T_HASH);
273
+ VALUE rb_port = rb_hash_aref(args, port_id);
274
+ VALUE rb_address = rb_hash_aref(args, address_id);
275
+ VALUE rb_handler = rb_hash_aref(args, handler_id);
276
+ VALUE rb_timeout = rb_hash_aref(args, timeout_id);
277
+ uint8_t timeout = 0;
278
+ if (rb_handler == Qnil || rb_handler == Qfalse || rb_handler == Qtrue) {
279
+ rb_raise(rb_eArgError, "A callback object (:handler) must be provided.");
280
+ }
281
+ IodineStore.add(rb_handler);
282
+ if (rb_address != Qnil) {
283
+ Check_Type(rb_address, T_STRING);
284
+ }
285
+ if (rb_port != Qnil) {
286
+ Check_Type(rb_port, T_STRING);
287
+ }
288
+ if (rb_timeout != Qnil) {
289
+ Check_Type(rb_timeout, T_FIXNUM);
290
+ timeout = NUM2USHORT(rb_timeout);
291
+ }
292
+ facil_connect(.port = (rb_port == Qnil ? NULL : StringValueCStr(rb_port)),
293
+ .address =
294
+ (rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
295
+ .on_connect = iodine_tcp_on_connect,
296
+ .on_fail = iodine_tcp_on_fail, .timeout = timeout,
297
+ .udata = (void *)rb_handler);
298
+ return rb_handler;
299
+ (void)self;
300
+ }
301
+
302
+ // clang-format off
303
+ /**
304
+ The {attach_fd} method instructs iodine to attach a socket to the server using it's numerical file descriptor.
305
+
306
+ This is faster than attaching a Ruby IO object since it allows iodine to directly call the system's read/write methods. However, this doesn't support TLS/SSL connections.
307
+
308
+ This method requires two objects, a file descriptor (`fd`) and a callback object.
309
+
310
+ See {listen} for details about the callback object.
311
+
312
+ Returns the callback object (handler) used.
313
+ */
314
+ static VALUE iodine_tcp_attach_fd(VALUE self, VALUE fd, VALUE handler) {
315
+ // clang-format on
316
+ Check_Type(fd, T_FIXNUM);
317
+ if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
318
+ rb_raise(rb_eArgError, "A callback object must be provided.");
319
+ }
320
+ IodineStore.add(handler);
321
+ int other = dup(NUM2INT(fd));
322
+ if (other == -1) {
323
+ rb_raise(rb_eIOError, "invalid fd.");
324
+ }
325
+ intptr_t uuid = sock_open(other);
326
+ iodine_tcp_attch_uuid(uuid, handler);
327
+ IodineStore.remove(handler);
328
+ return handler;
329
+ (void)self;
330
+ }
331
+
332
+ /* *****************************************************************************
333
+ Add the Ruby API methods to the Iodine object
334
+ ***************************************************************************** */
335
+
336
+ void iodine_init_tcp_connections(void) {
337
+ call_id = rb_intern2("call", 4);
338
+ port_id = IodineStore.add(rb_id2sym(rb_intern("port")));
339
+ address_id = IodineStore.add(rb_id2sym(rb_intern("address")));
340
+ handler_id = IodineStore.add(rb_id2sym(rb_intern("handler")));
341
+ timeout_id = IodineStore.add(rb_id2sym(rb_intern("timout")));
342
+ on_closed_id = rb_intern("on_closed");
343
+
344
+ IodineBinaryEncoding = rb_enc_find("binary");
345
+
346
+ rb_define_module_function(IodineModule, "listen", iodine_tcp_listen, 1);
347
+ rb_define_module_function(IodineModule, "connect", iodine_tcp_connect, 1);
348
+ rb_define_module_function(IodineModule, "attach_fd", iodine_tcp_attach_fd, 2);
349
+ }
350
+
351
+ /* *****************************************************************************
352
+ Allow uuid attachment
353
+ ***************************************************************************** */
354
+
355
+ /** assigns a protocol and IO object to a handler */
356
+ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
357
+ if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
358
+ sock_close(uuid);
359
+ return;
360
+ }
361
+ /* temporary, in case `iodine_connection_new` invokes the GC */
362
+ iodine_protocol_s *p = malloc(sizeof(*p));
363
+ if (!p) {
364
+ perror("FATAL ERROR: No Memory!");
365
+ exit(errno);
366
+ }
367
+ *p = (iodine_protocol_s){
368
+ .p =
369
+ {
370
+ .service = iodine_tcp_service,
371
+ .on_data = iodine_tcp_on_data,
372
+ .on_ready = NULL /* set only after the on_open callback */,
373
+ .on_shutdown = iodine_tcp_on_shutdown,
374
+ .on_close = iodine_tcp_on_close,
375
+ .ping = iodine_tcp_ping,
376
+ },
377
+ .io = iodine_connection_new(.type = IODINE_CONNECTION_RAW, .uuid = uuid,
378
+ .arg = p, .handler = handler),
379
+ };
380
+ /* clear away (remember the connection object manages these concerns) */
381
+ facil_attach(uuid, &p->p);
382
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil);
383
+ p->p.on_ready = iodine_tcp_on_ready;
384
+ evio_add_write(sock_uuid2fd(uuid), (void *)uuid);
385
+ }