iodine 0.7.16 → 0.7.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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
@@ -3,6 +3,36 @@
3
3
 
4
4
  #include "ruby.h"
5
5
 
6
+ #include "fio.h"
7
+ #include "fio_tls.h"
8
+ #include "fiobj.h"
9
+ /* used for iodine_connect and iodine_listen routing */
10
+ typedef struct {
11
+ fio_str_info_s address;
12
+ fio_str_info_s port;
13
+ fio_str_info_s method;
14
+ fio_str_info_s path;
15
+ fio_str_info_s body;
16
+ fio_str_info_s public;
17
+ fio_str_info_s url;
18
+ fio_tls_s *tls;
19
+ VALUE handler;
20
+ FIOBJ headers;
21
+ FIOBJ cookies;
22
+ size_t max_headers;
23
+ size_t max_body;
24
+ intptr_t max_clients;
25
+ size_t max_msg;
26
+ uint8_t timeout;
27
+ uint8_t ping;
28
+ uint8_t log;
29
+ enum {
30
+ IODINE_SERVICE_RAW,
31
+ IODINE_SERVICE_HTTP,
32
+ IODINE_SERVICE_WS,
33
+ } service;
34
+ } iodine_connection_args_s;
35
+
6
36
  #include "iodine_caller.h"
7
37
  #include "iodine_connection.h"
8
38
  #include "iodine_defer.h"
@@ -14,12 +44,15 @@
14
44
  #include "iodine_rack_io.h"
15
45
  #include "iodine_store.h"
16
46
  #include "iodine_tcp.h"
47
+ #include "iodine_tls.h"
17
48
 
18
49
  /* *****************************************************************************
19
50
  Constants
20
51
  ***************************************************************************** */
21
52
  extern VALUE IodineModule;
22
53
  extern VALUE IodineBaseModule;
54
+ extern VALUE iodine_default_args;
55
+ extern ID iodine_call_id;
23
56
 
24
57
  #define IODINE_RSTRINFO(rstr) \
25
58
  ((fio_str_info_s){.len = RSTRING_LEN(rstr), .data = RSTRING_PTR(rstr)})
@@ -51,14 +51,14 @@ Pub/Sub storage
51
51
 
52
52
  static inline VALUE iodine_sub_unsubscribe(fio_subhash_s *store,
53
53
  fio_str_info_s channel) {
54
- if (fio_subhash_remove(store, fio_siphash(channel.data, channel.len), channel,
55
- NULL))
54
+ if (fio_subhash_remove(store, fiobj_hash_string(channel.data, channel.len),
55
+ channel, NULL))
56
56
  return Qfalse;
57
57
  return Qtrue;
58
58
  }
59
59
  static inline void iodine_sub_add(fio_subhash_s *store, subscription_s *sub) {
60
60
  fio_str_info_s ch = fio_subscription_channel(sub);
61
- fio_subhash_insert(store, fio_siphash(ch.data, ch.len), ch, sub, NULL);
61
+ fio_subhash_insert(store, fiobj_hash_string(ch.data, ch.len), ch, sub, NULL);
62
62
  }
