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/http.h
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#ifndef H_HTTP_H
|
2
2
|
/*
|
3
|
-
Copyright: Boaz Segev, 2016-
|
3
|
+
Copyright: Boaz Segev, 2016-2019
|
4
4
|
License: MIT
|
5
5
|
|
6
6
|
Feel free to copy, use and enjoy according to the license provided.
|
@@ -447,9 +447,10 @@ intptr_t http_listen(const char *port, const char *binding,
|
|
447
447
|
*
|
448
448
|
* The `on_finish` callback is always called.
|
449
449
|
*/
|
450
|
-
intptr_t http_connect(const char *
|
451
|
-
|
452
|
-
|
450
|
+
intptr_t http_connect(const char *url, const char *unix_address,
|
451
|
+
struct http_settings_s);
|
452
|
+
#define http_connect(url, unix_address, ...) \
|
453
|
+
http_connect((url), (unix_address), (struct http_settings_s){__VA_ARGS__})
|
453
454
|
|
454
455
|
/**
|
455
456
|
* Returns the settings used to setup the connection or NULL on error.
|
@@ -586,9 +587,9 @@ int http_upgrade2ws(http_s *http, websocket_settings_s);
|
|
586
587
|
*
|
587
588
|
* Returns -1 on error;
|
588
589
|
*/
|
589
|
-
int websocket_connect(const char *
|
590
|
-
#define websocket_connect(
|
591
|
-
websocket_connect((
|
590
|
+
int websocket_connect(const char *url, websocket_settings_s settings);
|
591
|
+
#define websocket_connect(url, ...) \
|
592
|
+
websocket_connect((url), (websocket_settings_s){__VA_ARGS__})
|
592
593
|
|
593
594
|
#include <websockets.h>
|
594
595
|
|
@@ -949,16 +950,8 @@ HTTP URL parsing
|
|
949
950
|
***************************************************************************** */
|
950
951
|
|
951
952
|
/** the result returned by `http_url_parse` */
|
952
|
-
typedef
|
953
|
-
|
954
|
-
fio_str_info_s user;
|
955
|
-
fio_str_info_s password;
|
956
|
-
fio_str_info_s host;
|
957
|
-
fio_str_info_s port;
|
958
|
-
fio_str_info_s path;
|
959
|
-
fio_str_info_s query;
|
960
|
-
fio_str_info_s target;
|
961
|
-
} http_url_s;
|
953
|
+
typedef fio_url_s http_url_s
|
954
|
+
__attribute__((deprecated("use fio_url_s instead")));
|
962
955
|
|
963
956
|
/**
|
964
957
|
* Parses the URI returning it's components and their lengths (no decoding
|
@@ -991,7 +984,7 @@ typedef struct {
|
|
991
984
|
*
|
992
985
|
* Invalid formats might produce unexpected results. No error testing performed.
|
993
986
|
*/
|
994
|
-
|
987
|
+
#define http_url_parse(url, len) fio_url_parse((url), (len))
|
995
988
|
|
996
989
|
#if DEBUG
|
997
990
|
void http_tests(void);
|
data/ext/iodine/http1.c
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
Copyright: Boaz Segev, 2017
|
2
|
+
Copyright: Boaz Segev, 2017-2019
|
3
3
|
License: MIT
|
4
4
|
*/
|
5
5
|
#include <fio.h>
|
@@ -101,7 +101,7 @@ static FIOBJ headers2str(http_s *h, uintptr_t padding) {
|
|
101
101
|
|
102
102
|
static uintptr_t connection_hash;
|
103
103
|
if (!connection_hash)
|
104
|
-
connection_hash =
|
104
|
+
connection_hash = fiobj_hash_string("connection", 10);
|
105
105
|
|
106
106
|
struct header_writer_s w;
|
107
107
|
{
|
@@ -156,7 +156,7 @@ static FIOBJ headers2str(http_s *h, uintptr_t padding) {
|
|
156
156
|
/* make sure we have a host header? */
|
157
157
|
static uint64_t host_hash;
|
158
158
|
if (!host_hash)
|
159
|
-
host_hash =
|
159
|
+
host_hash = fiobj_hash_string("host", 4);
|
160
160
|
FIOBJ tmp;
|
161
161
|
if (!fiobj_hash_get2(h->private_data.out_headers, host_hash) &&
|
162
162
|
(tmp = fiobj_hash_get2(h->headers, host_hash))) {
|
@@ -314,7 +314,7 @@ static void http1_websocket_client_on_hangup(http_settings_s *settings) {
|
|
314
314
|
websocket_settings_s *s = settings->udata;
|
315
315
|
if (s) {
|
316
316
|
if (s->on_close)
|
317
|
-
s->on_close(0,
|
317
|
+
s->on_close(0, s->udata);
|
318
318
|
fio_free(settings->udata);
|
319
319
|
settings->udata = NULL;
|
320
320
|
}
|
@@ -326,9 +326,9 @@ static int http1_http2websocket_server(http_s *h, websocket_settings_s *args) {
|
|
326
326
|
static uintptr_t sec_version = 0;
|
327
327
|
static uintptr_t sec_key = 0;
|
328
328
|
if (!sec_version)
|
329
|
-
sec_version =
|
329
|
+
sec_version = fiobj_hash_string("sec-websocket-version", 21);
|
330
330
|
if (!sec_key)
|
331
|
-
sec_key =
|
331
|
+
sec_key = fiobj_hash_string("sec-websocket-key", 17);
|
332
332
|
|
333
333
|
FIOBJ tmp = fiobj_hash_get2(h->headers, sec_version);
|
334
334
|
if (!tmp)
|
data/ext/iodine/http1.h
CHANGED
data/ext/iodine/http1_parser.c
CHANGED
data/ext/iodine/http1_parser.h
CHANGED
data/ext/iodine/http_internal.c
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
Copyright: Boaz Segev, 2016-
|
2
|
+
Copyright: Boaz Segev, 2016-2019
|
3
3
|
License: MIT
|
4
4
|
|
5
5
|
Feel free to copy, use and enjoy according to the license provided.
|
@@ -17,20 +17,18 @@ static uint64_t http_upgrade_hash = 0;
|
|
17
17
|
void http_on_request_handler______internal(http_s *h,
|
18
18
|
http_settings_s *settings) {
|
19
19
|
if (!http_upgrade_hash)
|
20
|
-
http_upgrade_hash =
|
20
|
+
http_upgrade_hash = fiobj_hash_string("upgrade", 7);
|
21
21
|
h->udata = settings->udata;
|
22
22
|
|
23
23
|
static uint64_t host_hash = 0;
|
24
24
|
if (!host_hash)
|
25
|
-
host_hash =
|
25
|
+
host_hash = fiobj_hash_string("host", 4);
|
26
26
|
|
27
27
|
if (1) {
|
28
28
|
/* test for Host header and avoid duplicates */
|
29
29
|
FIOBJ tmp = fiobj_hash_get2(h->headers, host_hash);
|
30
|
-
if (!tmp)
|
31
|
-
|
32
|
-
return;
|
33
|
-
}
|
30
|
+
if (!tmp)
|
31
|
+
goto missing_host;
|
34
32
|
if (FIOBJ_TYPE_IS(tmp, FIOBJ_T_ARRAY)) {
|
35
33
|
fiobj_hash_set(h->headers, HTTP_HEADER_HOST, fiobj_ary_pop(tmp));
|
36
34
|
}
|
@@ -70,12 +68,16 @@ upgrade:
|
|
70
68
|
eventsource:
|
71
69
|
settings->on_upgrade(h, (char *)"sse", 3);
|
72
70
|
return;
|
71
|
+
missing_host:
|
72
|
+
FIO_LOG_DEBUG("missing Host header");
|
73
|
+
http_send_error(h, 400);
|
74
|
+
return;
|
73
75
|
}
|
74
76
|
|
75
77
|
void http_on_response_handler______internal(http_s *h,
|
76
78
|
http_settings_s *settings) {
|
77
79
|
if (!http_upgrade_hash)
|
78
|
-
http_upgrade_hash =
|
80
|
+
http_upgrade_hash = fiobj_hash_string("upgrade", 7);
|
79
81
|
h->udata = settings->udata;
|
80
82
|
FIOBJ t = fiobj_hash_get2(h->headers, http_upgrade_hash);
|
81
83
|
if (t == FIOBJ_INVALID) {
|
data/ext/iodine/http_internal.h
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
Copyright: Boaz Segev, 2016-
|
2
|
+
Copyright: Boaz Segev, 2016-2019
|
3
3
|
License: MIT
|
4
4
|
|
5
5
|
Feel free to copy, use and enjoy according to the license provided.
|
@@ -218,8 +218,18 @@ static inline void set_header_add(FIOBJ hash, FIOBJ name, FIOBJ value) {
|
|
218
218
|
fiobj_ary_push(tmp, old);
|
219
219
|
old = tmp;
|
220
220
|
}
|
221
|
-
|
222
|
-
|
221
|
+
if (FIOBJ_TYPE_IS(value, FIOBJ_T_ARRAY)) {
|
222
|
+
for (size_t i = 0; i < fiobj_ary_count(value); ++i) {
|
223
|
+
fiobj_ary_push(old, fiobj_dup(fiobj_ary_index(value, i)));
|
224
|
+
}
|
225
|
+
/* frees `value` */
|
226
|
+
fiobj_hash_set(hash, name, old);
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
/* value will be owned by both hash and array */
|
230
|
+
fiobj_ary_push(old, value);
|
231
|
+
/* don't free `value` (leave in array) */
|
232
|
+
fiobj_hash_replace(hash, name, old);
|
223
233
|
}
|
224
234
|
|
225
235
|
#endif /* H_HTTP_INTERNAL_H */
|
data/ext/iodine/iodine.c
CHANGED
@@ -28,7 +28,32 @@ Constants and State
|
|
28
28
|
|
29
29
|
VALUE IodineModule;
|
30
30
|
VALUE IodineBaseModule;
|
31
|
-
|
31
|
+
|
32
|
+
/** Default connection settings for {Iodine.listen} and {Iodine.connect}. */
|
33
|
+
VALUE iodine_default_args;
|
34
|
+
|
35
|
+
ID iodine_call_id;
|
36
|
+
|
37
|
+
static VALUE address_sym;
|
38
|
+
static VALUE app_sym;
|
39
|
+
static VALUE body_sym;
|
40
|
+
static VALUE cookies_sym;
|
41
|
+
static VALUE handler_sym;
|
42
|
+
static VALUE headers_sym;
|
43
|
+
static VALUE log_sym;
|
44
|
+
static VALUE max_body_sym;
|
45
|
+
static VALUE max_clients_sym;
|
46
|
+
static VALUE max_headers_sym;
|
47
|
+
static VALUE max_msg_sym;
|
48
|
+
static VALUE method_sym;
|
49
|
+
static VALUE path_sym;
|
50
|
+
static VALUE ping_sym;
|
51
|
+
static VALUE port_sym;
|
52
|
+
static VALUE public_sym;
|
53
|
+
static VALUE service_sym;
|
54
|
+
static VALUE timeout_sym;
|
55
|
+
static VALUE tls_sym;
|
56
|
+
static VALUE url_sym;
|
32
57
|
|
33
58
|
/* *****************************************************************************
|
34
59
|
Idling
|
@@ -37,7 +62,7 @@ Idling
|
|
37
62
|
/* performs a Ruby state callback and clears the Ruby object's memory */
|
38
63
|
static void iodine_perform_on_idle_callback(void *blk_) {
|
39
64
|
VALUE blk = (VALUE)blk_;
|
40
|
-
IodineCaller.call(blk,
|
65
|
+
IodineCaller.call(blk, iodine_call_id);
|
41
66
|
IodineStore.remove(blk);
|
42
67
|
fio_state_callback_remove(FIO_CALL_ON_IDLE, iodine_perform_on_idle_callback,
|
43
68
|
blk_);
|
@@ -361,6 +386,13 @@ static VALUE iodine_cli_parse(VALUE self) {
|
|
361
386
|
FIO_CLI_PRINT_HEADER("WebSocket Settings:"),
|
362
387
|
FIO_CLI_INT("-max-msg -maxms incoming WebSocket message limit in Kb. "
|
363
388
|
"Default: 250Kb"),
|
389
|
+
FIO_CLI_PRINT_HEADER("SSL/TLS:"),
|
390
|
+
FIO_CLI_BOOL("-tls enable SSL/TLS using a self-signed certificate."),
|
391
|
+
FIO_CLI_STRING(
|
392
|
+
"-tls-cert -cert the SSL/TLS public certificate file name."),
|
393
|
+
FIO_CLI_STRING("-tls-key -key the SSL/TLS private key file name."),
|
394
|
+
FIO_CLI_STRING("-tls-password the password (if any) protecting the "
|
395
|
+
"private key file."),
|
364
396
|
FIO_CLI_PRINT_HEADER("Connecting Iodine to Redis:"),
|
365
397
|
FIO_CLI_STRING(
|
366
398
|
"-redis -r an optional Redis URL server address. Default: none."),
|
@@ -385,7 +417,7 @@ static VALUE iodine_cli_parse(VALUE self) {
|
|
385
417
|
iodine_threads_set(IodineModule, INT2NUM(fio_cli_get_i("-t")));
|
386
418
|
}
|
387
419
|
if (fio_cli_get_bool("-v")) {
|
388
|
-
rb_hash_aset(defaults,
|
420
|
+
rb_hash_aset(defaults, log_sym, Qtrue);
|
389
421
|
}
|
390
422
|
if (fio_cli_get_bool("-warmup")) {
|
391
423
|
rb_hash_aset(defaults, ID2SYM(rb_intern("warmup_")), Qtrue);
|
@@ -402,16 +434,13 @@ static VALUE iodine_cli_parse(VALUE self) {
|
|
402
434
|
}
|
403
435
|
fio_cli_set("-p", "0");
|
404
436
|
}
|
405
|
-
rb_hash_aset(defaults,
|
406
|
-
rb_str_new_cstr(fio_cli_get("-b")));
|
437
|
+
rb_hash_aset(defaults, address_sym, rb_str_new_cstr(fio_cli_get("-b")));
|
407
438
|
}
|
408
439
|
if (fio_cli_get("-p")) {
|
409
|
-
rb_hash_aset(defaults,
|
410
|
-
rb_str_new_cstr(fio_cli_get("-p")));
|
440
|
+
rb_hash_aset(defaults, port_sym, rb_str_new_cstr(fio_cli_get("-p")));
|
411
441
|
}
|
412
442
|
if (fio_cli_get("-www")) {
|
413
|
-
rb_hash_aset(defaults,
|
414
|
-
rb_str_new_cstr(fio_cli_get("-www")));
|
443
|
+
rb_hash_aset(defaults, public_sym, rb_str_new_cstr(fio_cli_get("-www")));
|
415
444
|
}
|
416
445
|
if (!fio_cli_get("-redis") && getenv("IODINE_REDIS_URL")) {
|
417
446
|
fio_cli_set("-redis", getenv("IODINE_REDIS_URL"));
|
@@ -421,28 +450,50 @@ static VALUE iodine_cli_parse(VALUE self) {
|
|
421
450
|
rb_str_new_cstr(fio_cli_get("-redis")));
|
422
451
|
}
|
423
452
|
if (fio_cli_get("-k")) {
|
424
|
-
rb_hash_aset(defaults,
|
425
|
-
INT2NUM(fio_cli_get_i("-k")));
|
453
|
+
rb_hash_aset(defaults, timeout_sym, INT2NUM(fio_cli_get_i("-k")));
|
426
454
|
}
|
427
455
|
if (fio_cli_get("-ping")) {
|
428
|
-
rb_hash_aset(defaults,
|
429
|
-
INT2NUM(fio_cli_get_i("-ping")));
|
456
|
+
rb_hash_aset(defaults, ping_sym, INT2NUM(fio_cli_get_i("-ping")));
|
430
457
|
}
|
431
458
|
if (fio_cli_get("-redis-ping")) {
|
432
459
|
rb_hash_aset(defaults, ID2SYM(rb_intern("redis_ping_")),
|
433
460
|
INT2NUM(fio_cli_get_i("-redis-ping")));
|
434
461
|
}
|
435
462
|
if (fio_cli_get("-max-body")) {
|
436
|
-
rb_hash_aset(defaults,
|
437
|
-
INT2NUM((fio_cli_get_i("-max-body") * 1024 * 1024)));
|
463
|
+
rb_hash_aset(defaults, max_body_sym,
|
464
|
+
INT2NUM((fio_cli_get_i("-max-body") /* * 1024 * 1024 */)));
|
465
|
+
}
|
466
|
+
if (fio_cli_get("-maxms")) {
|
467
|
+
rb_hash_aset(defaults, max_msg_sym,
|
468
|
+
INT2NUM((fio_cli_get_i("-maxms") /* * 1024 */)));
|
438
469
|
}
|
439
|
-
if (fio_cli_get("-
|
440
|
-
rb_hash_aset(defaults,
|
441
|
-
INT2NUM((fio_cli_get_i("-
|
470
|
+
if (fio_cli_get("-maxhd")) {
|
471
|
+
rb_hash_aset(defaults, max_headers_sym,
|
472
|
+
INT2NUM((fio_cli_get_i("-maxhd") /* * 1024 */)));
|
442
473
|
}
|
443
|
-
if (fio_cli_get("-
|
444
|
-
|
445
|
-
|
474
|
+
if (fio_cli_get_bool("-tls") || fio_cli_get("-key") || fio_cli_get("-cert")) {
|
475
|
+
VALUE rbtls = IodineCaller.call(IodineTLSClass, rb_intern2("new", 3));
|
476
|
+
if (rbtls == Qnil) {
|
477
|
+
FIO_LOG_FATAL("Iodine internal error, Ruby TLS object is nil.");
|
478
|
+
exit(-1);
|
479
|
+
}
|
480
|
+
fio_tls_s *tls = iodine_tls2c(rbtls);
|
481
|
+
if (!tls) {
|
482
|
+
FIO_LOG_FATAL("Iodine internal error, TLS object NULL.");
|
483
|
+
exit(-1);
|
484
|
+
}
|
485
|
+
if (fio_cli_get("-tls-key") && fio_cli_get("-tls-cert")) {
|
486
|
+
fio_tls_cert_add(tls, NULL, fio_cli_get("-tls-cert"),
|
487
|
+
fio_cli_get("-tls-key"), fio_cli_get("-tls-password"));
|
488
|
+
} else {
|
489
|
+
if (!fio_cli_get_bool("-tls"))
|
490
|
+
FIO_LOG_ERROR("TLS support requires both key and certificate."
|
491
|
+
"\r\n\t\tfalling back on a self signed certificate.");
|
492
|
+
char name[1024];
|
493
|
+
fio_local_addr(name, 1024);
|
494
|
+
fio_tls_cert_add(tls, name, NULL, NULL, NULL);
|
495
|
+
}
|
496
|
+
rb_hash_aset(defaults, tls_sym, rbtls);
|
446
497
|
}
|
447
498
|
if (fio_cli_unnamed_count()) {
|
448
499
|
rb_hash_aset(defaults, ID2SYM(rb_intern("filename_")),
|
@@ -459,6 +510,697 @@ finish:
|
|
459
510
|
return ret;
|
460
511
|
}
|
461
512
|
|
513
|
+
/* *****************************************************************************
|
514
|
+
Argument support for `connect` / `listen`
|
515
|
+
***************************************************************************** */
|
516
|
+
|
517
|
+
static int for_each_header_value(VALUE key, VALUE val, VALUE h_) {
|
518
|
+
FIOBJ h = h_;
|
519
|
+
if (RB_TYPE_P(key, T_SYMBOL))
|
520
|
+
key = rb_sym2str(key);
|
521
|
+
if (!RB_TYPE_P(key, T_STRING)) {
|
522
|
+
FIO_LOG_WARNING("invalid key type in header hash, ignored.");
|
523
|
+
return ST_CONTINUE;
|
524
|
+
}
|
525
|
+
if (RB_TYPE_P(val, T_SYMBOL))
|
526
|
+
val = rb_sym2str(val);
|
527
|
+
if (RB_TYPE_P(val, T_STRING)) {
|
528
|
+
FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
|
529
|
+
fiobj_hash_set(h, k, fiobj_str_new(RSTRING_PTR(val), RSTRING_LEN(val)));
|
530
|
+
fiobj_free(k);
|
531
|
+
} else if (RB_TYPE_P(val, T_ARRAY)) {
|
532
|
+
FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
|
533
|
+
size_t len = rb_array_len(val);
|
534
|
+
FIOBJ v = fiobj_ary_new2(len);
|
535
|
+
fiobj_hash_set(h, k, v);
|
536
|
+
fiobj_free(k);
|
537
|
+
for (size_t i = 0; i < len; ++i) {
|
538
|
+
VALUE tmp = rb_ary_entry(val, i);
|
539
|
+
if (RB_TYPE_P(tmp, T_SYMBOL))
|
540
|
+
tmp = rb_sym2str(tmp);
|
541
|
+
if (RB_TYPE_P(tmp, T_STRING))
|
542
|
+
fiobj_ary_push(v, fiobj_str_new(RSTRING_PTR(tmp), RSTRING_LEN(tmp)));
|
543
|
+
}
|
544
|
+
} else {
|
545
|
+
FIO_LOG_WARNING("invalid header value type, ignored.");
|
546
|
+
}
|
547
|
+
return ST_CONTINUE;
|
548
|
+
}
|
549
|
+
|
550
|
+
static int for_each_cookie(VALUE key, VALUE val, VALUE h_) {
|
551
|
+
FIOBJ h = h_;
|
552
|
+
if (RB_TYPE_P(key, T_SYMBOL))
|
553
|
+
key = rb_sym2str(key);
|
554
|
+
if (!RB_TYPE_P(key, T_STRING)) {
|
555
|
+
FIO_LOG_WARNING("invalid key type in cookie hash, ignored.");
|
556
|
+
return ST_CONTINUE;
|
557
|
+
}
|
558
|
+
if (RB_TYPE_P(val, T_SYMBOL))
|
559
|
+
val = rb_sym2str(val);
|
560
|
+
if (RB_TYPE_P(val, T_STRING)) {
|
561
|
+
FIOBJ k = fiobj_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
|
562
|
+
fiobj_hash_set(h, k, fiobj_str_new(RSTRING_PTR(val), RSTRING_LEN(val)));
|
563
|
+
fiobj_free(k);
|
564
|
+
} else {
|
565
|
+
FIO_LOG_WARNING("invalid cookie value type, ignored.");
|
566
|
+
}
|
567
|
+
return ST_CONTINUE;
|
568
|
+
}
|
569
|
+
|
570
|
+
/* cleans up any resources used by the argument list processing */
|
571
|
+
FIO_FUNC void iodine_connect_args_cleanup(iodine_connection_args_s *s) {
|
572
|
+
if (!s)
|
573
|
+
return;
|
574
|
+
fiobj_free(s->cookies);
|
575
|
+
fiobj_free(s->headers);
|
576
|
+
if (s->port.capa)
|
577
|
+
fio_free(s->port.data);
|
578
|
+
if (s->address.capa)
|
579
|
+
fio_free(s->address.data);
|
580
|
+
if (s->tls)
|
581
|
+
fio_tls_destroy(s->tls);
|
582
|
+
}
|
583
|
+
|
584
|
+
/*
|
585
|
+
Accepts:
|
586
|
+
|
587
|
+
func(settings)
|
588
|
+
|
589
|
+
Supported Settigs:
|
590
|
+
- `:url`
|
591
|
+
- `:handler` (deprecated: `app`)
|
592
|
+
- `:service` (raw / ws / wss / http / https )
|
593
|
+
- `:address`
|
594
|
+
- `:port`
|
595
|
+
- `:path` (HTTP/WebSocket client)
|
596
|
+
- `:method` (HTTP client)
|
597
|
+
- `:headers` (HTTP/WebSocket client)
|
598
|
+
- `:cookies` (HTTP/WebSocket client)
|
599
|
+
- `:body` (HTTP client)
|
600
|
+
- `:tls`
|
601
|
+
- `:log` (HTTP only)
|
602
|
+
- `:public` (public folder, HTTP server only)
|
603
|
+
- `:timeout` (HTTP only)
|
604
|
+
- `:ping` (`:raw` clients and WebSockets only)
|
605
|
+
- `:max_headers` (HTTP only)
|
606
|
+
- `:max_body` (HTTP only)
|
607
|
+
- `:max_msg` (WebSockets only)
|
608
|
+
|
609
|
+
*/
|
610
|
+
FIO_FUNC iodine_connection_args_s iodine_connect_args(VALUE s, uint8_t is_srv) {
|
611
|
+
Check_Type(s, T_HASH);
|
612
|
+
iodine_connection_args_s r = {.ping = 0}; /* set all to 0 */
|
613
|
+
/* Collect argument values */
|
614
|
+
VALUE address = rb_hash_aref(s, address_sym);
|
615
|
+
VALUE app = rb_hash_aref(s, app_sym);
|
616
|
+
VALUE body = rb_hash_aref(s, body_sym);
|
617
|
+
VALUE cookies = rb_hash_aref(s, cookies_sym);
|
618
|
+
VALUE handler = rb_hash_aref(s, handler_sym);
|
619
|
+
VALUE headers = rb_hash_aref(s, headers_sym);
|
620
|
+
VALUE log = rb_hash_aref(s, log_sym);
|
621
|
+
VALUE max_body = rb_hash_aref(s, max_body_sym);
|
622
|
+
VALUE max_clients = rb_hash_aref(s, max_clients_sym);
|
623
|
+
VALUE max_headers = rb_hash_aref(s, max_headers_sym);
|
624
|
+
VALUE max_msg = rb_hash_aref(s, max_msg_sym);
|
625
|
+
VALUE method = rb_hash_aref(s, method_sym);
|
626
|
+
VALUE path = rb_hash_aref(s, path_sym);
|
627
|
+
VALUE ping = rb_hash_aref(s, ping_sym);
|
628
|
+
VALUE port = rb_hash_aref(s, port_sym);
|
629
|
+
VALUE r_public = rb_hash_aref(s, public_sym);
|
630
|
+
VALUE service = rb_hash_aref(s, service_sym);
|
631
|
+
VALUE timeout = rb_hash_aref(s, timeout_sym);
|
632
|
+
VALUE tls = rb_hash_aref(s, tls_sym);
|
633
|
+
VALUE r_url = rb_hash_aref(s, url_sym);
|
634
|
+
fio_str_info_s service_str = {.data = NULL};
|
635
|
+
|
636
|
+
/* Complete using default values */
|
637
|
+
if (address == Qnil)
|
638
|
+
address = rb_hash_aref(iodine_default_args, address_sym);
|
639
|
+
if (app == Qnil)
|
640
|
+
app = rb_hash_aref(iodine_default_args, app_sym);
|
641
|
+
if (cookies == Qnil)
|
642
|
+
cookies = rb_hash_aref(iodine_default_args, cookies_sym);
|
643
|
+
if (handler == Qnil)
|
644
|
+
handler = rb_hash_aref(iodine_default_args, handler_sym);
|
645
|
+
if (headers == Qnil)
|
646
|
+
headers = rb_hash_aref(iodine_default_args, headers_sym);
|
647
|
+
if (log == Qnil)
|
648
|
+
log = rb_hash_aref(iodine_default_args, log_sym);
|
649
|
+
if (max_body == Qnil)
|
650
|
+
max_body = rb_hash_aref(iodine_default_args, max_body_sym);
|
651
|
+
if (max_clients == Qnil)
|
652
|
+
max_clients = rb_hash_aref(iodine_default_args, max_clients_sym);
|
653
|
+
if (max_headers == Qnil)
|
654
|
+
max_headers = rb_hash_aref(iodine_default_args, max_headers_sym);
|
655
|
+
if (max_msg == Qnil)
|
656
|
+
max_msg = rb_hash_aref(iodine_default_args, max_msg_sym);
|
657
|
+
if (method == Qnil)
|
658
|
+
method = rb_hash_aref(iodine_default_args, method_sym);
|
659
|
+
if (path == Qnil)
|
660
|
+
path = rb_hash_aref(iodine_default_args, path_sym);
|
661
|
+
if (ping == Qnil)
|
662
|
+
ping = rb_hash_aref(iodine_default_args, ping_sym);
|
663
|
+
if (port == Qnil)
|
664
|
+
port = rb_hash_aref(iodine_default_args, port_sym);
|
665
|
+
if (r_public == Qnil) {
|
666
|
+
r_public = rb_hash_aref(iodine_default_args, public_sym);
|
667
|
+
}
|
668
|
+
// if (service == Qnil) // not supported by default settings...
|
669
|
+
// service = rb_hash_aref(iodine_default_args, service_sym);
|
670
|
+
if (timeout == Qnil)
|
671
|
+
timeout = rb_hash_aref(iodine_default_args, timeout_sym);
|
672
|
+
if (tls == Qnil)
|
673
|
+
tls = rb_hash_aref(iodine_default_args, tls_sym);
|
674
|
+
|
675
|
+
/* TODO: deprecation */
|
676
|
+
if (handler == Qnil) {
|
677
|
+
handler = rb_hash_aref(s, app_sym);
|
678
|
+
if (handler != Qnil)
|
679
|
+
FIO_LOG_WARNING(":app is deprecated in Iodine.listen and Iodine.connect. "
|
680
|
+
"Use :handler");
|
681
|
+
}
|
682
|
+
|
683
|
+
/* specific for HTTP */
|
684
|
+
if (is_srv && handler == Qnil && rb_block_given_p()) {
|
685
|
+
handler = rb_block_proc();
|
686
|
+
}
|
687
|
+
|
688
|
+
/* Raise exceptions on errors (last chance) */
|
689
|
+
if (handler == Qnil) {
|
690
|
+
rb_raise(rb_eArgError, "a :handler is required.");
|
691
|
+
}
|
692
|
+
|
693
|
+
/* Set existing values */
|
694
|
+
if (handler != Qnil) {
|
695
|
+
r.handler = handler;
|
696
|
+
}
|
697
|
+
if (address != Qnil && RB_TYPE_P(address, T_STRING)) {
|
698
|
+
r.address = IODINE_RSTRINFO(address);
|
699
|
+
}
|
700
|
+
if (body != Qnil && RB_TYPE_P(body, T_STRING)) {
|
701
|
+
r.body = IODINE_RSTRINFO(body);
|
702
|
+
}
|
703
|
+
if (cookies != Qnil && RB_TYPE_P(cookies, T_HASH)) {
|
704
|
+
r.cookies = fiobj_hash_new2(rb_hash_size(cookies));
|
705
|
+
rb_hash_foreach(cookies, for_each_cookie, r.cookies);
|
706
|
+
}
|
707
|
+
if (headers != Qnil && RB_TYPE_P(headers, T_HASH)) {
|
708
|
+
r.headers = fiobj_hash_new2(rb_hash_size(headers));
|
709
|
+
rb_hash_foreach(headers, for_each_header_value, r.headers);
|
710
|
+
}
|
711
|
+
if (log != Qnil && log != Qfalse) {
|
712
|
+
r.log = 1;
|
713
|
+
}
|
714
|
+
if (max_body != Qnil && RB_TYPE_P(max_body, T_FIXNUM)) {
|
715
|
+
r.max_body = FIX2ULONG(max_body) * 1024 * 1024;
|
716
|
+
}
|
717
|
+
if (max_clients != Qnil && RB_TYPE_P(max_clients, T_FIXNUM)) {
|
718
|
+
r.max_clients = FIX2ULONG(max_clients);
|
719
|
+
}
|
720
|
+
if (max_headers != Qnil && RB_TYPE_P(max_headers, T_FIXNUM)) {
|
721
|
+
r.max_headers = FIX2ULONG(max_headers) * 1024;
|
722
|
+
}
|
723
|
+
if (max_msg != Qnil && RB_TYPE_P(max_msg, T_FIXNUM)) {
|
724
|
+
r.max_msg = FIX2ULONG(max_msg) * 1024;
|
725
|
+
}
|
726
|
+
if (method != Qnil && RB_TYPE_P(method, T_STRING)) {
|
727
|
+
r.method = IODINE_RSTRINFO(method);
|
728
|
+
}
|
729
|
+
if (path != Qnil && RB_TYPE_P(path, T_STRING)) {
|
730
|
+
r.path = IODINE_RSTRINFO(path);
|
731
|
+
}
|
732
|
+
if (ping != Qnil && RB_TYPE_P(ping, T_FIXNUM)) {
|
733
|
+
if (FIX2ULONG(ping) > 255)
|
734
|
+
FIO_LOG_WARNING(":ping value over 255 will be silently ignored.");
|
735
|
+
else
|
736
|
+
r.ping = FIX2ULONG(ping);
|
737
|
+
}
|
738
|
+
if (port != Qnil) {
|
739
|
+
if (RB_TYPE_P(port, T_STRING)) {
|
740
|
+
char *tmp = RSTRING_PTR(port);
|
741
|
+
if (fio_atol(&tmp))
|
742
|
+
r.port = IODINE_RSTRINFO(port);
|
743
|
+
} else if (RB_TYPE_P(port, T_FIXNUM) && FIX2UINT(port)) {
|
744
|
+
if (FIX2UINT(port) >= 65536) {
|
745
|
+
FIO_LOG_WARNING("Port number %u is too high, quietly ignored.",
|
746
|
+
FIX2UINT(port));
|
747
|
+
} else {
|
748
|
+
r.port = (fio_str_info_s){.data = fio_malloc(16), .len = 0, .capa = 1};
|
749
|
+
r.port.len = fio_ltoa(r.port.data, FIX2INT(port), 10);
|
750
|
+
r.port.data[r.port.len] = 0;
|
751
|
+
}
|
752
|
+
}
|
753
|
+
}
|
754
|
+
|
755
|
+
if (r_public != Qnil && RB_TYPE_P(r_public, T_STRING)) {
|
756
|
+
r.public = IODINE_RSTRINFO(r_public);
|
757
|
+
}
|
758
|
+
if (service != Qnil && RB_TYPE_P(service, T_STRING)) {
|
759
|
+
service_str = IODINE_RSTRINFO(service);
|
760
|
+
} else if (service != Qnil && RB_TYPE_P(service, T_SYMBOL)) {
|
761
|
+
service = rb_sym2str(service);
|
762
|
+
service_str = IODINE_RSTRINFO(service);
|
763
|
+
}
|
764
|
+
if (timeout != Qnil && RB_TYPE_P(ping, T_FIXNUM)) {
|
765
|
+
if (FIX2ULONG(timeout) > 255)
|
766
|
+
FIO_LOG_WARNING(":timeout value over 255 will be silently ignored.");
|
767
|
+
else
|
768
|
+
r.timeout = FIX2ULONG(timeout);
|
769
|
+
}
|
770
|
+
if (tls != Qnil) {
|
771
|
+
r.tls = iodine_tls2c(tls);
|
772
|
+
if (r.tls)
|
773
|
+
fio_tls_dup(r.tls);
|
774
|
+
}
|
775
|
+
/* URL parsing */
|
776
|
+
if (r_url != Qnil && RB_TYPE_P(r_url, T_STRING)) {
|
777
|
+
r.url = IODINE_RSTRINFO(r_url);
|
778
|
+
fio_url_s u = fio_url_parse(r.url.data, r.url.len);
|
779
|
+
/* set service string */
|
780
|
+
if (u.scheme.data) {
|
781
|
+
service_str = u.scheme;
|
782
|
+
}
|
783
|
+
/* copy port number */
|
784
|
+
if (u.port.data) {
|
785
|
+
char *tmp = u.port.data;
|
786
|
+
if (fio_atol(&tmp) == 0) {
|
787
|
+
if (r.port.capa)
|
788
|
+
fio_free(r.port.data);
|
789
|
+
r.port = (fio_str_info_s){.data = NULL};
|
790
|
+
} else {
|
791
|
+
if (u.port.len > 5)
|
792
|
+
FIO_LOG_WARNING("Port number error (%.*s too long to be valid).",
|
793
|
+
(int)u.port.len, u.port.data);
|
794
|
+
if (r.port.capa && u.port.len >= 16) {
|
795
|
+
fio_free(r.port.data);
|
796
|
+
r.port = (fio_str_info_s){.data = NULL};
|
797
|
+
}
|
798
|
+
if (!r.port.capa)
|
799
|
+
r.port = (fio_str_info_s){
|
800
|
+
.data = fio_malloc(u.port.len + 1), .len = u.port.len, .capa = 1};
|
801
|
+
memcpy(r.port.data, u.port.data, u.port.len);
|
802
|
+
r.port.len = u.port.len;
|
803
|
+
r.port.data[r.port.len] = 0;
|
804
|
+
}
|
805
|
+
} else {
|
806
|
+
if (r.port.capa)
|
807
|
+
fio_free(r.port.data);
|
808
|
+
r.port = (fio_str_info_s){.data = NULL};
|
809
|
+
}
|
810
|
+
/* copy host / address */
|
811
|
+
if (u.host.data) {
|
812
|
+
r.address = (fio_str_info_s){
|
813
|
+
.data = fio_malloc(u.host.len + 1), .len = u.host.len, .capa = 1};
|
814
|
+
memcpy(r.address.data, u.host.data, u.host.len);
|
815
|
+
r.address.len = u.host.len;
|
816
|
+
r.address.data[r.address.len] = 0;
|
817
|
+
} else {
|
818
|
+
if (r.address.capa)
|
819
|
+
fio_free(r.address.data);
|
820
|
+
r.address = (fio_str_info_s){.data = NULL};
|
821
|
+
}
|
822
|
+
/* set path */
|
823
|
+
if (u.path.data) {
|
824
|
+
/* support possible Unix address as "raw://:0/my/sock.sock" */
|
825
|
+
if (r.address.data || r.port.data)
|
826
|
+
r.path = u.path;
|
827
|
+
else
|
828
|
+
r.address = u.path;
|
829
|
+
}
|
830
|
+
}
|
831
|
+
/* test/set service type */
|
832
|
+
r.service = IODINE_SERVICE_RAW;
|
833
|
+
if (service_str.data) {
|
834
|
+
switch (service_str.data[0]) {
|
835
|
+
case 't': /* overflow */
|
836
|
+
/* tcp or tls */
|
837
|
+
if (service_str.data[1] == 'l' && !r.tls) {
|
838
|
+
char *local = NULL;
|
839
|
+
char buf[1024];
|
840
|
+
buf[1023] = 0;
|
841
|
+
if (is_srv) {
|
842
|
+
local = buf;
|
843
|
+
if (fio_local_addr(buf, 1023) >= 1022)
|
844
|
+
local = NULL;
|
845
|
+
}
|
846
|
+
r.tls = fio_tls_new(local, NULL, NULL, NULL);
|
847
|
+
}
|
848
|
+
/* overflow */
|
849
|
+
case 'u': /* overflow */
|
850
|
+
/* unix */
|
851
|
+
case 'r':
|
852
|
+
/* raw */
|
853
|
+
r.service = IODINE_SERVICE_RAW;
|
854
|
+
break;
|
855
|
+
case 'h':
|
856
|
+
/* http(s) */
|
857
|
+
r.service = IODINE_SERVICE_HTTP;
|
858
|
+
if (service_str.len == 5 && !r.tls) {
|
859
|
+
char *local = NULL;
|
860
|
+
char buf[1024];
|
861
|
+
buf[1023] = 0;
|
862
|
+
if (is_srv) {
|
863
|
+
local = buf;
|
864
|
+
if (fio_local_addr(buf, 1023) >= 1022)
|
865
|
+
local = NULL;
|
866
|
+
}
|
867
|
+
r.tls = fio_tls_new(local, NULL, NULL, NULL);
|
868
|
+
}
|
869
|
+
case 'w':
|
870
|
+
/* ws(s) */
|
871
|
+
r.service = IODINE_SERVICE_WS;
|
872
|
+
if (service_str.len == 3 && !r.tls) {
|
873
|
+
char *local = NULL;
|
874
|
+
char buf[1024];
|
875
|
+
buf[1023] = 0;
|
876
|
+
if (is_srv) {
|
877
|
+
local = buf;
|
878
|
+
if (fio_local_addr(buf, 1023) >= 1022)
|
879
|
+
local = NULL;
|
880
|
+
}
|
881
|
+
r.tls = fio_tls_new(local, NULL, NULL, NULL);
|
882
|
+
}
|
883
|
+
break;
|
884
|
+
}
|
885
|
+
}
|
886
|
+
return r;
|
887
|
+
}
|
888
|
+
|
889
|
+
/* *****************************************************************************
|
890
|
+
Listen function routing
|
891
|
+
***************************************************************************** */
|
892
|
+
|
893
|
+
// clang-format off
|
894
|
+
/*
|
895
|
+
{Iodine.listen} can be used to listen to any incoming connections, including HTTP and raw (tcp/ip and unix sockets) connections.
|
896
|
+
|
897
|
+
Iodine.listen(settings)
|
898
|
+
|
899
|
+
Supported Settigs:
|
900
|
+
|
901
|
+
| | |
|
902
|
+
|---|---|
|
903
|
+
| `:url` | URL indicating service type, host name and port. Path will be parsed as a Unix socket. |
|
904
|
+
| `:handler` | (deprecated: `:app`) see details below. |
|
905
|
+
| `:address` | an IP address or a unix socket address. Only relevant if `:url` is missing. |
|
906
|
+
| `:log` | (HTTP only) request logging. |
|
907
|
+
| `:max_body` | (HTTP only) maximum upload size allowed per request before disconnection (in Mb). |
|
908
|
+
| `:max_headers` | (HTTP only) maximum total header length allowed per request (in Kb). |
|
909
|
+
| `:max_msg` | (WebSockets only) maximum message size pre message (in Kb). |
|
910
|
+
| `:ping` | (`:raw` clients and WebSockets only) ping interval (in seconds). Up to 255 seconds. |
|
911
|
+
| `:port` | port number to listen to either a String or Number) |
|
912
|
+
| `:public` | (HTTP server only) public folder for static file service. |
|
913
|
+
| `:service` | (`:raw` / `:tls` / `:ws` / `:wss` / `:http` / `:https` ) a supported service this socket will listen to. |
|
914
|
+
| `:timeout` | (HTTP only) keep-alive timeout in seconds. Up to 255 seconds. |
|
915
|
+
| `:tls` | an {Iodine::TLS} context object for encrypted connections. |
|
916
|
+
|
917
|
+
Some connection settings are only valid when listening to HTTP / WebSocket connections.
|
918
|
+
|
919
|
+
If `:url` is provided, it will overwrite the `:address` and `:port` settings (if provided).
|
920
|
+
|
921
|
+
For HTTP connections, the `:handler` **must** be a valid Rack application object (answers `.call(env)`).
|
922
|
+
|
923
|
+
Here's an example for an HTTP hello world application:
|
924
|
+
|
925
|
+
require 'iodine'
|
926
|
+
# a handler can be a block
|
927
|
+
Iodine.listen(service: :http, port: "3000") {|env| [200, {"Content-Length" => "12"}, ["Hello World!"]] }
|
928
|
+
# start the service
|
929
|
+
Iodine.threads = 1
|
930
|
+
Iodine.start
|
931
|
+
|
932
|
+
|
933
|
+
Here's another example, using a Unix Socket instead of a TCP/IP socket for an HTTP hello world application.
|
934
|
+
|
935
|
+
This example shows how the `:url` option can be used, but the `:address` settings could have been used for the same effect (with `port: 0`).
|
936
|
+
|
937
|
+
require 'iodine'
|
938
|
+
# note that unix sockets in URL form use an absolute path.
|
939
|
+
Iodine.listen(url: "http://:0/tmp/sock.sock") {|env| [200, {"Content-Length" => "12"}, ["Hello World!"]] }
|
940
|
+
# start the service
|
941
|
+
Iodine.threads = 1
|
942
|
+
Iodine.start
|
943
|
+
|
944
|
+
|
945
|
+
For raw connections, the `:handler` object should be an object that answer `.call` and returns a valid callback object that supports the following callbacks (see also {Iodine::Connection}):
|
946
|
+
|
947
|
+
| | |
|
948
|
+
|---|---|
|
949
|
+
| `on_open(client)` | called after a connection was established |
|
950
|
+
| `on_message(client,data)` | called when incoming data is available. Data may be fragmented. |
|
951
|
+
| `on_drained(client)` | called after pending `client.write` events have been processed (see {Iodine::Connection#pending}). |
|
952
|
+
| `ping(client)` | called whenever a timeout has occured (see {Iodine::Connection#timeout=}). |
|
953
|
+
| `on_shutdown(client)` | called if the server is shutting down. This is called before the connection is closed. |
|
954
|
+
| `on_close(client)` | called when the connection with the client was closed. |
|
955
|
+
|
956
|
+
The `client` argument passed to the `:handler` callbacks is an {Iodine::Connection} instance that represents the connection / the client.
|
957
|
+
|
958
|
+
Here's an example for a telnet based chat-room example:
|
959
|
+
|
960
|
+
require 'iodine'
|
961
|
+
# define the protocol for our service
|
962
|
+
module ChatHandler
|
963
|
+
def self.on_open(client)
|
964
|
+
# Set a connection timeout
|
965
|
+
client.timeout = 10
|
966
|
+
# subscribe to the chat channel.
|
967
|
+
client.subscribe :chat
|
968
|
+
# Write a welcome message
|
969
|
+
client.publish :chat, "new member entered the chat\r\n"
|
970
|
+
end
|
971
|
+
# this is called for incoming data - note data might be fragmented.
|
972
|
+
def self.on_message(client, data)
|
973
|
+
# publish the data we received
|
974
|
+
client.publish :chat, data
|
975
|
+
# close the connection when the time comes
|
976
|
+
client.close if data =~ /^bye[\n\r]/
|
977
|
+
end
|
978
|
+
# called whenever timeout occurs.
|
979
|
+
def self.ping(client)
|
980
|
+
client.write "System: quite, isn't it...?\r\n"
|
981
|
+
end
|
982
|
+
# called if the connection is still open and the server is shutting down.
|
983
|
+
def self.on_shutdown(client)
|
984
|
+
# write the data we received
|
985
|
+
client.write "Chat server going away. Try again later.\r\n"
|
986
|
+
end
|
987
|
+
# returns the callback object (self).
|
988
|
+
def self.call
|
989
|
+
self
|
990
|
+
end
|
991
|
+
end
|
992
|
+
# we use can both the `handler` keyword or a block, anything that answers #call.
|
993
|
+
Iodine.listen(service: :raw, port: "3000", handler: ChatHandler)
|
994
|
+
# we can listen to more than a single socket at a time.
|
995
|
+
Iodine.listen(url: "raw://:3030", handler: ChatHandler)
|
996
|
+
# start the service
|
997
|
+
Iodine.threads = 1
|
998
|
+
Iodine.start
|
999
|
+
|
1000
|
+
|
1001
|
+
|
1002
|
+
Returns the handler object used.
|
1003
|
+
*/
|
1004
|
+
static VALUE iodine_listen(VALUE self, VALUE args) {
|
1005
|
+
// clang-format on
|
1006
|
+
iodine_connection_args_s s = iodine_connect_args(args, 1);
|
1007
|
+
intptr_t uuid = -1;
|
1008
|
+
switch (s.service) {
|
1009
|
+
case IODINE_SERVICE_RAW:
|
1010
|
+
uuid = iodine_tcp_listen(s);
|
1011
|
+
break;
|
1012
|
+
case IODINE_SERVICE_HTTP: /* overflow */
|
1013
|
+
case IODINE_SERVICE_WS:
|
1014
|
+
uuid = iodine_http_listen(s);
|
1015
|
+
break;
|
1016
|
+
}
|
1017
|
+
iodine_connect_args_cleanup(&s);
|
1018
|
+
if (uuid == -1)
|
1019
|
+
rb_raise(rb_eRuntimeError, "Couldn't open listening socket.");
|
1020
|
+
return s.handler;
|
1021
|
+
(void)self;
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
/* *****************************************************************************
|
1025
|
+
Connect function routing
|
1026
|
+
***************************************************************************** */
|
1027
|
+
|
1028
|
+
// clang-format off
|
1029
|
+
/*
|
1030
|
+
|
1031
|
+
The {connect} method instructs iodine to connect to a server using either TCP/IP or Unix sockets.
|
1032
|
+
|
1033
|
+
Iodine.connect(settings)
|
1034
|
+
|
1035
|
+
Supported Settigs:
|
1036
|
+
|
1037
|
+
|
1038
|
+
| | |
|
1039
|
+
|---|---|
|
1040
|
+
| `:url` | URL indicating service type, host name, port and optional path. |
|
1041
|
+
| `:handler` | see details below. |
|
1042
|
+
| `:address` | an IP address or a unix socket address. Only relevant if `:url` is missing. |
|
1043
|
+
| `:body` | (HTTP client) the body to be sent. |
|
1044
|
+
| `:cookies` | (HTTP/WebSocket client) cookie data. |
|
1045
|
+
| `:headers` | (HTTP/WebSocket client) custom headers. |
|
1046
|
+
| `:log` | (HTTP only) - logging the requests. |
|
1047
|
+
| `:max_body` | (HTTP only) - limits HTTP body in the response, see {listen}. |
|
1048
|
+
| `:max_headers` | (HTTP only) - limits the header length in the response, see {listen}. |
|
1049
|
+
| `:max_msg` | (WebSockets only) maximum incoming message size pre message (in Kb). |
|
1050
|
+
| `:method` | (HTTP client) a String such as "GET" or "POST". |
|
1051
|
+
| `:path` |HTTP/WebSocket client) the HTTP path to be used. |
|
1052
|
+
| `:ping` | ping interval (in seconds). Up to 255 seconds. |
|
1053
|
+
| `:port` | port number to listen to either a String or Number) |
|
1054
|
+
| `:public` | (public folder, HTTP server only) |
|
1055
|
+
| `:service` | (`:raw` / `:tls` / `:ws` / `:wss` ) |
|
1056
|
+
| `:timeout` | (HTTP only) keep-alive timeout in seconds. Up to 255 seconds. |
|
1057
|
+
| `:tls` | an {Iodine::TLS} context object for encrypted connections. |
|
1058
|
+
|
1059
|
+
Some connection settings are only valid for HTTP / WebSocket connections.
|
1060
|
+
|
1061
|
+
If `:url` is provided, it will overwrite the `:address`, `:port` and `:path` settings (if provided).
|
1062
|
+
|
1063
|
+
Unlike {Iodine.listen}, a block can't be used and a `:handler` object **must** be provided.
|
1064
|
+
|
1065
|
+
If the connection fails, only the `on_close` callback will be called (with a `nil` client).
|
1066
|
+
|
1067
|
+
Here's an example TCP/IP client that sends a simple HTTP GET request:
|
1068
|
+
|
1069
|
+
# use a secure connection?
|
1070
|
+
USE_TLS = false
|
1071
|
+
|
1072
|
+
# remote server details
|
1073
|
+
$port = USE_TLS ? 443 : 80
|
1074
|
+
$address = "google.com"
|
1075
|
+
|
1076
|
+
|
1077
|
+
# require iodine
|
1078
|
+
require 'iodine'
|
1079
|
+
|
1080
|
+
# Iodine runtime settings
|
1081
|
+
Iodine.threads = 1
|
1082
|
+
Iodine.workers = 1
|
1083
|
+
Iodine.verbosity = 3 # warnings only
|
1084
|
+
|
1085
|
+
|
1086
|
+
# a client callback handler
|
1087
|
+
module Client
|
1088
|
+
|
1089
|
+
def self.on_open(connection)
|
1090
|
+
# Set a connection timeout
|
1091
|
+
connection.timeout = 10
|
1092
|
+
# subscribe to the chat channel.
|
1093
|
+
puts "* Sending request..."
|
1094
|
+
connection.write "GET / HTTP/1.1\r\nHost: #{$address}\r\n\r\n"
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
def self.on_message(connection, data)
|
1098
|
+
# publish the data we received
|
1099
|
+
STDOUT.write data
|
1100
|
+
# close the connection after a second... we're not really parsing anything, so it's a guess.
|
1101
|
+
Iodine.run_after(1000) { connection.close }
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
def self.on_close(connection)
|
1105
|
+
# stop iodine
|
1106
|
+
Iodine.stop
|
1107
|
+
puts "Done."
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
# returns the callback object (self).
|
1111
|
+
def self.call
|
1112
|
+
self
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
|
1117
|
+
|
1118
|
+
if(USE_TLS)
|
1119
|
+
tls = Iodine::TLS.new
|
1120
|
+
# ALPN blocks should return a valid calback object
|
1121
|
+
tls.on_protocol("http/1.1") { Client }
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
Iodine.connect(address: $address, port: $port, handler: Client, tls: tls)
|
1125
|
+
|
1126
|
+
# start the iodine reactor
|
1127
|
+
Iodine.start
|
1128
|
+
|
1129
|
+
Iodine also supports WebSocket client connections, using either the `url` property or the `ws` and `wss` service names.
|
1130
|
+
|
1131
|
+
The following example establishes a secure (TLS) connects to the WebSocket echo testing server at `wss://echo.websocket.org`:
|
1132
|
+
|
1133
|
+
# require iodine
|
1134
|
+
require 'iodine'
|
1135
|
+
|
1136
|
+
# The client class
|
1137
|
+
class EchoClient
|
1138
|
+
|
1139
|
+
def on_open(connection)
|
1140
|
+
@messages = [ "Hello World!",
|
1141
|
+
"I'm alive and sending messages",
|
1142
|
+
"I also receive messages",
|
1143
|
+
"now that we all know this...",
|
1144
|
+
"I can stop.",
|
1145
|
+
"Goodbye." ]
|
1146
|
+
send_one_message(connection)
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def on_message(connection, message)
|
1150
|
+
puts "Received: #{message}"
|
1151
|
+
send_one_message(connection)
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
def on_close(connection)
|
1155
|
+
# in this example, we stop iodine once the client is closed
|
1156
|
+
puts "* Client closed."
|
1157
|
+
Iodine.stop
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
# We use this method to pop messages from the queue and send them
|
1161
|
+
#
|
1162
|
+
# When the queue is empty, we disconnect the client.
|
1163
|
+
def send_one_message(connection)
|
1164
|
+
msg = @messages.shift
|
1165
|
+
if(msg)
|
1166
|
+
connection.write msg
|
1167
|
+
else
|
1168
|
+
connection.close
|
1169
|
+
end
|
1170
|
+
end
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
Iodine.threads = 1
|
1174
|
+
Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 40
|
1175
|
+
Iodine.start
|
1176
|
+
|
1177
|
+
**Note**: the `on_close` callback is always called, even if a connection couldn't be established.
|
1178
|
+
|
1179
|
+
Returns the handler object used.
|
1180
|
+
*/
|
1181
|
+
static VALUE iodine_connect(VALUE self, VALUE args) {
|
1182
|
+
// clang-format on
|
1183
|
+
iodine_connection_args_s s = iodine_connect_args(args, 0);
|
1184
|
+
intptr_t uuid = -1;
|
1185
|
+
switch (s.service) {
|
1186
|
+
case IODINE_SERVICE_RAW:
|
1187
|
+
uuid = iodine_tcp_connect(s);
|
1188
|
+
break;
|
1189
|
+
case IODINE_SERVICE_HTTP:
|
1190
|
+
iodine_connect_args_cleanup(&s);
|
1191
|
+
rb_raise(rb_eRuntimeError, "HTTP client connections aren't supported yet.");
|
1192
|
+
return Qnil;
|
1193
|
+
break;
|
1194
|
+
case IODINE_SERVICE_WS:
|
1195
|
+
uuid = iodine_ws_connect(s);
|
1196
|
+
break;
|
1197
|
+
}
|
1198
|
+
iodine_connect_args_cleanup(&s);
|
1199
|
+
if (uuid == -1)
|
1200
|
+
rb_raise(rb_eRuntimeError, "Couldn't open client socket.");
|
1201
|
+
return self;
|
1202
|
+
}
|
1203
|
+
|
462
1204
|
/* *****************************************************************************
|
463
1205
|
Ruby loads the library and invokes the Init_<lib_name> function...
|
464
1206
|
|
@@ -466,6 +1208,33 @@ Here we connect all the C code to the Ruby interface, completing the bridge
|
|
466
1208
|
between Lib-Server and Ruby.
|
467
1209
|
***************************************************************************** */
|
468
1210
|
void Init_iodine(void) {
|
1211
|
+
/* common Symbol objects in use by Iodine */
|
1212
|
+
#define IODINE_MAKE_SYM(name) \
|
1213
|
+
do { \
|
1214
|
+
name##_sym = rb_id2sym(rb_intern(#name)); \
|
1215
|
+
rb_global_variable(&name##_sym); \
|
1216
|
+
} while (0)
|
1217
|
+
IODINE_MAKE_SYM(address);
|
1218
|
+
IODINE_MAKE_SYM(app);
|
1219
|
+
IODINE_MAKE_SYM(body);
|
1220
|
+
IODINE_MAKE_SYM(cookies);
|
1221
|
+
IODINE_MAKE_SYM(handler);
|
1222
|
+
IODINE_MAKE_SYM(headers);
|
1223
|
+
IODINE_MAKE_SYM(log);
|
1224
|
+
IODINE_MAKE_SYM(max_body);
|
1225
|
+
IODINE_MAKE_SYM(max_clients);
|
1226
|
+
IODINE_MAKE_SYM(max_headers);
|
1227
|
+
IODINE_MAKE_SYM(max_msg);
|
1228
|
+
IODINE_MAKE_SYM(method);
|
1229
|
+
IODINE_MAKE_SYM(path);
|
1230
|
+
IODINE_MAKE_SYM(ping);
|
1231
|
+
IODINE_MAKE_SYM(port);
|
1232
|
+
IODINE_MAKE_SYM(public);
|
1233
|
+
IODINE_MAKE_SYM(service);
|
1234
|
+
IODINE_MAKE_SYM(timeout);
|
1235
|
+
IODINE_MAKE_SYM(tls);
|
1236
|
+
IODINE_MAKE_SYM(url);
|
1237
|
+
|
469
1238
|
// load any environment specific patches
|
470
1239
|
patch_env();
|
471
1240
|
|
@@ -476,7 +1245,7 @@ void Init_iodine(void) {
|
|
476
1245
|
IodineModule = rb_define_module("Iodine");
|
477
1246
|
IodineBaseModule = rb_define_module_under(IodineModule, "Base");
|
478
1247
|
VALUE IodineCLIModule = rb_define_module_under(IodineBaseModule, "CLI");
|
479
|
-
|
1248
|
+
iodine_call_id = rb_intern2("call", 4);
|
480
1249
|
|
481
1250
|
// register core methods
|
482
1251
|
rb_define_module_function(IodineModule, "threads", iodine_threads_get, 0);
|
@@ -490,10 +1259,22 @@ void Init_iodine(void) {
|
|
490
1259
|
rb_define_module_function(IodineModule, "on_idle", iodine_sched_on_idle, 0);
|
491
1260
|
rb_define_module_function(IodineModule, "master?", iodine_master_is, 0);
|
492
1261
|
rb_define_module_function(IodineModule, "worker?", iodine_worker_is, 0);
|
1262
|
+
rb_define_module_function(IodineModule, "listen", iodine_listen, 1);
|
1263
|
+
rb_define_module_function(IodineModule, "connect", iodine_connect, 1);
|
493
1264
|
|
494
1265
|
// register CLI methods
|
495
1266
|
rb_define_module_function(IodineCLIModule, "parse", iodine_cli_parse, 0);
|
496
1267
|
|
1268
|
+
/** Default connection settings for {listen} and {connect}. */
|
1269
|
+
iodine_default_args = rb_hash_new();
|
1270
|
+
/** Default connection settings for {listen} and {connect}. */
|
1271
|
+
rb_const_set(IodineModule, rb_intern("DEFAULT_SETTINGS"),
|
1272
|
+
iodine_default_args);
|
1273
|
+
|
1274
|
+
/** Depracated, use {Iodine::DEFAULT_SETTINGS}. */
|
1275
|
+
rb_const_set(IodineModule, rb_intern("DEFAULT_HTTP_ARGS"),
|
1276
|
+
iodine_default_args);
|
1277
|
+
|
497
1278
|
// initialize Object storage for GC protection
|
498
1279
|
iodine_storage_init();
|
499
1280
|
|
@@ -509,6 +1290,9 @@ void Init_iodine(void) {
|
|
509
1290
|
// initialize the HTTP module
|
510
1291
|
iodine_init_http();
|
511
1292
|
|
1293
|
+
// initialize SSL/TLS support module
|
1294
|
+
iodine_init_tls();
|
1295
|
+
|
512
1296
|
// initialize JSON helpers
|
513
1297
|
iodine_init_json();
|
514
1298
|
|