iodine 0.7.16 → 0.7.17
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/.travis.yml +5 -4
- data/.yardopts +8 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +1 -1
- data/LIMITS.md +6 -0
- data/README.md +93 -13
- data/{SPEC-Websocket-Draft.md → SPEC-WebSocket-Draft.md} +0 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +3 -3
- data/ext/iodine/extconf.rb +21 -0
- data/ext/iodine/fio.c +659 -69
- data/ext/iodine/fio.h +350 -95
- data/ext/iodine/fio_cli.c +4 -3
- data/ext/iodine/fio_json_parser.h +1 -1
- data/ext/iodine/fio_siphash.c +13 -11
- data/ext/iodine/fio_siphash.h +6 -3
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +634 -0
- data/ext/iodine/fio_tls_openssl.c +1011 -0
- data/ext/iodine/fio_tmpfile.h +1 -1
- data/ext/iodine/fiobj.h +1 -1
- data/ext/iodine/fiobj_ary.c +1 -1
- data/ext/iodine/fiobj_ary.h +1 -1
- data/ext/iodine/fiobj_data.c +1 -1
- data/ext/iodine/fiobj_data.h +1 -1
- data/ext/iodine/fiobj_hash.c +1 -1
- data/ext/iodine/fiobj_hash.h +1 -1
- data/ext/iodine/fiobj_json.c +18 -16
- data/ext/iodine/fiobj_json.h +1 -1
- data/ext/iodine/fiobj_mustache.c +4 -0
- data/ext/iodine/fiobj_mustache.h +4 -0
- data/ext/iodine/fiobj_numbers.c +1 -1
- data/ext/iodine/fiobj_numbers.h +1 -1
- data/ext/iodine/fiobj_str.c +3 -3
- data/ext/iodine/fiobj_str.h +1 -1
- data/ext/iodine/fiobject.c +1 -1
- data/ext/iodine/fiobject.h +8 -2
- data/ext/iodine/http.c +128 -337
- data/ext/iodine/http.h +11 -18
- data/ext/iodine/http1.c +6 -6
- data/ext/iodine/http1.h +1 -1
- data/ext/iodine/http1_parser.c +1 -1
- data/ext/iodine/http1_parser.h +1 -1
- data/ext/iodine/http_internal.c +10 -8
- data/ext/iodine/http_internal.h +13 -3
- data/ext/iodine/http_mime_parser.h +1 -1
- data/ext/iodine/iodine.c +806 -22
- data/ext/iodine/iodine.h +33 -0
- data/ext/iodine/iodine_connection.c +23 -18
- data/ext/iodine/iodine_http.c +239 -225
- data/ext/iodine/iodine_http.h +4 -1
- data/ext/iodine/iodine_mustache.c +59 -54
- data/ext/iodine/iodine_pubsub.c +1 -1
- data/ext/iodine/iodine_tcp.c +34 -100
- data/ext/iodine/iodine_tcp.h +4 -0
- data/ext/iodine/iodine_tls.c +267 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1 -1
- data/ext/iodine/redis_engine.c +14 -6
- data/ext/iodine/redis_engine.h +1 -1
- data/ext/iodine/resp_parser.h +1 -1
- data/ext/iodine/websocket_parser.h +1 -1
- data/ext/iodine/websockets.c +1 -1
- data/ext/iodine/websockets.h +1 -1
- data/iodine.gemspec +2 -1
- data/lib/iodine.rb +19 -5
- data/lib/iodine/connection.rb +13 -0
- data/lib/iodine/mustache.rb +7 -24
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -1
- metadata +15 -5
data/ext/iodine/iodine_http.h
CHANGED
@@ -12,7 +12,10 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
12
12
|
extern VALUE IODINE_R_HIJACK;
|
13
13
|
extern VALUE IODINE_R_HIJACK_IO;
|
14
14
|
extern VALUE IODINE_R_HIJACK_CB;
|
15
|
-
extern VALUE iodine_default_args;
|
16
15
|
void iodine_init_http(void);
|
17
16
|
|
17
|
+
intptr_t iodine_http_listen(iodine_connection_args_s args);
|
18
|
+
// intptr_t iodine_http_connect(iodine_connection_args_s args); // not yet...
|
19
|
+
intptr_t iodine_ws_connect(iodine_connection_args_s args);
|
20
|
+
|
18
21
|
#endif
|
@@ -9,9 +9,9 @@
|
|
9
9
|
|
10
10
|
static ID call_func_id;
|
11
11
|
static ID to_s_func_id;
|
12
|
-
static
|
13
|
-
static
|
14
|
-
static
|
12
|
+
static VALUE filename_id;
|
13
|
+
static VALUE data_id;
|
14
|
+
static VALUE template_id;
|
15
15
|
/* *****************************************************************************
|
16
16
|
C <=> Ruby Data allocation
|
17
17
|
***************************************************************************** */
|
@@ -255,20 +255,63 @@ Loading the template
|
|
255
255
|
***************************************************************************** */
|
256
256
|
|
257
257
|
/**
|
258
|
-
Loads
|
258
|
+
Loads the mustache template found in `:filename`. If `:template` is provided it
|
259
|
+
will be used instead of reading the file's content.
|
260
|
+
|
261
|
+
Iodine::Mustache.new(filename, template = nil)
|
262
|
+
|
263
|
+
When template data is provided, filename (if any) will only be used for partial
|
264
|
+
template path resolution and the template data will be used for the template's
|
265
|
+
content. This allows, for example, for front matter to be extracted before
|
266
|
+
parsing the template.
|
259
267
|
|
260
268
|
Once a template was loaded, it could be rendered using {render}.
|
269
|
+
|
270
|
+
Accepts named arguments as well:
|
271
|
+
|
272
|
+
Iodine::Mustache.new(filename: "foo.mustache", template: "{{ bar }}")
|
273
|
+
|
261
274
|
*/
|
262
|
-
static VALUE iodine_mustache_new(VALUE
|
275
|
+
static VALUE iodine_mustache_new(int argc, VALUE *argv, VALUE self) {
|
276
|
+
VALUE filename = Qnil, template = Qnil;
|
277
|
+
if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) {
|
278
|
+
/* named arguments */
|
279
|
+
filename = rb_hash_aref(argv[0], filename_id);
|
280
|
+
template = rb_hash_aref(argv[0], template_id);
|
281
|
+
} else {
|
282
|
+
/* regular arguments */
|
283
|
+
if (argc == 0 || argc > 2)
|
284
|
+
rb_raise(rb_eArgError, "expecting 1..2 arguments or named arguments.");
|
285
|
+
filename = argv[0];
|
286
|
+
if (argc > 1) {
|
287
|
+
template = argv[1];
|
288
|
+
}
|
289
|
+
}
|
290
|
+
if (filename == Qnil && template == Qnil)
|
291
|
+
rb_raise(rb_eArgError, "need either template contents or file name.");
|
292
|
+
|
293
|
+
if (template != Qnil)
|
294
|
+
Check_Type(template, T_STRING);
|
295
|
+
if (filename != Qnil)
|
296
|
+
Check_Type(filename, T_STRING);
|
297
|
+
|
298
|
+
fio_str_s str = FIO_STR_INIT;
|
299
|
+
|
263
300
|
mustache_s **m = NULL;
|
264
301
|
TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m);
|
265
302
|
if (!m) {
|
266
303
|
rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error.");
|
267
304
|
}
|
268
|
-
|
305
|
+
|
269
306
|
mustache_error_en err;
|
270
|
-
*m = mustache_load(.filename =
|
271
|
-
|
307
|
+
*m = mustache_load(.filename =
|
308
|
+
(filename == Qnil ? NULL : RSTRING_PTR(filename)),
|
309
|
+
.filename_len =
|
310
|
+
(filename == Qnil ? 0 : RSTRING_LEN(filename)),
|
311
|
+
.data = (template == Qnil ? NULL : RSTRING_PTR(template)),
|
312
|
+
.data_len = (template == Qnil ? 0 : RSTRING_LEN(template)),
|
313
|
+
.err = &err);
|
314
|
+
|
272
315
|
if (!*m)
|
273
316
|
goto error;
|
274
317
|
return self;
|
@@ -412,7 +455,7 @@ static VALUE iodine_mustache_render_klass(int argc, VALUE *argv, VALUE self) {
|
|
412
455
|
}
|
413
456
|
}
|
414
457
|
if (filename == Qnil && template == Qnil)
|
415
|
-
rb_raise(rb_eArgError, "
|
458
|
+
rb_raise(rb_eArgError, "need either template contents or file name.");
|
416
459
|
|
417
460
|
if (template != Qnil)
|
418
461
|
Check_Type(template, T_STRING);
|
@@ -502,53 +545,15 @@ Initialize Iodine::Mustache
|
|
502
545
|
void iodine_init_mustache(void) {
|
503
546
|
call_func_id = rb_intern2("call", 4);
|
504
547
|
to_s_func_id = rb_intern2("to_s", 4);
|
505
|
-
filename_id = rb_intern2("filename", 8);
|
506
|
-
data_id = rb_intern2("data", 4);
|
507
|
-
template_id = rb_intern2("template", 8);
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
This offers more security against XSS and protects against the chance of
|
513
|
-
executing Ruby code within the template.
|
514
|
-
|
515
|
-
You can test the parser using:
|
516
|
-
|
517
|
-
TEMPLATE="my_template.mustache"
|
518
|
-
|
519
|
-
require 'json'
|
520
|
-
require 'iodine'
|
521
|
-
TIMES = 100
|
522
|
-
STR = IO.binread(JSON_FILENAME); nil
|
523
|
-
|
524
|
-
JSON.parse(STR) == Iodine::JSON.parse(STR) # => true
|
525
|
-
JSON.parse(STR,
|
526
|
-
symbolize_names: true) == Iodine::JSON.parse(STR,
|
527
|
-
symbolize_names: true) # => true
|
528
|
-
JSON.parse!(STR) == Iodine::JSON.parse!(STR) # => true/false (unknown)
|
529
|
-
|
530
|
-
# warm-up
|
531
|
-
TIMES.times { JSON.parse STR }
|
532
|
-
TIMES.times { Iodine::JSON.parse STR }
|
533
|
-
|
534
|
-
Benchmark.bm do |b|
|
535
|
-
sys = b.report("system") { TIMES.times { JSON.parse STR } }
|
536
|
-
sys_sym = b.report("system sym") { TIMES.times { JSON.parse STR,
|
537
|
-
symbolize_names: true } }
|
538
|
-
iodine = b.report("iodine") { TIMES.times { Iodine::JSON.parse STR } }
|
539
|
-
iodine_sym = b.report("iodine sym") do
|
540
|
-
TIMES.times { Iodine::JSON.parse STR,
|
541
|
-
symbolize_names: true }
|
542
|
-
end
|
543
|
-
puts "System / Iodine: #{sys/iodine}"
|
544
|
-
puts "System-sym/Iodine-sym: #{sys_sym/iodine_sym}"
|
545
|
-
end; nil
|
546
|
-
|
547
|
-
|
548
|
-
*/
|
548
|
+
filename_id = rb_id2sym(rb_intern2("filename", 8));
|
549
|
+
data_id = rb_id2sym(rb_intern2("data", 4));
|
550
|
+
template_id = rb_id2sym(rb_intern2("template", 8));
|
551
|
+
rb_global_variable(&filename_id);
|
552
|
+
rb_global_variable(&data_id);
|
553
|
+
rb_global_variable(&template_id);
|
549
554
|
VALUE tmp = rb_define_class_under(IodineModule, "Mustache", rb_cData);
|
550
555
|
rb_define_alloc_func(tmp, iodine_mustache_data_alloc_c);
|
551
|
-
rb_define_method(tmp, "initialize", iodine_mustache_new, 1);
|
556
|
+
rb_define_method(tmp, "initialize", iodine_mustache_new, -1);
|
552
557
|
rb_define_method(tmp, "render", iodine_mustache_render, 1);
|
553
558
|
rb_define_singleton_method(tmp, "render", iodine_mustache_render_klass, -1);
|
554
559
|
// rb_define_module_function(tmp, "render", iodine_mustache_render_klass, 2);
|
data/ext/iodine/iodine_pubsub.c
CHANGED
@@ -388,7 +388,7 @@ static VALUE iodine_pubsub_redis_new(int argc, VALUE *argv, VALUE self) {
|
|
388
388
|
}
|
389
389
|
|
390
390
|
/* parse URL assume redis://redis:password@localhost:6379 */
|
391
|
-
|
391
|
+
fio_url_s info = fio_url_parse(RSTRING_PTR(url), RSTRING_LEN(url));
|
392
392
|
|
393
393
|
FIO_LOG_INFO("Initializing Redis engine for address: %.*s",
|
394
394
|
(int)RSTRING_LEN(url), RSTRING_PTR(url));
|
data/ext/iodine/iodine_tcp.c
CHANGED
@@ -100,8 +100,10 @@ static void iodine_tcp_ping(intptr_t uuid, fio_protocol_s *protocol) {
|
|
100
100
|
(void)uuid;
|
101
101
|
}
|
102
102
|
|
103
|
-
/** called when a connection opens */
|
103
|
+
/** fio_listen callback, called when a connection opens */
|
104
104
|
static void iodine_tcp_on_open(intptr_t uuid, void *udata) {
|
105
|
+
if (!fio_is_valid(uuid))
|
106
|
+
return;
|
105
107
|
VALUE handler = IodineCaller.call((VALUE)udata, call_id);
|
106
108
|
IodineStore.add(handler);
|
107
109
|
iodine_tcp_attch_uuid(uuid, handler);
|
@@ -115,15 +117,12 @@ static void iodine_tcp_on_finish(intptr_t uuid, void *udata) {
|
|
115
117
|
}
|
116
118
|
|
117
119
|
/**
|
118
|
-
* The `on_connect` callback should
|
119
|
-
*
|
120
|
-
*
|
121
|
-
* Should either call `fio_attach` or close the connection.
|
120
|
+
* The `on_connect` callback should either call `fio_attach` or close the
|
121
|
+
* connection.
|
122
122
|
*/
|
123
123
|
static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
|
124
|
-
|
125
|
-
|
126
|
-
IodineStore.remove(handler);
|
124
|
+
iodine_tcp_attch_uuid(uuid, (VALUE)udata);
|
125
|
+
IodineStore.remove((VALUE)udata);
|
127
126
|
}
|
128
127
|
|
129
128
|
/**
|
@@ -131,13 +130,7 @@ static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
|
|
131
130
|
* is passed along.
|
132
131
|
*/
|
133
132
|
static void iodine_tcp_on_fail(intptr_t uuid, void *udata) {
|
134
|
-
|
135
|
-
if (rb_respond_to(handler, on_closed_id)) {
|
136
|
-
VALUE client = Qnil;
|
137
|
-
IodineCaller.call2(handler, on_closed_id, 1, &client);
|
138
|
-
}
|
139
|
-
IodineStore.remove(handler);
|
140
|
-
(void)uuid;
|
133
|
+
IodineStore.remove((VALUE)udata);
|
141
134
|
}
|
142
135
|
|
143
136
|
/* *****************************************************************************
|
@@ -203,7 +196,7 @@ Here's a telnet based chat-room example:
|
|
203
196
|
self
|
204
197
|
end
|
205
198
|
end
|
206
|
-
# we can
|
199
|
+
# we use can both the `handler` keyword or a block, anything that answers #call.
|
207
200
|
Iodine.listen(port: "3000", handler: ChatHandler)
|
208
201
|
# start the service
|
209
202
|
Iodine.threads = 1
|
@@ -213,45 +206,13 @@ Here's a telnet based chat-room example:
|
|
213
206
|
|
214
207
|
Returns the handler object used.
|
215
208
|
*/
|
216
|
-
|
209
|
+
intptr_t iodine_tcp_listen(iodine_connection_args_s args) {
|
217
210
|
// clang-format on
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
if (rb_handler == Qnil || rb_handler == Qfalse || rb_handler == Qtrue) {
|
224
|
-
rb_need_block();
|
225
|
-
rb_handler = rb_block_proc();
|
226
|
-
}
|
227
|
-
IodineStore.add(rb_handler);
|
228
|
-
if (rb_address != Qnil) {
|
229
|
-
Check_Type(rb_address, T_STRING);
|
230
|
-
}
|
231
|
-
|
232
|
-
if (rb_port != Qnil) {
|
233
|
-
if (rb_port == Qfalse) {
|
234
|
-
fio_str_write_i(&port, 0);
|
235
|
-
} else if (RB_TYPE_P(rb_port, T_STRING))
|
236
|
-
fio_str_write(&port, RSTRING_PTR(rb_port), RSTRING_LEN(rb_port));
|
237
|
-
else if (RB_TYPE_P(rb_port, T_FIXNUM))
|
238
|
-
fio_str_write_i(&port, FIX2LONG(rb_port));
|
239
|
-
else
|
240
|
-
rb_raise(rb_eTypeError,
|
241
|
-
"The `port` property MUST be either a String or a Number");
|
242
|
-
}
|
243
|
-
if (fio_listen(.port = fio_str_info(&port).data,
|
244
|
-
.address =
|
245
|
-
(rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
|
246
|
-
.on_open = iodine_tcp_on_open,
|
247
|
-
.on_finish = iodine_tcp_on_finish,
|
248
|
-
.udata = (void *)rb_handler) == -1) {
|
249
|
-
IodineStore.remove(rb_handler);
|
250
|
-
rb_raise(rb_eRuntimeError,
|
251
|
-
"failed to listen to requested address, unknown error.");
|
252
|
-
}
|
253
|
-
return rb_handler;
|
254
|
-
(void)self;
|
211
|
+
IodineStore.add(args.handler);
|
212
|
+
return fio_listen(.port = args.port.data, .address = args.address.data,
|
213
|
+
.on_open = iodine_tcp_on_open,
|
214
|
+
.on_finish = iodine_tcp_on_finish, .tls = args.tls,
|
215
|
+
.udata = (void *)args.handler);
|
255
216
|
}
|
256
217
|
|
257
218
|
// clang-format off
|
@@ -264,6 +225,7 @@ The method accepts a single Hash argument with the following optional keys:
|
|
264
225
|
: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).
|
265
226
|
:handler :: A connection callback object that supports the following same callbacks listen in the {listen} method's documentation.
|
266
227
|
:timeout :: An integer timeout for connection establishment (doen't effect the new connection's timeout. Should be in the rand of 0..255.
|
228
|
+
:tls :: An {Iodine::TLS} object (optional) for secure connections.
|
267
229
|
|
268
230
|
The method also accepts an optional block.
|
269
231
|
|
@@ -273,45 +235,13 @@ If the connection fails, only the `on_close` callback will be called (with a `ni
|
|
273
235
|
|
274
236
|
Returns the handler object used.
|
275
237
|
*/
|
276
|
-
|
238
|
+
intptr_t iodine_tcp_connect(iodine_connection_args_s args){
|
277
239
|
// clang-format on
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
uint8_t timeout = 0;
|
284
|
-
fio_str_s port = FIO_STR_INIT;
|
285
|
-
if (rb_handler == Qnil || rb_handler == Qfalse || rb_handler == Qtrue) {
|
286
|
-
rb_raise(rb_eArgError, "A callback object (:handler) must be provided.");
|
287
|
-
}
|
288
|
-
IodineStore.add(rb_handler);
|
289
|
-
if (rb_address != Qnil) {
|
290
|
-
Check_Type(rb_address, T_STRING);
|
291
|
-
}
|
292
|
-
if (rb_port != Qnil) {
|
293
|
-
if (rb_port == Qfalse) {
|
294
|
-
fio_str_write_i(&port, 0);
|
295
|
-
} else if (RB_TYPE_P(rb_port, T_STRING))
|
296
|
-
fio_str_write(&port, RSTRING_PTR(rb_port), RSTRING_LEN(rb_port));
|
297
|
-
else if (RB_TYPE_P(rb_port, T_FIXNUM))
|
298
|
-
fio_str_write_i(&port, FIX2LONG(rb_port));
|
299
|
-
else
|
300
|
-
rb_raise(rb_eTypeError,
|
301
|
-
"The `port` property MUST be either a String or a Number");
|
302
|
-
}
|
303
|
-
if (rb_timeout != Qnil) {
|
304
|
-
Check_Type(rb_timeout, T_FIXNUM);
|
305
|
-
timeout = NUM2USHORT(rb_timeout);
|
306
|
-
}
|
307
|
-
fio_connect(.port = fio_str_info(&port).data,
|
308
|
-
.address =
|
309
|
-
(rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
|
310
|
-
.on_connect = iodine_tcp_on_connect,
|
311
|
-
.on_fail = iodine_tcp_on_fail, .timeout = timeout,
|
312
|
-
.udata = (void *)rb_handler);
|
313
|
-
return rb_handler;
|
314
|
-
(void)self;
|
240
|
+
IodineStore.add(args.handler);
|
241
|
+
return fio_connect(.port = args.port.data, .address = args.address.data,
|
242
|
+
.on_connect = iodine_tcp_on_connect, .tls = args.tls,
|
243
|
+
.on_fail = iodine_tcp_on_fail, .timeout = args.ping,
|
244
|
+
.udata = (void *)args.handler);
|
315
245
|
}
|
316
246
|
|
317
247
|
// clang-format off
|
@@ -358,8 +288,6 @@ void iodine_init_tcp_connections(void) {
|
|
358
288
|
|
359
289
|
IodineBinaryEncoding = rb_enc_find("binary");
|
360
290
|
|
361
|
-
rb_define_module_function(IodineModule, "listen", iodine_tcp_listen, 1);
|
362
|
-
rb_define_module_function(IodineModule, "connect", iodine_tcp_connect, 1);
|
363
291
|
rb_define_module_function(IodineModule, "attach_fd", iodine_tcp_attach_fd, 2);
|
364
292
|
}
|
365
293
|
|
@@ -369,16 +297,15 @@ Allow uuid attachment
|
|
369
297
|
|
370
298
|
/** assigns a protocol and IO object to a handler */
|
371
299
|
void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
|
300
|
+
FIO_LOG_DEBUG("Iodine attaching handler %p to uuid %p", (void *)handler,
|
301
|
+
(void *)uuid);
|
372
302
|
if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
|
373
303
|
fio_close(uuid);
|
374
304
|
return;
|
375
305
|
}
|
376
306
|
/* temporary, in case `iodine_connection_new` invokes the GC */
|
377
307
|
iodine_protocol_s *p = malloc(sizeof(*p));
|
378
|
-
|
379
|
-
perror("FATAL ERROR: No Memory!");
|
380
|
-
exit(errno);
|
381
|
-
}
|
308
|
+
FIO_ASSERT_ALLOC(p);
|
382
309
|
*p = (iodine_protocol_s){
|
383
310
|
.p =
|
384
311
|
{
|
@@ -393,6 +320,13 @@ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
|
|
393
320
|
};
|
394
321
|
/* clear away (remember the connection object manages these concerns) */
|
395
322
|
fio_attach(uuid, &p->p);
|
396
|
-
|
397
|
-
|
323
|
+
if (fio_is_valid(uuid)) {
|
324
|
+
iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil);
|
325
|
+
p->p.on_ready = iodine_tcp_on_ready;
|
326
|
+
fio_force_event(uuid, FIO_EVENT_ON_READY);
|
327
|
+
} else {
|
328
|
+
FIO_LOG_DEBUG(
|
329
|
+
"Iodine couldn't attach handler %p to uuid %p - invalid uuid.",
|
330
|
+
(void *)handler, (void *)uuid);
|
331
|
+
}
|
398
332
|
}
|
data/ext/iodine/iodine_tcp.h
CHANGED
@@ -3,7 +3,11 @@
|
|
3
3
|
|
4
4
|
#include "ruby.h"
|
5
5
|
|
6
|
+
#include "iodine.h"
|
7
|
+
|
6
8
|
void iodine_init_tcp_connections(void);
|
7
9
|
void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler);
|
10
|
+
intptr_t iodine_tcp_listen(iodine_connection_args_s args);
|
11
|
+
intptr_t iodine_tcp_connect(iodine_connection_args_s args);
|
8
12
|
|
9
13
|
#endif
|
@@ -0,0 +1,267 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#include <fio.h>
|
4
|
+
#include <fio_tls.h>
|
5
|
+
#include <iodine.h>
|
6
|
+
|
7
|
+
static VALUE server_name_sym = Qnil, certificate_sym = Qnil,
|
8
|
+
private_key_sym = Qnil, password_sym = Qnil;
|
9
|
+
VALUE IodineTLSClass;
|
10
|
+
/* *****************************************************************************
|
11
|
+
C <=> Ruby Data allocation
|
12
|
+
***************************************************************************** */
|
13
|
+
|
14
|
+
static size_t iodine_tls_data_size(const void *c_) {
|
15
|
+
return sizeof(fio_tls_s *);
|
16
|
+
(void)c_;
|
17
|
+
}
|
18
|
+
|
19
|
+
static void iodine_tls_data_free(void *c_) { fio_tls_destroy(c_); }
|
20
|
+
|
21
|
+
static const rb_data_type_t iodine_tls_data_type = {
|
22
|
+
.wrap_struct_name = "IodineTLSData",
|
23
|
+
.function =
|
24
|
+
{
|
25
|
+
.dmark = NULL,
|
26
|
+
.dfree = iodine_tls_data_free,
|
27
|
+
.dsize = iodine_tls_data_size,
|
28
|
+
},
|
29
|
+
.data = NULL,
|
30
|
+
// .flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
31
|
+
};
|
32
|
+
|
33
|
+
/* Iodine::PubSub::Engine.allocate */
|
34
|
+
static VALUE iodine_tls_data_alloc_c(VALUE klass) {
|
35
|
+
fio_tls_s *tls = fio_tls_new(NULL, NULL, NULL, NULL);
|
36
|
+
FIO_ASSERT_ALLOC(tls);
|
37
|
+
return TypedData_Wrap_Struct(klass, &iodine_tls_data_type, tls);
|
38
|
+
}
|
39
|
+
|
40
|
+
/* *****************************************************************************
|
41
|
+
ALPN selection callback
|
42
|
+
***************************************************************************** */
|
43
|
+
|
44
|
+
FIO_FUNC void iodine_tls_alpn_cb(intptr_t uuid, void *udata, void *block_) {
|
45
|
+
if (!fio_is_valid(uuid)) {
|
46
|
+
FIO_LOG_DEBUG("ALPN callback called for invalid connetion. SSL/TLS error?");
|
47
|
+
return;
|
48
|
+
}
|
49
|
+
VALUE new_handler = IodineCaller.call((VALUE)block_, iodine_call_id);
|
50
|
+
if (!new_handler || new_handler == Qnil || new_handler == Qtrue ||
|
51
|
+
new_handler == Qfalse) {
|
52
|
+
fio_close(uuid);
|
53
|
+
return;
|
54
|
+
}
|
55
|
+
|
56
|
+
iodine_tcp_attch_uuid(uuid, new_handler);
|
57
|
+
|
58
|
+
(void)udata; /* we can't use `udata`, since it's different in HTTP vs. TCP */
|
59
|
+
}
|
60
|
+
|
61
|
+
/* *****************************************************************************
|
62
|
+
C API
|
63
|
+
***************************************************************************** */
|
64
|
+
|
65
|
+
fio_tls_s *iodine_tls2c(VALUE self) {
|
66
|
+
fio_tls_s *c = NULL;
|
67
|
+
if (self == Qnil || self == Qfalse)
|
68
|
+
return NULL;
|
69
|
+
TypedData_Get_Struct(self, fio_tls_s, &iodine_tls_data_type, c);
|
70
|
+
if (!c) {
|
71
|
+
rb_raise(rb_eTypeError, "Iodine::TLS error - not an Iodine::TLS object?");
|
72
|
+
}
|
73
|
+
return c;
|
74
|
+
}
|
75
|
+
|
76
|
+
/* *****************************************************************************
|
77
|
+
Ruby API
|
78
|
+
***************************************************************************** */
|
79
|
+
|
80
|
+
/**
|
81
|
+
Assigns the TLS context a public sertificate, allowing remote parties to
|
82
|
+
validate the connection's identity.
|
83
|
+
|
84
|
+
A self signed certificate is automatically created if the `server_name` argument
|
85
|
+
is specified and either (or both) of the `certificate` or `private_ket`
|
86
|
+
arguments are missing.
|
87
|
+
|
88
|
+
Some implementations allow servers to have more than a single certificate, which
|
89
|
+
will be selected using the SNI extension. I believe the existing OpenSSL
|
90
|
+
implementation supports this option (untested).
|
91
|
+
|
92
|
+
Iodine::TLS#use_certificate(server_name,
|
93
|
+
certificate = nil,
|
94
|
+
private_key = nil,
|
95
|
+
password = nil)
|
96
|
+
|
97
|
+
Certificates and keys should be String objects leading to a PEM file.
|
98
|
+
|
99
|
+
This method also accepts named arguments. i.e.:
|
100
|
+
|
101
|
+
tls = Iodine::TLS.new
|
102
|
+
tls.use_certificate server_name: "example.com"
|
103
|
+
tls.use_certificate certificate: "my_cert.pem", private_key: "my_key.pem"
|
104
|
+
|
105
|
+
Since TLS setup is crucial for security, a missing file will result in Iodine
|
106
|
+
crashing with an error message. This is expected behavior.
|
107
|
+
|
108
|
+
*/
|
109
|
+
static VALUE iodine_tls_use_certificate(int argc, VALUE *argv, VALUE self) {
|
110
|
+
VALUE server_name = Qnil, certificate = Qnil, private_key = Qnil,
|
111
|
+
password = Qnil;
|
112
|
+
if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) {
|
113
|
+
/* named arguments */
|
114
|
+
server_name = rb_hash_aref(argv[0], server_name_sym);
|
115
|
+
certificate = rb_hash_aref(argv[0], certificate_sym);
|
116
|
+
private_key = rb_hash_aref(argv[0], private_key_sym);
|
117
|
+
password = rb_hash_aref(argv[0], password_sym);
|
118
|
+
} else {
|
119
|
+
/* regular arguments */
|
120
|
+
switch (argc) {
|
121
|
+
case 4: /* overflow */
|
122
|
+
password = argv[3];
|
123
|
+
Check_Type(password, T_STRING);
|
124
|
+
case 3: /* overflow */
|
125
|
+
private_key = argv[1];
|
126
|
+
Check_Type(private_key, T_STRING);
|
127
|
+
case 2: /* overflow */
|
128
|
+
certificate = argv[2];
|
129
|
+
Check_Type(certificate, T_STRING);
|
130
|
+
case 1: /* overflow */
|
131
|
+
server_name = argv[0];
|
132
|
+
Check_Type(server_name, T_STRING);
|
133
|
+
break;
|
134
|
+
default:
|
135
|
+
rb_raise(rb_eArgError, "expecting 1..4 arguments or named arguments.");
|
136
|
+
return self;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
fio_tls_s *t = iodine_tls2c(self);
|
141
|
+
char *srvname = (server_name == Qnil ? NULL : StringValueCStr(server_name));
|
142
|
+
char *pubcert = (certificate == Qnil ? NULL : StringValueCStr(certificate));
|
143
|
+
char *prvkey = (private_key == Qnil ? NULL : StringValueCStr(private_key));
|
144
|
+
char *pass = (password == Qnil ? NULL : StringValueCStr(password));
|
145
|
+
|
146
|
+
fio_tls_cert_add(t, srvname, pubcert, prvkey, pass);
|
147
|
+
return self;
|
148
|
+
}
|
149
|
+
|
150
|
+
/**
|
151
|
+
Adds a certificate PEM file to the list of trusted certificates and enforces
|
152
|
+
peer verification.
|
153
|
+
|
154
|
+
This is extremely important when using {Iodine::TLS} for client connections,
|
155
|
+
since adding the target server's
|
156
|
+
|
157
|
+
It is enough to add the Certificate Authority's (CA) certificate, there's no
|
158
|
+
need to add each client or server certificate.
|
159
|
+
|
160
|
+
When {trust} is used on a server TLS, only trusted clients will be allowed to
|
161
|
+
connect.
|
162
|
+
|
163
|
+
Since TLS setup is crucial for security, a missing file will result in Iodine
|
164
|
+
crashing with an error message. This is expected behavior.
|
165
|
+
*/
|
166
|
+
static VALUE iodine_tls_trust(VALUE self, VALUE certificate) {
|
167
|
+
Check_Type(certificate, T_STRING);
|
168
|
+
fio_tls_s *t = iodine_tls2c(self);
|
169
|
+
char *pubcert =
|
170
|
+
(certificate == Qnil ? NULL : IODINE_RSTRINFO(certificate).data);
|
171
|
+
fio_tls_trust(t, pubcert);
|
172
|
+
return self;
|
173
|
+
}
|
174
|
+
|
175
|
+
/**
|
176
|
+
Adds an ALPN protocol callback for the named protocol, the required block must
|
177
|
+
return the handler for that protocol.
|
178
|
+
|
179
|
+
The first protocol added will be the default protocol in cases where ALPN
|
180
|
+
failed.
|
181
|
+
|
182
|
+
i.e.:
|
183
|
+
|
184
|
+
tls.on_protocol("http/1.1") { HTTPConnection.new }
|
185
|
+
|
186
|
+
When implementing TLS clients, this identifies the protocol(s) that should be
|
187
|
+
requested by the client.
|
188
|
+
|
189
|
+
When implementing TLS servers, this identifies the protocol(s) offered by the
|
190
|
+
server.
|
191
|
+
|
192
|
+
More than a single protocol can be set, but iodine doesn't offer, at this
|
193
|
+
moment, a way to handle these changes or to detect which protocol was selected
|
194
|
+
except by assigning a different callback per protocol.
|
195
|
+
|
196
|
+
This is implemented using the ALPN extension to TLS.
|
197
|
+
*/
|
198
|
+
static VALUE iodine_tls_alpn(VALUE self, VALUE protocol_name) {
|
199
|
+
Check_Type(protocol_name, T_STRING);
|
200
|
+
rb_need_block();
|
201
|
+
fio_tls_s *t = iodine_tls2c(self);
|
202
|
+
char *prname =
|
203
|
+
(protocol_name == Qnil ? NULL : IODINE_RSTRINFO(protocol_name).data);
|
204
|
+
VALUE block = IodineStore.add(rb_block_proc());
|
205
|
+
fio_tls_alpn_add(t, prname, iodine_tls_alpn_cb, (void *)block,
|
206
|
+
(void (*)(void *))IodineStore.remove);
|
207
|
+
return self;
|
208
|
+
}
|
209
|
+
|
210
|
+
/**
|
211
|
+
Loads the mustache template found in `:filename`. If `:template` is provided
|
212
|
+
it will be used instead of reading the file's content.
|
213
|
+
|
214
|
+
Iodine::Mustache.new(filename, template = nil)
|
215
|
+
|
216
|
+
When template data is provided, filename (if any) will only be used for
|
217
|
+
partial template path resolution and the template data will be used for the
|
218
|
+
template's content. This allows, for example, for front matter to be extracted
|
219
|
+
before parsing the template.
|
220
|
+
|
221
|
+
Once a template was loaded, it could be rendered using {render}.
|
222
|
+
|
223
|
+
Accepts named arguments as well:
|
224
|
+
|
225
|
+
Iodine::Mustache.new(filename: "foo.mustache", template: "{{ bar }}")
|
226
|
+
|
227
|
+
*/
|
228
|
+
static VALUE iodine_tls_new(int argc, VALUE *argv, VALUE self) {
|
229
|
+
if (argc) {
|
230
|
+
iodine_tls_use_certificate(argc, argv, self);
|
231
|
+
}
|
232
|
+
return self;
|
233
|
+
}
|
234
|
+
|
235
|
+
/* *****************************************************************************
|
236
|
+
Initialize Iodine::TLS
|
237
|
+
***************************************************************************** */
|
238
|
+
#define IODINE_MAKE_SYM(name) \
|
239
|
+
do { \
|
240
|
+
name##_sym = rb_id2sym(rb_intern(#name)); \
|
241
|
+
rb_global_variable(&name##_sym); \
|
242
|
+
} while (0)
|
243
|
+
|
244
|
+
void iodine_init_tls(void) {
|
245
|
+
|
246
|
+
IODINE_MAKE_SYM(server_name);
|
247
|
+
IODINE_MAKE_SYM(certificate);
|
248
|
+
IODINE_MAKE_SYM(private_key);
|
249
|
+
IODINE_MAKE_SYM(password);
|
250
|
+
|
251
|
+
IodineTLSClass = rb_define_class_under(IodineModule, "TLS", rb_cData);
|
252
|
+
rb_define_alloc_func(IodineTLSClass, iodine_tls_data_alloc_c);
|
253
|
+
rb_define_method(IodineTLSClass, "initialize", iodine_tls_new, -1);
|
254
|
+
rb_define_method(IodineTLSClass, "use_certificate",
|
255
|
+
iodine_tls_use_certificate, -1);
|
256
|
+
rb_define_method(IodineTLSClass, "trust", iodine_tls_trust, 1);
|
257
|
+
rb_define_method(IodineTLSClass, "on_protocol", iodine_tls_alpn, 1);
|
258
|
+
|
259
|
+
#if HAVE_OPENSSL
|
260
|
+
rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qtrue);
|
261
|
+
#elif HAVE_BEARSSL
|
262
|
+
rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse);
|
263
|
+
#else
|
264
|
+
rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse);
|
265
|
+
#endif
|
266
|
+
}
|
267
|
+
#undef IODINE_MAKE_SYM
|