63
63
  static inline void iodine_sub_clear_all(fio_subhash_s *store) {
64
64
  fio_subhash_free(store);
@@ -357,7 +357,7 @@ static VALUE iodine_connection_handler_set(VALUE self, VALUE handler) {
357
357
  }
358
358
  if (data->info.handler != handler) {
359
359
  uint8_t answers_on_open = (rb_respond_to(handler, on_open_id) != 0);
360
- if(data->answers_on_close)
360
+ if (data->answers_on_close)
361
361
  IodineCaller.call2(data->info.handler, on_close_id, 1, &self);
362
362
  fio_lock(&data->lock);
363
363
  data->info.handler = handler;
@@ -395,8 +395,13 @@ static void iodine_on_pubsub(fio_msg_s *msg) {
395
395
  iodine_connection_data_s *data = msg->udata1;
396
396
  VALUE block = (VALUE)msg->udata2;
397
397
  switch (block) {
398
+ case 0: /* fallthrough */
398
399
  case Qnil: /* fallthrough */
399
400
  case Qtrue: { /* Qtrue == binary WebSocket */
401
+ if (!data) {
402
+ FIO_LOG_ERROR("Pub/Sub direct called with no connection data!");
403
+ return;
404
+ }
400
405
  if (data->info.handler == Qnil || data->info.uuid == -1 ||
401
406
  fio_is_closed(data->info.uuid))
402
407
  return;
@@ -423,7 +428,7 @@ static void iodine_on_pubsub(fio_msg_s *msg) {
423
428
  }
424
429
  }
425
430
  default:
426
- if (data->info.uuid != -1) {
431
+ if (data && data->info.uuid != -1) {
427
432
  fio_protocol_s *pr =
428
433
  fio_protocol_try_lock(data->info.uuid, FIO_PR_LOCK_TASK);
429
434
  if (!pr) {
@@ -560,13 +565,10 @@ The second, optional, argument must be a Hash (if given).
560
565
 
561
566
  The options Hash supports the following possible keys (other keys are ignored, all keys are Symbols):
562
567
 
563
- :match :: The channel / subject name matching type to be used. Valid value is: `:redis`. Future versions hope to support `:nats` and `:rabbit` patern matching as well.
564
-
565
- :to :: The channel / subject to subscribe to.
566
-
567
- :as :: (only for WebSocket connections) accepts the optional value `:binary`. default is `:text`. Note that binary transmissions are illegal for some connections (such as SSE) and an attempted binary subscription will fail for these connections.
568
-
569
- :handler :: Any object that answers `#call(source, msg)` where source is the stream / channel name.
568
+ - `:match` - The channel / subject name matching type to be used. Valid value is: `:redis`. Future versions hope to support `:nats` and `:rabbit` patern matching as well.
569
+ - `:to` - The channel / subject to subscribe to.
570
+ - `:as` - (only for WebSocket connections) accepts the optional value `:binary`. default is `:text`. Note that binary transmissions are illegal for some connections (such as SSE) and an attempted binary subscription will fail for these connections.
571
+ - `:handler` - Any object that answers `.call(source, msg)` where source is the stream / channel name.
570
572
 
571
573
  Note: if an existing subscription with the same name exists, it will be replaced by this new subscription.
572
574
 
@@ -676,11 +678,9 @@ The method accepts an optional `engine` argument:
676
678
 
677
679
  Alternatively, accepts the following named arguments:
678
680
 
679
- :to :: The channel to publish to (required).
680
-
681
- :message :: The message to be published (required).
682
-
683
- :engine :: If provided, the engine to use for pub/sub. Otherwise the default engine is used.
681
+ - `:to` - The channel to publish to (required).
682
+ - `:message` - The message to be published (required).
683
+ - `:engine` - If provided, the engine to use for pub/sub. Otherwise the default engine is used.
684
684
 
685
685
  */
686
686
  static VALUE iodine_pubsub_publish(int argc, VALUE *argv, VALUE self) {
@@ -771,7 +771,7 @@ VALUE iodine_connection_new(iodine_connection_s args) {
771
771
  void iodine_connection_fire_event(VALUE connection,
772
772
  iodine_connection_event_type_e ev,
773
773
  VALUE msg) {
774
- if (connection == Qnil) {
774
+ if (!connection || connection == Qnil) {
775
775
  FIO_LOG_ERROR(
776
776
  "(iodine) nil connection handle used by an internal API call");
777
777
  return;
@@ -783,6 +783,11 @@ void iodine_connection_fire_event(VALUE connection,
783
783
  (void *)connection);
784
784
  return;
785
785
  }
786
+ if (!data->info.handler || data->info.handler == Qnil) {
787
+ FIO_LOG_DEBUG("(iodine) invalid connection handler, can't fire event %d",
788
+ (int)ev);
789
+ return;
790
+ }
786
791
  VALUE args[2] = {connection, msg};
787
792
  switch (ev) {
788
793
  case IODINE_CONNECTION_ON_OPEN:
@@ -38,6 +38,13 @@ static VALUE RACK_UPGRADE_SSE;
38
38
  static VALUE RACK_UPGRADE_WEBSOCKET;
39
39
  static VALUE UPGRADE_TCP;
40
40
 
41
+ static VALUE HTTP_ACCEPT;
42
+ static VALUE HTTP_USER_AGENT;
43
+ static VALUE HTTP_ACCEPT_ENCODING;
44
+ static VALUE HTTP_ACCEPT_LANGUAGE;
45
+ static VALUE HTTP_CONNECTION;
46
+ static VALUE HTTP_HOST;
47
+
41
48
  static VALUE hijack_func_sym;
42
49
  static ID close_method_id;
43
50
  static ID each_method_id;
@@ -54,9 +61,6 @@ static rb_encoding *IodineBinaryEncoding;
54
61
 
55
62
  static uint8_t support_xsendfile = 0;
56
63
 
57
- /** Used by {listen2http} to set missing arguments. */
58
- VALUE iodine_default_args;
59
-
60
64
  #define rack_declare(rack_name) static VALUE rack_name
61
65
 
62
66
  #define rack_set(rack_name, str) \
@@ -251,7 +255,20 @@ static int iodine_copy2env_task(FIOBJ o, void *env_) {
251
255
  FIOBJ name = fiobj_hash_key_in_loop();
252
256
  fio_str_info_s tmp = fiobj_obj2cstr(name);
253
257
  VALUE hname = (VALUE)0;
254
- if (tmp.len > 59) {
258
+ /* test for common header names, using pre-allocated memory */
259
+ if (tmp.len == 6 && !memcmp("accept", tmp.data, 6)) {
260
+ hname = HTTP_ACCEPT;
261
+ } else if (tmp.len == 10 && !memcmp("user-agent", tmp.data, 10)) {
262
+ hname = HTTP_USER_AGENT;
263
+ } else if (tmp.len == 15 && !memcmp("accept-encoding", tmp.data, 15)) {
264
+ hname = HTTP_ACCEPT_ENCODING;
265
+ } else if (tmp.len == 15 && !memcmp("accept-language", tmp.data, 15)) {
266
+ hname = HTTP_ACCEPT_LANGUAGE;
267
+ } else if (tmp.len == 10 && !memcmp("connection", tmp.data, 10)) {
268
+ hname = HTTP_CONNECTION;
269
+ } else if (tmp.len == 4 && !memcmp("host", tmp.data, 4)) {
270
+ hname = HTTP_HOST;
271
+ } else if (tmp.len > 59) {
255
272
  char *buf = fio_malloc(tmp.len + 5);
256
273
  memcpy(buf, "HTTP_", 5);
257
274
  for (size_t i = 0; i < tmp.len; ++i) {
@@ -339,7 +356,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
339
356
  /* handle the HOST header, including the possible host:#### format*/
340
357
  static uint64_t host_hash = 0;
341
358
  if (!host_hash)
342
- host_hash = fio_siphash("host", 4);
359
+ host_hash = fiobj_hash_string("host", 4);
343
360
  tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, host_hash));
344
361
  pos = tmp.data;
345
362
  while (*pos && *pos != ':')
@@ -362,7 +379,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
362
379
  {
363
380
  static uint64_t content_length_hash = 0;
364
381
  if (!content_length_hash)
365
- content_length_hash = fio_siphash("content-length", 14);
382
+ content_length_hash = fiobj_hash_string("content-length", 14);
366
383
  FIOBJ cl = fiobj_hash_get2(h->headers, content_length_hash);
367
384
  if (cl) {
368
385
  tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, content_length_hash));
@@ -376,7 +393,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
376
393
  {
377
394
  static uint64_t content_type_hash = 0;
378
395
  if (!content_type_hash)
379
- content_type_hash = fio_siphash("content-type", 12);
396
+ content_type_hash = fiobj_hash_string("content-type", 12);
380
397
  FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash);
381
398
  if (ct) {
382
399
  tmp = fiobj_obj2cstr(ct);
@@ -393,10 +410,10 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
393
410
  FIOBJ objtmp;
394
411
  static uint64_t xforward_hash = 0;
395
412
  if (!xforward_hash)
396
- xforward_hash = fio_siphash("x-forwarded-proto", 27);
413
+ xforward_hash = fiobj_hash_string("x-forwarded-proto", 27);
397
414
  static uint64_t forward_hash = 0;
398
415
  if (!forward_hash)
399
- forward_hash = fio_siphash("forwarded", 9);
416
+ forward_hash = fiobj_hash_string("forwarded", 9);
400
417
  if ((objtmp = fiobj_hash_get2(h->headers, xforward_hash))) {
401
418
  tmp = fiobj_obj2cstr(objtmp);
402
419
  if (tmp.len >= 5 && !strncasecmp(tmp.data, "https", 5)) {
@@ -432,11 +449,15 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
432
449
  }
433
450
  }
434
451
  }
452
+ } else if (http_settings(h)->tls) {
453
+ /* no forwarding information, but we do have TLS */
454
+ rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
435
455
  } else {
456
+ /* no TLS, no forwarding, assume `http`, which is the default */
436
457
  }
437
458
  }
438
459
 
439
- /* add all remianing headers */
460
+ /* add all remaining headers */
440
461
  fiobj_each1(h->headers, 0, iodine_copy2env_task, (void *)env);
441
462
  return env;
442
463
  }
@@ -776,213 +797,8 @@ static void on_rack_upgrade(http_s *h, char *proto, size_t len) {
776
797
  }
777
798
 
778
799
  /* *****************************************************************************
779
- Listenninng to HTTP
780
- *****************************************************************************
781
- */
782
-
783
- static void free_iodine_http(http_settings_s *s) {
784
- IodineStore.remove((VALUE)s->udata);
785
- }
786
-
787
- // clang-format off
788
- /**
789
- Listens to incoming HTTP connections and handles incoming requests using the
790
- Rack specification.
791
-
792
- This is delegated to a lower level C HTTP and Websocket implementation, no
793
- Ruby object will be crated except the `env` object required by the Rack
794
- specifications.
795
-
796
- Accepts a single Hash argument with the following properties:
797
-
798
- (it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash)
799
-
800
- app:: the Rack application that handles incoming requests. Default: `nil`.
801
- port:: the port to listen to. Default: 3000.
802
- address:: the address to bind to. Default: binds to all possible addresses.
803
- log:: enable response logging (Hijacked sockets aren't logged). Default: off.
804
- public:: The root public folder for static file service. Default: none.
805
- timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
806
- max_body:: The maximum body size for incoming HTTP messages in bytes. Default: ~50Mib.
807
- max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib.
808
- max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
809
- ping:: The Websocket `ping` interval. Default: 40 seconds.
810
-
811
- Either the `app` or the `public` properties are required. If niether exists,
812
- the function will fail. If both exist, Iodine will serve static files as well
813
- as dynamic requests.
814
-
815
- When using the static file server, it's possible to serve `gzip` versions of
816
- the static files by saving a compressed version with the `gz` extension (i.e.
817
- `styles.css.gz`).
818
-
819
- `gzip` will only be served to clients tat support the `gzip` transfer
820
- encoding.
821
-
822
- Once HTTP/2 is supported (planned, but probably very far away), HTTP/2
823
- timeouts will be dynamically managed by Iodine. The `timeout` option is only
824
- relevant to HTTP/1.x connections.
825
- */
826
- static VALUE iodine_http_listen(VALUE self, VALUE opt) {
827
- // clang-format on
828
- uint8_t log_http = 0;
829
- size_t ping = 0;
830
- size_t max_body = 0;
831
- size_t max_headers = 0;
832
- size_t max_msg = 0;
833
- Check_Type(opt, T_HASH);
834
- /* copy from deafult hash */
835
- /* test arguments */
836
- VALUE app = rb_hash_aref(opt, ID2SYM(rb_intern("app")));
837
- VALUE www = rb_hash_aref(opt, ID2SYM(rb_intern("public")));
838
- VALUE port = rb_hash_aref(opt, ID2SYM(rb_intern("port")));
839
- VALUE address = rb_hash_aref(opt, ID2SYM(rb_intern("address")));
840
- VALUE tout = rb_hash_aref(opt, ID2SYM(rb_intern("timeout")));
841
- if (www == Qnil) {
842
- www = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("public")));
843
- }
844
- if (port == Qnil) {
845
- port = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("port")));
846
- }
847
- if (address == Qnil) {
848
- address = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("address")));
849
- }
850
- if (tout == Qnil) {
851
- tout = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("timeout")));
852
- }
853
-
854
- VALUE tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_msg")));
855
- if (tmp == Qnil) {
856
- tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_msg")));
857
- }
858
- if (tmp != Qnil && tmp != Qfalse) {
859
- Check_Type(tmp, T_FIXNUM);
860
- max_msg = FIX2ULONG(tmp);
861
- }
862
-
863
- tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_body")));
864
- if (tmp == Qnil) {
865
- tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_body")));
866
- }
867
- if (tmp != Qnil && tmp != Qfalse) {
868
- Check_Type(tmp, T_FIXNUM);
869
- max_body = FIX2ULONG(tmp);
870
- }
871
- tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_headers")));
872
- if (tmp == Qnil) {
873
- tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_headers")));
874
- }
875
- if (tmp != Qnil && tmp != Qfalse) {
876
- Check_Type(tmp, T_FIXNUM);
877
- max_headers = FIX2ULONG(tmp);
878
- }
879
-
880
- tmp = rb_hash_aref(opt, ID2SYM(rb_intern("ping")));
881
- if (tmp == Qnil) {
882
- tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("ping")));
883
- }
884
- if (tmp != Qnil && tmp != Qfalse) {
885
- Check_Type(tmp, T_FIXNUM);
886
- ping = FIX2ULONG(tmp);
887
- }
888
- if (ping > 255) {
889
- fprintf(stderr, "Iodine Warning: Websocket timeout value "
890
- "is over 255 and will be ignored.\n");
891
- ping = 0;
892
- }
893
-
894
- tmp = rb_hash_aref(opt, ID2SYM(rb_intern("log")));
895
- if (tmp == Qnil) {
896
- tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("log")));
897
- }
898
- if (tmp != Qnil && tmp != Qfalse)
899
- log_http = 1;
900
-
901
- if ((app == Qnil || app == Qfalse) && (www == Qnil || www == Qfalse)) {
902
- fprintf(stderr, "Iodine Warning: HTTP without application or public folder "
903
- "(ignored).\n");
904
- return Qfalse;
905
- }
906
-
907
- if ((www != Qnil && www != Qfalse)) {
908
- Check_Type(www, T_STRING);
909
- IodineStore.add(www);
910
- rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
911
- rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
912
- support_xsendfile = 1;
913
- } else
914
- www = 0;
915
-
916
- if ((address != Qnil && address != Qfalse))
917
- Check_Type(address, T_STRING);
918
- else
919
- address = 0;
920
-
921
- if ((tout != Qnil && tout != Qfalse)) {
922
- Check_Type(tout, T_FIXNUM);
923
- tout = FIX2ULONG(tout);
924
- } else
925
- tout = 0;
926
- if (tout > 255) {
927
- fprintf(stderr, "Iodine Warning: HTTP timeout value "
928
- "is over 255 and is silently ignored.\n");
929
- tout = 0;
930
- }
931
-
932
- if (port != Qnil && port != Qfalse) {
933
- if (!RB_TYPE_P(port, T_STRING) && !RB_TYPE_P(port, T_FIXNUM))
934
- rb_raise(rb_eTypeError,
935
- "The `port` property MUST be either a String or a Number");
936
- if (RB_TYPE_P(port, T_FIXNUM))
937
- port = rb_funcall2(port, iodine_to_s_method_id, 0, NULL);
938
- IodineStore.add(port);
939
- } else if (port == Qfalse)
940
- port = 0;
941
- else if (address &&
942
- (StringValueCStr(address)[0] > '9' ||
943
- StringValueCStr(address)[0] < '0') &&
944
- StringValueCStr(address)[0] != ':' &&
945
- (RSTRING_LEN(address) < 3 || StringValueCStr(address)[2] != ':')) {
946
- /* address is likely a Unix domain socket address, not an IP address... */
947
- port = Qnil;
948
- } else {
949
- port = rb_str_new("3000", 4);
950
- IodineStore.add(port);
951
- }
952
-
953
- if ((app != Qnil && app != Qfalse))
954
- IodineStore.add(app);
955
- else
956
- app = 0;
957
-
958
- if (http_listen((port ? StringValueCStr(port) : NULL),
959
- (address ? StringValueCStr(address) : NULL),
960
- .on_request = on_rack_request, .on_upgrade = on_rack_upgrade,
961
- .udata = (void *)app,
962
- .timeout = (tout ? FIX2INT(tout) : tout), .ws_timeout = ping,
963
- .ws_max_msg_size = max_msg, .max_header_size = max_headers,
964
- .on_finish = free_iodine_http, .log = log_http,
965
- .max_body_size = max_body,
966
- .public_folder = (www ? StringValueCStr(www) : NULL)) == -1) {
967
- FIO_LOG_ERROR("Failed to initialize a listening HTTP socket for port %s",
968
- port ? StringValueCStr(port) : "3000");
969
- rb_raise(rb_eRuntimeError, "Listening socket initialization failed");
970
- return Qfalse;
971
- }
972
-
973
- if ((app == Qnil || app == Qfalse)) {
974
- FIO_LOG_WARNING(
975
- "(listen2http) no app, the HTTP service on port %s will only serve "
976
- "static files.",
977
- (port ? StringValueCStr(port) : "3000"));
978
- }
979
- if (www) {
980
- FIO_LOG_INFO("Serving static files from %s", StringValueCStr(www));
981
- }
982
-
983
- return Qtrue;
984
- (void)self;
985
- }
800
+ Rack `env` Template Initialization
801
+ ***************************************************************************** */
986
802
 
