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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +63 -100
- data/bin/raw-rbhttp +12 -7
- data/examples/config.ru +8 -7
- data/examples/echo.ru +8 -7
- data/examples/info.md +41 -35
- data/examples/pubsub_engine.ru +12 -12
- data/examples/redis.ru +10 -12
- data/examples/shootout.ru +19 -42
- data/exe/iodine +116 -1
- data/ext/iodine/defer.c +1 -1
- data/ext/iodine/facil.c +12 -8
- data/ext/iodine/facil.h +2 -2
- data/ext/iodine/iodine.c +177 -343
- data/ext/iodine/iodine.h +18 -72
- data/ext/iodine/iodine_caller.c +132 -0
- data/ext/iodine/iodine_caller.h +21 -0
- data/ext/iodine/iodine_connection.c +841 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +391 -0
- data/ext/iodine/iodine_defer.h +7 -0
- data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
- data/ext/iodine/iodine_helpers.c +51 -5
- data/ext/iodine/iodine_helpers.h +2 -3
- data/ext/iodine/iodine_http.c +284 -141
- data/ext/iodine/iodine_http.h +2 -2
- data/ext/iodine/iodine_json.c +13 -13
- data/ext/iodine/iodine_json.h +1 -1
- data/ext/iodine/iodine_pubsub.c +573 -823
- data/ext/iodine/iodine_pubsub.h +15 -27
- data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
- data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
- data/ext/iodine/iodine_store.c +136 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +385 -0
- data/ext/iodine/iodine_tcp.h +9 -0
- data/lib/iodine.rb +73 -171
- data/lib/iodine/connection.rb +34 -0
- data/lib/iodine/pubsub.rb +5 -18
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -182
- metadata +17 -18
- data/ext/iodine/iodine_protocol.c +0 -689
- data/ext/iodine/iodine_protocol.h +0 -13
- data/ext/iodine/iodine_websockets.c +0 -550
- data/ext/iodine/iodine_websockets.h +0 -17
- data/ext/iodine/rb-call.c +0 -156
- data/ext/iodine/rb-call.h +0 -70
- data/ext/iodine/rb-defer.c +0 -124
- data/ext/iodine/rb-registry.c +0 -150
- data/ext/iodine/rb-registry.h +0 -34
- data/lib/iodine/cli.rb +0 -89
- data/lib/iodine/monkeypatch.rb +0 -46
- data/lib/iodine/protocol.rb +0 -42
- data/lib/iodine/websocket.rb +0 -16
data/ext/iodine/iodine_pubsub.h
CHANGED
@@ -1,37 +1,25 @@
|
|
1
|
-
#ifndef
|
2
|
-
#define
|
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
|
-
|
13
|
-
|
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
|
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
|
26
|
-
pubsub_engine_s *p;
|
13
|
+
pubsub_engine_s do_not_touch;
|
27
14
|
VALUE handler;
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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 "
|
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 =
|
199
|
-
|
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
|
-
|
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
|
};
|
@@ -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
|
+
}
|