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.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -4
  3. data/.yardopts +8 -0
  4. data/CHANGELOG.md +26 -0
  5. data/LICENSE.txt +1 -1
  6. data/LIMITS.md +6 -0
  7. data/README.md +93 -13
  8. data/{SPEC-Websocket-Draft.md → SPEC-WebSocket-Draft.md} +0 -0
  9. data/examples/tcp_client.rb +66 -0
  10. data/examples/x-sendfile.ru +14 -0
  11. data/exe/iodine +3 -3
  12. data/ext/iodine/extconf.rb +21 -0
  13. data/ext/iodine/fio.c +659 -69
  14. data/ext/iodine/fio.h +350 -95
  15. data/ext/iodine/fio_cli.c +4 -3
  16. data/ext/iodine/fio_json_parser.h +1 -1
  17. data/ext/iodine/fio_siphash.c +13 -11
  18. data/ext/iodine/fio_siphash.h +6 -3
  19. data/ext/iodine/fio_tls.h +129 -0
  20. data/ext/iodine/fio_tls_missing.c +634 -0
  21. data/ext/iodine/fio_tls_openssl.c +1011 -0
  22. data/ext/iodine/fio_tmpfile.h +1 -1
  23. data/ext/iodine/fiobj.h +1 -1
  24. data/ext/iodine/fiobj_ary.c +1 -1
  25. data/ext/iodine/fiobj_ary.h +1 -1
  26. data/ext/iodine/fiobj_data.c +1 -1
  27. data/ext/iodine/fiobj_data.h +1 -1
  28. data/ext/iodine/fiobj_hash.c +1 -1
  29. data/ext/iodine/fiobj_hash.h +1 -1
  30. data/ext/iodine/fiobj_json.c +18 -16
  31. data/ext/iodine/fiobj_json.h +1 -1
  32. data/ext/iodine/fiobj_mustache.c +4 -0
  33. data/ext/iodine/fiobj_mustache.h +4 -0
  34. data/ext/iodine/fiobj_numbers.c +1 -1
  35. data/ext/iodine/fiobj_numbers.h +1 -1
  36. data/ext/iodine/fiobj_str.c +3 -3
  37. data/ext/iodine/fiobj_str.h +1 -1
  38. data/ext/iodine/fiobject.c +1 -1
  39. data/ext/iodine/fiobject.h +8 -2
  40. data/ext/iodine/http.c +128 -337
  41. data/ext/iodine/http.h +11 -18
  42. data/ext/iodine/http1.c +6 -6
  43. data/ext/iodine/http1.h +1 -1
  44. data/ext/iodine/http1_parser.c +1 -1
  45. data/ext/iodine/http1_parser.h +1 -1
  46. data/ext/iodine/http_internal.c +10 -8
  47. data/ext/iodine/http_internal.h +13 -3
  48. data/ext/iodine/http_mime_parser.h +1 -1
  49. data/ext/iodine/iodine.c +806 -22
  50. data/ext/iodine/iodine.h +33 -0
  51. data/ext/iodine/iodine_connection.c +23 -18
  52. data/ext/iodine/iodine_http.c +239 -225
  53. data/ext/iodine/iodine_http.h +4 -1
  54. data/ext/iodine/iodine_mustache.c +59 -54
  55. data/ext/iodine/iodine_pubsub.c +1 -1
  56. data/ext/iodine/iodine_tcp.c +34 -100
  57. data/ext/iodine/iodine_tcp.h +4 -0
  58. data/ext/iodine/iodine_tls.c +267 -0
  59. data/ext/iodine/iodine_tls.h +13 -0
  60. data/ext/iodine/mustache_parser.h +1 -1
  61. data/ext/iodine/redis_engine.c +14 -6
  62. data/ext/iodine/redis_engine.h +1 -1
  63. data/ext/iodine/resp_parser.h +1 -1
  64. data/ext/iodine/websocket_parser.h +1 -1
  65. data/ext/iodine/websockets.c +1 -1
  66. data/ext/iodine/websockets.h +1 -1
  67. data/iodine.gemspec +2 -1
  68. data/lib/iodine.rb +19 -5
  69. data/lib/iodine/connection.rb +13 -0
  70. data/lib/iodine/mustache.rb +7 -24
  71. data/lib/iodine/tls.rb +16 -0
  72. data/lib/iodine/version.rb +1 -1
  73. data/lib/rack/handler/iodine.rb +1 -1
  74. metadata +15 -5
@@ -1,6 +1,6 @@
1
1
  #ifndef H_HTTP_H
2
2
  /*
3
- Copyright: Boaz Segev, 2016-2018
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 *address, struct http_settings_s);
451
- #define http_connect(address, ...) \
452
- http_connect((address), (struct http_settings_s){__VA_ARGS__})
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 *address, websocket_settings_s settings);
590
- #define websocket_connect(address, ...) \
591
- websocket_connect((address), (websocket_settings_s){__VA_ARGS__})
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 struct {
953
- fio_str_info_s scheme;
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
- http_url_s http_url_parse(const char *url, size_t length);
987
+ #define http_url_parse(url, len) fio_url_parse((url), (len))
995
988
 
996
989
  #if DEBUG
997
990
  void http_tests(void);
@@ -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 = fio_siphash("connection", 10);
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 = fio_siphash("host", 4);
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, settings->udata);
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 = fio_siphash("sec-websocket-version", 21);
329
+ sec_version = fiobj_hash_string("sec-websocket-version", 21);
330
330
  if (!sec_key)
331
- sec_key = fio_siphash("sec-websocket-key", 17);
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)
@@ -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
  #ifndef H_HTTP1_H
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright: Boaz Segev, 2017-2018
2
+ Copyright: Boaz Segev, 2017-2019
3
3
  License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
@@ -1,6 +1,6 @@
1
1
  #ifndef H_HTTP1_PARSER_H
2
2
  /*
3
- Copyright: Boaz Segev, 2017-2018
3
+ Copyright: Boaz Segev, 2017-2019
4
4
  License: MIT
5
5
 
6
6
  Feel free to copy, use and enjoy according to the license provided.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright: Boaz Segev, 2016-2018
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 = fio_siphash("upgrade", 7);
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 = fio_siphash("host", 4);
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
- http_send_error(h, 400);
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 = fio_siphash("upgrade", 7);
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) {
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright: Boaz Segev, 2016-2018
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
- fiobj_ary_push(old, value); /* value is owned by both hash and array */
222
- fiobj_hash_replace(hash, name, old); /* don't free `value` (leave in array) */
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 */
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright: Boaz Segev, 2018
2
+ Copyright: Boaz Segev, 2018-2019
3
3
  License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
@@ -28,7 +28,32 @@ Constants and State
28
28
 
29
29
  VALUE IodineModule;
30
30
  VALUE IodineBaseModule;
31
- static ID call_id;
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, call_id);
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, ID2SYM(rb_intern("log")), Qtrue);
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, ID2SYM(rb_intern("address")),
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, ID2SYM(rb_intern("port")),
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, ID2SYM(rb_intern("public")),
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, ID2SYM(rb_intern("timeout")),
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, ID2SYM(rb_intern("ping")),
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, ID2SYM(rb_intern("max_body")),
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("-max-message")) {
440
- rb_hash_aset(defaults, ID2SYM(rb_intern("max_msg")),
441
- INT2NUM((fio_cli_get_i("-max-message") * 1024)));
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("-max-headers")) {
444
- rb_hash_aset(defaults, ID2SYM(rb_intern("max_headers")),
445
- INT2NUM((fio_cli_get_i("-max-headers") * 1024)));
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
- call_id = rb_intern2("call", 4);
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