987
803
  static void initialize_env_template(void) {
988
804
  if (env_template_no_upgrade)
@@ -1054,19 +870,209 @@ static void initialize_env_template(void) {
1054
870
  }
1055
871
 
1056
872
  /* *****************************************************************************
1057
- Initialization
873
+ Listenninng to HTTP
874
+ *****************************************************************************
875
+ */
876
+
877
+ static void free_iodine_http(http_settings_s *s) {
878
+ IodineStore.remove((VALUE)s->udata);
879
+ }
880
+
881
+ // clang-format off
882
+ /**
883
+ Listens to incoming HTTP connections and handles incoming requests using the
884
+ Rack specification.
885
+
886
+ This is delegated to a lower level C HTTP and Websocket implementation, no
887
+ Ruby object will be crated except the `env` object required by the Rack
888
+ specifications.
889
+
890
+ Accepts a single Hash argument with the following properties:
891
+
892
+ (it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash)
893
+
894
+ app:: the Rack application that handles incoming requests. Default: `nil`.
895
+ port:: the port to listen to. Default: 3000.
896
+ address:: the address to bind to. Default: binds to all possible addresses.
897
+ log:: enable response logging (Hijacked sockets aren't logged). Default: off.
898
+ public:: The root public folder for static file service. Default: none.
899
+ timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
900
+ max_body:: The maximum body size for incoming HTTP messages in bytes. Default: ~50Mib.
901
+ max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib.
902
+ max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
903
+ ping:: The Websocket `ping` interval. Default: 40 seconds.
904
+
905
+ Either the `app` or the `public` properties are required. If niether exists,
906
+ the function will fail. If both exist, Iodine will serve static files as well
907
+ as dynamic requests.
908
+
909
+ When using the static file server, it's possible to serve `gzip` versions of
910
+ the static files by saving a compressed version with the `gz` extension (i.e.
911
+ `styles.css.gz`).
912
+
913
+ `gzip` will only be served to clients tat support the `gzip` transfer
914
+ encoding.
915
+
916
+ Once HTTP/2 is supported (planned, but probably very far away), HTTP/2
917
+ timeouts will be dynamically managed by Iodine. The `timeout` option is only
918
+ relevant to HTTP/1.x connections.
919
+ */
920
+ intptr_t iodine_http_listen(iodine_connection_args_s args){
921
+ // clang-format on
922
+ if (args.public.data) {
923
+ rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
924
+ rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
925
+ support_xsendfile = 1;
926
+ }
927
+ IodineStore.add(args.handler);
928
+ intptr_t uuid = http_listen(
929
+ args.port.data, args.address.data, .on_request = on_rack_request,
930
+ .on_upgrade = on_rack_upgrade, .udata = (void *)args.handler,
931
+ .tls = args.tls, .timeout = args.timeout, .ws_timeout = args.ping,
932
+ .ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers,
933
+ .on_finish = free_iodine_http, .log = args.log,
934
+ .max_body_size = args.max_body, .public_folder = args.public.data);
935
+ if (uuid == -1)
936
+ return uuid;
937
+
938
+ if ((args.handler == Qnil || args.handler == Qfalse)) {
939
+ FIO_LOG_WARNING("(listen) no handler / app, the HTTP service on port %s "
940
+ "will only serve "
941
+ "static files.",
942
+ args.port.data ? args.port.data : args.address.data);
943
+ }
944
+ if (args.public.data) {
945
+ FIO_LOG_INFO("Serving static files from %s", args.public.data);
946
+ }
947
+
948
+ return uuid;
949
+ }
950
+
951
+ /* *****************************************************************************
952
+ HTTP Websocket Connect
1058
953
  ***************************************************************************** */
1059
954
 
1060
- void iodine_init_http(void) {
955
+ typedef struct {
956
+ FIOBJ method;
957
+ FIOBJ headers;
958
+ FIOBJ cookies;
959
+ FIOBJ body;
960
+ VALUE io;
961
+ } request_data_s;
962
+
963
+ static request_data_s *request_data_create(iodine_connection_args_s *args) {
964
+ request_data_s *r = fio_malloc(sizeof(*r));
965
+ FIO_ASSERT_ALLOC(r);
966
+ VALUE io =
967
+ iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL,
968
+ .handler = args->handler, .env = Qnil, .uuid = 0);
969
+
970
+ *r = (request_data_s){
971
+ .method = fiobj_str_new(args->method.data, args->method.len),
972
+ .headers = fiobj_dup(args->headers),
973
+ .cookies = fiobj_dup(args->cookies),
974
+ .body = fiobj_str_new(args->body.data, args->body.len),
975
+ .io = io,
976
+ };
977
+ return r;
978
+ }
979
+
980
+ static void request_data_destroy(request_data_s *r) {
981
+ fiobj_free(r->method);
982
+ fiobj_free(r->body);
983
+ fiobj_free(r->headers);
984
+ fiobj_free(r->cookies);
985
+ fio_free(r);
986
+ }
987
+
988
+ static int each_header_ws_client_task(FIOBJ val, void *h_) {
989
+ http_s *h = h_;
990
+ FIOBJ key = fiobj_hash_key_in_loop();
991
+ http_set_header(h, key, fiobj_dup(val));
992
+ return 0;
993
+ }
994
+ static int each_cookie_ws_client_task(FIOBJ val, void *h_) {
995
+ http_s *h = h_;
996
+ FIOBJ key = fiobj_hash_key_in_loop();
997
+ fio_str_info_s n = fiobj_obj2cstr(key);
998
+ fio_str_info_s v = fiobj_obj2cstr(val);
999
+ http_set_cookie(h, .name = n.data, .name_len = n.len, .value = v.data,
1000
+ .value_len = v.len);
1001
+ return 0;
1002
+ }
1061
1003
 
1062
- rb_define_module_function(IodineModule, "listen2http", iodine_http_listen, 1);
1004
+ static void ws_client_http_connected(http_s *h) {
1005
+ request_data_s *s = h->udata;
1006
+ if (!s)
1007
+ return;
1008
+ h->udata = http_settings(h)->udata = NULL;
1009
+ if (!h->path) {
1010
+ h->path = fiobj_str_new("/", 1);
1011
+ }
1012
+ /* TODO: add headers and cookies */
1013
+ fiobj_each1(s->headers, 0, each_header_ws_client_task, h);
1014
+ fiobj_each1(s->headers, 0, each_cookie_ws_client_task, h);
1015
+ if (s->io && s->io != Qnil)
1016
+ http_upgrade2ws(
1017
+ h, .on_message = iodine_ws_on_message, .on_open = iodine_ws_on_open,
1018
+ .on_ready = iodine_ws_on_ready, .on_shutdown = iodine_ws_on_shutdown,
1019
+ .on_close = iodine_ws_on_close, .udata = (void *)s->io);
1020
+ request_data_destroy(s);
1021
+ }
1063
1022
 
1064
- /** Used by {listen2http} to set missing arguments. */
1065
- iodine_default_args = rb_hash_new();
1023
+ static void ws_client_http_connection_finished(http_settings_s *settings) {
1024
+ if (!settings)
1025
+ return;
1026
+ request_data_s *s = settings->udata;
1027
+ if (s) {
1028
+ if (s->io && s->io != Qnil)
1029
+ iodine_connection_fire_event(s->io, IODINE_CONNECTION_ON_CLOSE, Qnil);
1030
+ request_data_destroy(s);
1031
+ }
1032
+ }
1066
1033
 
1067
- /** Used by {listen2http} to set missing arguments. */
1068
- rb_const_set(IodineModule, rb_intern2("DEFAULT_HTTP_ARGS", 17),
1069
- iodine_default_args);
1034
+ /** Connects to a (remote) WebSocket service. */
1035
+ intptr_t iodine_ws_connect(iodine_connection_args_s args) {
1036
+ // http_connect(url, unixaddr, struct http_settings_s)
1037
+ uint8_t is_unix_socket = 0;
1038
+ if (memchr(args.address.data, '/', args.address.len)) {
1039
+ is_unix_socket = 1;
1040
+ }
1041
+ FIOBJ url_tmp = FIOBJ_INVALID;
1042
+ if (!args.url.data) {
1043
+ url_tmp = fiobj_str_buf(64);
1044
+ if (args.tls)
1045
+ fiobj_str_write(url_tmp, "wss://", 6);
1046
+ else
1047
+ fiobj_str_write(url_tmp, "ws://", 5);
1048
+ if (!is_unix_socket) {
1049
+ fiobj_str_write(url_tmp, args.address.data, args.address.len);
1050
+ if (args.port.data) {
1051
+ fiobj_str_write(url_tmp, ":", 1);
1052
+ fiobj_str_write(url_tmp, args.port.data, args.port.len);
1053
+ }
1054
+ }
1055
+ if (args.path.data)
1056
+ fiobj_str_write(url_tmp, args.path.data, args.path.len);
1057
+ else
1058
+ fiobj_str_write(url_tmp, "/", 1);
1059
+ args.url = fiobj_obj2cstr(url_tmp);
1060
+ }
1061
+
1062
+ intptr_t uuid = http_connect(
1063
+ args.url.data, (is_unix_socket ? args.address.data : NULL),
1064
+ .udata = request_data_create(&args),
1065
+ .on_response = ws_client_http_connected,
1066
+ .on_finish = ws_client_http_connection_finished, .tls = args.tls);
1067
+ fiobj_free(url_tmp);
1068
+ return uuid;
1069
+ }
1070
+
1071
+ /* *****************************************************************************
1072
+ Initialization
1073
+ ***************************************************************************** */
1074
+
1075
+ void iodine_init_http(void) {
1070
1076
 
1071
1077
  rack_autoset(REQUEST_METHOD);
1072
1078
  rack_autoset(PATH_INFO);
@@ -1078,6 +1084,14 @@ void iodine_init_http(void) {
1078
1084
  rack_autoset(SERVER_PROTOCOL);
1079
1085
  rack_autoset(HTTP_VERSION);
1080
1086
  rack_autoset(REMOTE_ADDR);
1087
+
1088
+ rack_autoset(HTTP_ACCEPT);
1089
+ rack_autoset(HTTP_USER_AGENT);
1090
+ rack_autoset(HTTP_ACCEPT_ENCODING);
1091
+ rack_autoset(HTTP_ACCEPT_LANGUAGE);
1092
+ rack_autoset(HTTP_CONNECTION);
1093
+ rack_autoset(HTTP_HOST);
1094
+
1081
1095
  rack_set(HTTP_SCHEME, "http");
1082
1096
  rack_set(HTTPS_SCHEME, "https");
1083
1097
  rack_set(QUERY_ESTRING, "");