iodine 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +63 -100
  4. data/bin/raw-rbhttp +12 -7
  5. data/examples/config.ru +8 -7
  6. data/examples/echo.ru +8 -7
  7. data/examples/info.md +41 -35
  8. data/examples/pubsub_engine.ru +12 -12
  9. data/examples/redis.ru +10 -12
  10. data/examples/shootout.ru +19 -42
  11. data/exe/iodine +116 -1
  12. data/ext/iodine/defer.c +1 -1
  13. data/ext/iodine/facil.c +12 -8
  14. data/ext/iodine/facil.h +2 -2
  15. data/ext/iodine/iodine.c +177 -343
  16. data/ext/iodine/iodine.h +18 -72
  17. data/ext/iodine/iodine_caller.c +132 -0
  18. data/ext/iodine/iodine_caller.h +21 -0
  19. data/ext/iodine/iodine_connection.c +841 -0
  20. data/ext/iodine/iodine_connection.h +55 -0
  21. data/ext/iodine/iodine_defer.c +391 -0
  22. data/ext/iodine/iodine_defer.h +7 -0
  23. data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
  24. data/ext/iodine/iodine_helpers.c +51 -5
  25. data/ext/iodine/iodine_helpers.h +2 -3
  26. data/ext/iodine/iodine_http.c +284 -141
  27. data/ext/iodine/iodine_http.h +2 -2
  28. data/ext/iodine/iodine_json.c +13 -13
  29. data/ext/iodine/iodine_json.h +1 -1
  30. data/ext/iodine/iodine_pubsub.c +573 -823
  31. data/ext/iodine/iodine_pubsub.h +15 -27
  32. data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
  33. data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
  34. data/ext/iodine/iodine_store.c +136 -0
  35. data/ext/iodine/iodine_store.h +20 -0
  36. data/ext/iodine/iodine_tcp.c +385 -0
  37. data/ext/iodine/iodine_tcp.h +9 -0
  38. data/lib/iodine.rb +73 -171
  39. data/lib/iodine/connection.rb +34 -0
  40. data/lib/iodine/pubsub.rb +5 -18
  41. data/lib/iodine/rack_utils.rb +43 -0
  42. data/lib/iodine/version.rb +1 -1
  43. data/lib/rack/handler/iodine.rb +1 -182
  44. metadata +17 -18
  45. data/ext/iodine/iodine_protocol.c +0 -689
  46. data/ext/iodine/iodine_protocol.h +0 -13
  47. data/ext/iodine/iodine_websockets.c +0 -550
  48. data/ext/iodine/iodine_websockets.h +0 -17
  49. data/ext/iodine/rb-call.c +0 -156
  50. data/ext/iodine/rb-call.h +0 -70
  51. data/ext/iodine/rb-defer.c +0 -124
  52. data/ext/iodine/rb-registry.c +0 -150
  53. data/ext/iodine/rb-registry.h +0 -34
  54. data/lib/iodine/cli.rb +0 -89
  55. data/lib/iodine/monkeypatch.rb +0 -46
  56. data/lib/iodine/protocol.rb +0 -42
  57. data/lib/iodine/websocket.rb +0 -16
@@ -9,7 +9,7 @@ Feel free to copy, use and enjoy according to the license provided.
9
9
  #include <fiobj.h>
10
10
  #include <ruby.h>
11
11
 
12
- #include "rb-registry.h"
12
+ #include "iodine_store.h"
13
13
 
14
14
  typedef struct {
15
15
  FIOBJ stack;
@@ -35,7 +35,7 @@ static inline VALUE fiobj2rb(FIOBJ o, uint8_t str2sym) {
35
35
  rb = Qfalse;
36
36
  break;
37
37
  case FIOBJ_T_FLOAT:
38
- rb = DBL2NUM(fiobj_obj2float(o));
38
+ rb = rb_float_new(fiobj_obj2float(o));
39
39
  break;
40
40
  case FIOBJ_T_DATA: /* fallthrough */
41
41
  case FIOBJ_T_UNKNOWN: /* fallthrough */
@@ -67,7 +67,7 @@ static int fiobj2rb_task(FIOBJ o, void *data_) {
67
67
  fiobj2rb_s *data = data_;
68
68
  VALUE rb_tmp;
69
69
  rb_tmp = fiobj2rb(o, 0);
70
- Registry.add(rb_tmp);
70
+ IodineStore.add(rb_tmp);
71
71
  if (data->rb) {
72
72
  if (RB_TYPE_P(data->rb, T_HASH)) {
73
73
  rb_hash_aset(data->rb, fiobj2rb(fiobj_hash_key_in_loop(), data->str2sym),
@@ -76,10 +76,10 @@ static int fiobj2rb_task(FIOBJ o, void *data_) {
76
76
  rb_ary_push(data->rb, rb_tmp);
77
77
  }
78
78
  --(data->count);
79
- Registry.remove(rb_tmp);
79
+ IodineStore.remove(rb_tmp);
80
80
  } else {
81
81
  data->rb = rb_tmp;
82
- // Registry.add(rb_tmp);
82
+ // IodineStore.add(rb_tmp);
83
83
  }
84
84
  if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY)) {
85
85
  fiobj_ary_push(data->stack, (FIOBJ)data->count);
@@ -108,7 +108,7 @@ static inline VALUE fiobj2rb_deep(FIOBJ obj, uint8_t str2sym) {
108
108
  while (fiobj_ary_pop(data.stack))
109
109
  ;
110
110
  fiobj_free(data.stack);
111
- Registry.remove(data.rb);
111
+ IodineStore.remove(data.rb);
112
112
  return data.rb;
113
113
  }
114
114
 
@@ -4,18 +4,21 @@ License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
- #include "iodine_helpers.h"
7
+ #include "iodine.h"
8
8
 
9
9
  #include "http.h"
10
+ #include <ruby/encoding.h>
10
11
 
11
12
  /*
12
13
  Add all sorts of useless stuff here.
13
14
  */
14
15
 
16
+ static ID iodine_to_i_func_id;
17
+ static rb_encoding *IodineUTF8Encoding;
18
+
15
19
  /* *****************************************************************************
16
20
  URL Decoding
17
21
  ***************************************************************************** */
18
-
19
22
  /**
20
23
  Decodes a URL encoded String in place.
21
24
 
@@ -210,9 +213,47 @@ static VALUE iodine_rfc2109(VALUE self, VALUE rtm) {
210
213
  Ruby Initialization
211
214
  ***************************************************************************** */
212
215
 
213
- void Iodine_init_helpers(void) {
214
- VALUE tmp = rb_define_module_under(Iodine, "Rack");
216
+ void iodine_init_helpers(void) {
217
+ iodine_to_i_func_id = rb_intern("to_i");
218
+ IodineUTF8Encoding = rb_enc_find("UTF-8");
219
+ VALUE tmp = rb_define_module_under(IodineModule, "Rack");
220
+ // clang-format off
221
+ /*
222
+ Iodine does NOT monkey patch Rack automatically. However, it's possible and recommended to moneky patch Rack::Utils to use the methods in this module.
223
+
224
+ Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine):
225
+
226
+ require 'iodine'
227
+ require 'rack'
228
+ # a String in need of decoding
229
+ s = '%E3%83%AB%E3%83%93%E3%82%A4%E3%82%B9%E3%81%A8'
230
+ Benchmark.bm do |bm|
231
+ # Pre-Patch
232
+ bm.report(" Rack.unescape") {1_000_000.times { Rack::Utils.unescape s } }
233
+ bm.report(" Rack.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } }
234
+ bm.report(" Rack.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } }
235
+ # Perform Patch
236
+ Iodine.patch_rack
237
+ puts " --- Monkey Patching Rack ---"
238
+ # Post Patch
239
+ bm.report("Patched.unescape") {1_000_000.times { Rack::Utils.unescape s } }
240
+ bm.report(" Patched.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } }
241
+ bm.report(" Patched.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } }
242
+ end && nil
243
+
244
+ Results:
245
+ user system total real
246
+ Rack.unescape 8.706881 0.019995 8.726876 ( 8.740530)
247
+ Rack.rfc2822 3.270305 0.007519 3.277824 ( 3.279416)
248
+ Rack.rfc2109 3.152188 0.003852 3.156040 ( 3.157975)
249
+ --- Monkey Patching Rack ---
250
+ Patched.unescape 0.327231 0.003125 0.330356 ( 0.337090)
251
+ Patched.rfc2822 0.691304 0.003330 0.694634 ( 0.701172)
252
+ Patched.rfc2109 0.685029 0.001956 0.686985 ( 0.687607)
253
+
254
+ */
215
255
  tmp = rb_define_module_under(tmp, "Utils");
256
+ // clang-format on
216
257
  rb_define_module_function(tmp, "decode_url!", url_decode_inplace, 1);
217
258
  rb_define_module_function(tmp, "decode_url", url_decode, 1);
218
259
  rb_define_module_function(tmp, "decode_path!", path_decode_inplace, 1);
@@ -221,8 +262,13 @@ void Iodine_init_helpers(void) {
221
262
  rb_define_module_function(tmp, "rfc2109", iodine_rfc2109, 1);
222
263
  rb_define_module_function(tmp, "rfc2822", iodine_rfc2822, 1);
223
264
 
224
- tmp = rb_define_module_under(IodineBase, "MonkeyPatch");
265
+ /*
266
+ The monkey-patched methods are in this module, allowing Iodine::Rack::Utils to
267
+ include non-patched methods as well.
268
+ */
269
+ tmp = rb_define_module_under(IodineBaseModule, "MonkeyPatch");
225
270
  tmp = rb_define_module_under(tmp, "RackUtils");
271
+ // clang-format on
226
272
  /* we define it all twice for easier monkey patching */
227
273
  rb_define_method(tmp, "unescape", unescape, -1);
228
274
  rb_define_method(tmp, "unescape_path", path_decode, 1);
@@ -1,13 +1,12 @@
1
1
  #ifndef H_IODINE_HELPERS_H
2
2
  #define H_IODINE_HELPERS_H
3
3
  /*
4
- Copyright: Boaz segev, 2016-2017
4
+ Copyright: Boaz segev, 2016-2018
5
5
  License: MIT
6
6
 
7
7
  Feel free to copy, use and enjoy according to the license provided.
8
8
  */
9
- #include "iodine.h"
10
9
 
11
- void Iodine_init_helpers(void);
10
+ void iodine_init_helpers(void);
12
11
 
13
12
  #endif
@@ -4,12 +4,14 @@ License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
- #include "iodine_http.h"
7
+ #include "iodine.h"
8
+
9
+ #include "evio.h"
8
10
  #include "fio_mem.h"
9
11
  #include "http.h"
10
- #include "iodine_json.h"
11
- #include "iodine_websockets.h"
12
- #include "rb-rack-io.h"
12
+ #include <ruby/encoding.h>
13
+ #include <ruby/io.h>
14
+ // #include "iodine_websockets.h"
13
15
 
14
16
  #include <arpa/inet.h>
15
17
  #include <ctype.h>
@@ -26,31 +28,36 @@ typedef struct {
26
28
  VALUE env;
27
29
  } iodine_http_settings_s;
28
30
 
29
- /* these three are used also by rb-rack-io.c */
31
+ /* these three are used also by iodin_rack_io.c */
30
32
  VALUE IODINE_R_HIJACK;
31
33
  VALUE IODINE_R_HIJACK_IO;
32
34
  VALUE IODINE_R_HIJACK_CB;
33
35
 
34
- static VALUE UPGRADE_TCP;
35
- static VALUE UPGRADE_TCP_Q;
36
- static VALUE UPGRADE_WEBSOCKET;
37
- static VALUE UPGRADE_WEBSOCKET_Q;
38
36
  static VALUE RACK_UPGRADE;
39
37
  static VALUE RACK_UPGRADE_Q;
40
38
  static VALUE RACK_UPGRADE_SSE;
41
39
  static VALUE RACK_UPGRADE_WEBSOCKET;
40
+ static VALUE UPGRADE_TCP;
42
41
 
43
42
  static VALUE hijack_func_sym;
44
43
  static ID close_method_id;
45
44
  static ID each_method_id;
46
45
  static ID attach_method_id;
46
+ static ID iodine_to_s_method_id;
47
+ static ID iodine_call_proc_id;
47
48
 
48
49
  static VALUE env_template_no_upgrade;
49
50
  static VALUE env_template_websockets;
50
51
  static VALUE env_template_sse;
51
52
 
53
+ static rb_encoding *IodineUTF8Encoding;
54
+ static rb_encoding *IodineBinaryEncoding;
55
+
52
56
  static uint8_t support_xsendfile = 0;
53
57
 
58
+ /** Used by {listen2http} to set missing arguments. */
59
+ static VALUE iodine_default_args;
60
+
54
61
  #define rack_declare(rack_name) static VALUE rack_name
55
62
 
56
63
  #define rack_set(rack_name, str) \
@@ -104,6 +111,136 @@ typedef struct {
104
111
  } upgrade;
105
112
  } iodine_http_request_handle_s;
106
113
 
114
+ /* *****************************************************************************
115
+ WebSocket support
116
+ ***************************************************************************** */
117
+
118
+ typedef struct {
119
+ char *data;
120
+ size_t size;
121
+ uint8_t is_text;
122
+ VALUE io;
123
+ } iodine_msg2ruby_s;
124
+
125
+ static void *iodine_ws_fire_message(void *msg_) {
126
+ iodine_msg2ruby_s *msg = msg_;
127
+ VALUE data = rb_enc_str_new(
128
+ msg->data, msg->size,
129
+ (msg->is_text ? rb_utf8_encoding() : rb_ascii8bit_encoding()));
130
+ iodine_connection_fire_event(msg->io, IODINE_CONNECTION_ON_MESSAGE, data);
131
+ return NULL;
132
+ }
133
+
134
+ static void iodine_ws_on_message(ws_s *ws, char *data, size_t size,
135
+ uint8_t is_text) {
136
+ iodine_msg2ruby_s msg = {
137
+ .data = data,
138
+ .size = size,
139
+ .is_text = is_text,
140
+ .io = (VALUE)websocket_udata(ws),
141
+ };
142
+ IodineCaller.leaveGVL(iodine_ws_fire_message, &msg);
143
+ }
144
+ /**
145
+ * The (optional) on_open callback will be called once the websocket
146
+ * connection is established and before is is registered with `facil`, so no
147
+ * `on_message` events are raised before `on_open` returns.
148
+ */
149
+ static void iodine_ws_on_open(ws_s *ws) {
150
+ VALUE h = (VALUE)websocket_udata(ws);
151
+ iodine_connection_s *c = iodine_connection_CData(h);
152
+ c->arg = ws;
153
+ c->uuid = websocket_uuid(ws);
154
+ iodine_connection_fire_event(h, IODINE_CONNECTION_ON_OPEN, Qnil);
155
+ }
156
+ /**
157
+ * The (optional) on_ready callback will be after a the underlying socket's
158
+ * buffer changes it's state from full to empty.
159
+ *
160
+ * If the socket's buffer is never used, the callback is never called.
161
+ */
162
+ static void iodine_ws_on_ready(ws_s *ws) {
163
+ iodine_connection_fire_event((VALUE)websocket_udata(ws),
164
+ IODINE_CONNECTION_ON_DRAINED, Qnil);
165
+ }
166
+ /**
167
+ * The (optional) on_shutdown callback will be called if a websocket
168
+ * connection is still open while the server is shutting down (called before
169
+ * `on_close`).
170
+ */
171
+ static void iodine_ws_on_shutdown(ws_s *ws) {
172
+ iodine_connection_fire_event((VALUE)websocket_udata(ws),
173
+ IODINE_CONNECTION_ON_SHUTDOWN, Qnil);
174
+ }
175
+ /**
176
+ * The (optional) on_close callback will be called once a websocket connection
177
+ * is terminated or failed to be established.
178
+ *
179
+ * The `uuid` is the connection's unique ID that can identify the Websocket. A
180
+ * value of `uuid == 0` indicates the Websocket connection wasn't established
181
+ * (an error occured).
182
+ *
183
+ * The `udata` is the user data as set during the upgrade or using the
184
+ * `websocket_udata_set` function.
185
+ */
186
+ static void iodine_ws_on_close(intptr_t uuid, void *udata) {
187
+ iodine_connection_fire_event((VALUE)udata, IODINE_CONNECTION_ON_CLOSE, Qnil);
188
+ (void)uuid;
189
+ }
190
+
191
+ static void iodine_ws_attach(http_s *h, VALUE handler, VALUE env) {
192
+ VALUE io =
193
+ iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL,
194
+ .handler = handler, .env = env, .uuid = 0);
195
+ if (io == Qnil)
196
+ return;
197
+
198
+ http_upgrade2ws(.http = h, .on_message = iodine_ws_on_message,
199
+ .on_open = iodine_ws_on_open, .on_ready = iodine_ws_on_ready,
200
+ .on_shutdown = iodine_ws_on_shutdown,
201
+ .on_close = iodine_ws_on_close, .udata = (void *)io);
202
+ }
203
+
204
+ /* *****************************************************************************
205
+ SSE support
206
+ ***************************************************************************** */
207
+
208
+ static void iodine_sse_on_ready(http_sse_s *sse) {
209
+ iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_DRAINED,
210
+ Qnil);
211
+ }
212
+
213
+ static void iodine_sse_on_shutdown(http_sse_s *sse) {
214
+ iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_SHUTDOWN,
215
+ Qnil);
216
+ }
217
+ static void iodine_sse_on_close(http_sse_s *sse) {
218
+ iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_CLOSE,
219
+ Qnil);
220
+ }
221
+
222
+ static void iodine_sse_on_open(http_sse_s *sse) {
223
+ VALUE h = (VALUE)sse->udata;
224
+ iodine_connection_s *c = iodine_connection_CData(h);
225
+ c->arg = sse;
226
+ c->uuid = http_sse2uuid(sse);
227
+ iodine_connection_fire_event(h, IODINE_CONNECTION_ON_OPEN, Qnil);
228
+ sse->on_ready = iodine_sse_on_ready;
229
+ evio_add_write(sock_uuid2fd(c->uuid), (void *)c->uuid);
230
+ }
231
+
232
+ static void iodine_sse_attach(http_s *h, VALUE handler, VALUE env) {
233
+ VALUE io = iodine_connection_new(.type = IODINE_CONNECTION_SSE, .arg = NULL,
234
+ .handler = handler, .env = env, .uuid = 0);
235
+ if (io == Qnil)
236
+ return;
237
+
238
+ http_upgrade2sse(h, .on_open = iodine_sse_on_open,
239
+ .on_ready = NULL /* will be set after the on_open */,
240
+ .on_shutdown = iodine_sse_on_shutdown,
241
+ .on_close = iodine_sse_on_close, .udata = (void *)io);
242
+ }
243
+
107
244
  /* *****************************************************************************
108
245
  Copying data from the C request to the Rack's ENV
109
246
  ***************************************************************************** */
@@ -164,7 +301,7 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
164
301
  env = rb_hash_dup(env_template_no_upgrade);
165
302
  break;
166
303
  }
167
- Registry.add(env);
304
+ IodineStore.add(env);
168
305
 
169
306
  fio_cstr_s tmp;
170
307
  char *pos = NULL;
@@ -207,14 +344,6 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
207
344
  }
208
345
  }
209
346
 
210
- /* setup input IO + hijack support */
211
- {
212
- VALUE m;
213
- rb_hash_aset(env, R_INPUT, (m = IodineRackIO.create(h, env)));
214
- m = rb_obj_method(m, hijack_func_sym);
215
- rb_hash_aset(env, IODINE_R_HIJACK, m);
216
- }
217
-
218
347
  /* handle the HOST header, including the possible host:#### format*/
219
348
  static uint64_t host_hash = 0;
220
349
  if (!host_hash)
@@ -333,11 +462,11 @@ static int for_each_header_data(VALUE key, VALUE val, VALUE h_) {
333
462
  http_s *h = (http_s *)h_;
334
463
  // fprintf(stderr, "For_each - headers\n");
335
464
  if (TYPE(key) != T_STRING)
336
- key = RubyCaller.call(key, iodine_to_s_method_id);
465
+ key = IodineCaller.call(key, iodine_to_s_method_id);
337
466
  if (TYPE(key) != T_STRING)
338
467
  return ST_CONTINUE;
339
468
  if (TYPE(val) != T_STRING) {
340
- val = RubyCaller.call(val, iodine_to_s_method_id);
469
+ val = IodineCaller.call(val, iodine_to_s_method_id);
341
470
  if (TYPE(val) != T_STRING)
342
471
  return ST_STOP;
343
472
  }
@@ -392,7 +521,7 @@ static inline int ruby2c_response_send(iodine_http_request_handle_s *handle,
392
521
  if (handle->h->status < 200 || handle->h->status == 204 ||
393
522
  handle->h->status == 304) {
394
523
  if (rb_respond_to(body, close_method_id))
395
- RubyCaller.call(body, close_method_id);
524
+ IodineCaller.call(body, close_method_id);
396
525
  body = Qnil;
397
526
  handle->type = IODINE_HTTP_NONE;
398
527
  return 0;
@@ -420,7 +549,7 @@ static inline int ruby2c_response_send(iodine_http_request_handle_s *handle,
420
549
  (VALUE)handle->body);
421
550
  // we need to call `close` in case the object is an IO / BodyProxy
422
551
  if (rb_respond_to(body, close_method_id))
423
- RubyCaller.call(body, close_method_id);
552
+ IodineCaller.call(body, close_method_id);
424
553
  return 0;
425
554
  }
426
555
  return -1;
@@ -438,37 +567,54 @@ static inline int ruby2c_review_upgrade(iodine_http_request_handle_s *req,
438
567
  // send headers
439
568
  http_finish(h);
440
569
  // call the callback
441
- VALUE io_ruby = RubyCaller.call(rb_hash_aref(env, IODINE_R_HIJACK),
442
- iodine_call_proc_id);
443
- RubyCaller.call2(handler, iodine_call_proc_id, 1, &io_ruby);
570
+ VALUE io_ruby = IodineCaller.call(rb_hash_aref(env, IODINE_R_HIJACK),
571
+ iodine_call_proc_id);
572
+ IodineCaller.call2(handler, iodine_call_proc_id, 1, &io_ruby);
573
+ goto upgraded;
444
574
  } else if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_IO)) != Qnil) {
445
- // do nothing
446
- } else if (req->upgrade == IODINE_UPGRADE_WEBSOCKET &&
447
- ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil ||
448
- (handler = rb_hash_aref(env, UPGRADE_WEBSOCKET)) != Qnil)) {
449
- // use response as existing base for native websocket upgrade
450
- iodine_upgrade_websocket(h, handler);
451
- } else if (req->upgrade == IODINE_UPGRADE_SSE &&
452
- (handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
453
- // use response as existing base for SSE upgrade
454
- iodine_upgrade_sse(h, handler);
575
+ // do nothing, just cleanup
576
+ goto upgraded;
455
577
  } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil) {
456
- // hijack post headers (might be very bad)
457
- intptr_t uuid = http_hijack(h, NULL);
458
- // send headers
459
- http_finish(h);
460
- // upgrade protocol
461
- VALUE args[2] = {(ULONG2NUM(sock_uuid2fd(uuid))), handler};
462
- RubyCaller.call2(Iodine, attach_method_id, 2, args);
463
- // nothing left to do to prevent response processing.
578
+ goto tcp_ip_upgrade;
464
579
  } else {
465
- return 0;
580
+ switch (req->upgrade) {
581
+ case IODINE_UPGRADE_WEBSOCKET:
582
+ if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
583
+ // use response as existing base for native websocket upgrade
584
+ iodine_ws_attach(h, handler, env);
585
+ goto upgraded;
586
+ }
587
+ break;
588
+ case IODINE_UPGRADE_SSE:
589
+ if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
590
+ // use response as existing base for SSE upgrade
591
+ iodine_sse_attach(h, handler, env);
592
+ goto upgraded;
593
+ }
594
+ break;
595
+ default:
596
+ if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
597
+ tcp_ip_upgrade : {
598
+ // use response as existing base for raw TCP/IP upgrade
599
+ intptr_t uuid = http_hijack(h, NULL);
600
+ // send headers
601
+ http_finish(h);
602
+ // upgrade protocol to raw TCP/IP
603
+ iodine_tcp_attch_uuid(uuid, handler);
604
+ goto upgraded;
605
+ }
606
+ }
607
+ break;
608
+ }
466
609
  }
610
+ return 0;
611
+
612
+ upgraded:
467
613
  // get body object to close it (if needed)
468
614
  handler = rb_ary_entry(rbresponse, 2);
469
615
  // we need to call `close` in case the object is an IO / BodyProxy
470
616
  if (handler != Qnil && rb_respond_to(handler, close_method_id))
471
- RubyCaller.call(handler, close_method_id);
617
+ IodineCaller.call(handler, close_method_id);
472
618
  return 1;
473
619
  }
474
620
 
@@ -486,14 +632,17 @@ static inline void *iodine_handle_request_in_GVL(void *handle_) {
486
632
 
487
633
  // create / register env variable
488
634
  env = copy2env(handle);
489
- // will be used later
490
- VALUE tmp;
635
+ // create rack.io
636
+ VALUE tmp = IodineRackIO.create(h, env);
491
637
  // pass env variable to handler
492
- rbresponse = RubyCaller.call2((VALUE)h->udata, iodine_call_proc_id, 1, &env);
638
+ rbresponse =
639
+ IodineCaller.call2((VALUE)h->udata, iodine_call_proc_id, 1, &env);
640
+ // close rack.io
641
+ IodineRackIO.close(tmp);
493
642
  // test handler's return value
494
643
  if (rbresponse == 0 || rbresponse == Qnil)
495
644
  goto internal_error;
496
- Registry.add(rbresponse);
645
+ IodineStore.add(rbresponse);
497
646
 
498
647
  // set response status
499
648
  tmp = rb_ary_entry(rbresponse, 0);
@@ -519,7 +668,7 @@ static inline void *iodine_handle_request_in_GVL(void *handle_) {
519
668
  if (OBJ_FROZEN(response_headers)) {
520
669
  response_headers = rb_hash_dup(response_headers);
521
670
  }
522
- Registry.add(response_headers);
671
+ IodineStore.add(response_headers);
523
672
  handle->body = fiobj_str_new(RSTRING_PTR(xfiles), RSTRING_LEN(xfiles));
524
673
  handle->type = IODINE_HTTP_XSENDFILE;
525
674
  rb_hash_delete(response_headers, XSENDFILE);
@@ -527,7 +676,7 @@ static inline void *iodine_handle_request_in_GVL(void *handle_) {
527
676
  rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER);
528
677
  // review each header and write it to the response.
529
678
  rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h));
530
- Registry.remove(response_headers);
679
+ IodineStore.remove(response_headers);
531
680
  // send the file directly and finish
532
681
  return NULL;
533
682
  }
@@ -540,26 +689,26 @@ static inline void *iodine_handle_request_in_GVL(void *handle_) {
540
689
  if (ruby2c_response_send(handle, rbresponse, env))
541
690
  goto internal_error;
542
691
 
543
- Registry.remove(rbresponse);
544
- Registry.remove(env);
692
+ IodineStore.remove(rbresponse);
693
+ IodineStore.remove(env);
545
694
  return NULL;
546
695
 
547
696
  external_done:
548
- Registry.remove(rbresponse);
549
- Registry.remove(env);
697
+ IodineStore.remove(rbresponse);
698
+ IodineStore.remove(env);
550
699
  handle->type = IODINE_HTTP_NONE;
551
700
  return NULL;
552
701
 
553
702
  err_not_found:
554
- Registry.remove(rbresponse);
555
- Registry.remove(env);
703
+ IodineStore.remove(rbresponse);
704
+ IodineStore.remove(env);
556
705
  h->status = 404;
557
706
  handle->type = IODINE_HTTP_ERROR;
558
707
  return NULL;
559
708
 
560
709
  internal_error:
561
- Registry.remove(rbresponse);
562
- Registry.remove(env);
710
+ IodineStore.remove(rbresponse);
711
+ IodineStore.remove(env);
563
712
  h->status = 500;
564
713
  handle->type = IODINE_HTTP_ERROR;
565
714
  return NULL;
@@ -606,7 +755,8 @@ static void on_rack_request(http_s *h) {
606
755
  iodine_http_request_handle_s handle = (iodine_http_request_handle_s){
607
756
  .h = h, .upgrade = IODINE_UPGRADE_NONE,
608
757
  };
609
- RubyCaller.call_c((void *(*)(void *))iodine_handle_request_in_GVL, &handle);
758
+ IodineCaller.enterGVL((void *(*)(void *))iodine_handle_request_in_GVL,
759
+ &handle);
610
760
  iodine_perform_handle_action(handle);
611
761
  }
612
762
 
@@ -616,11 +766,13 @@ static void on_rack_upgrade(http_s *h, char *proto, size_t len) {
616
766
  handle.upgrade = IODINE_UPGRADE_WEBSOCKET;
617
767
  } else if (len == 3 && proto[0] == 's') {
618
768
  handle.upgrade = IODINE_UPGRADE_SSE;
619
- } else {
620
- http_send_error(h, 400);
621
- return;
622
769
  }
623
- RubyCaller.call_c(iodine_handle_request_in_GVL, &handle);
770
+ /* when we stop supporting custom Upgrade headers: */
771
+ // else {
772
+ // http_send_error(h, 400);
773
+ // return;
774
+ // }
775
+ IodineCaller.enterGVL(iodine_handle_request_in_GVL, &handle);
624
776
  iodine_perform_handle_action(handle);
625
777
  (void)proto;
626
778
  (void)len;
@@ -631,7 +783,7 @@ Listenninng to HTTP
631
783
  *****************************************************************************
632
784
  */
633
785
 
634
- void *iodine_print_http_msg2_in_gvl(void *d_) {
786
+ void *iodine_print_http_msg_in_gvl(void *d_) {
635
787
  // Write message
636
788
  struct {
637
789
  VALUE www;
@@ -646,64 +798,26 @@ void *iodine_print_http_msg2_in_gvl(void *d_) {
646
798
  return NULL;
647
799
  }
648
800
 
649
- void *iodine_print_http_msg_in_gvl(void *d_) {
650
- // Write message
651
- VALUE iodine_version = rb_const_get(Iodine, rb_intern("VERSION"));
652
- VALUE ruby_version = rb_const_get(Iodine, rb_intern("RUBY_VERSION"));
653
- struct {
654
- VALUE www;
655
- VALUE port;
656
- } *arg = d_;
657
- if (arg->www) {
658
- fprintf(stderr,
659
- "\nStarting up Iodine HTTP Server on port %s:\n"
660
- " * Ruby v.%s\n * Iodine v.%s \n"
661
- " * Serving static files from %s\n\n",
662
- StringValueCStr(arg->port), StringValueCStr(ruby_version),
663
- StringValueCStr(iodine_version), StringValueCStr(arg->www));
664
- } else {
665
- fprintf(stderr,
666
- "\nStarting up Iodine HTTP Server on port %s:\n"
667
- " * Ruby v.%s\n * Iodine v.%s \n\n",
668
- StringValueCStr(arg->port), StringValueCStr(ruby_version),
669
- StringValueCStr(iodine_version));
670
- }
671
-
672
- return NULL;
673
- }
674
-
675
- static void iodine_print_http_msg1(void *www, void *port) {
801
+ static void iodine_print_http_msg(void *www, void *port) {
676
802
  if (getpid() != facil_parent_pid())
677
803
  goto finish;
678
804
  struct {
679
805
  void *www;
680
806
  void *port;
681
807
  } data = {.www = www, .port = port};
682
- RubyCaller.call_c(iodine_print_http_msg_in_gvl, (void *)&data);
808
+ IodineCaller.enterGVL(iodine_print_http_msg_in_gvl, (void *)&data);
683
809
  finish:
684
810
  if (www) {
685
- Registry.remove((VALUE)www);
811
+ IodineStore.remove((VALUE)www);
686
812
  }
687
- Registry.remove((VALUE)port);
688
- }
689
- static void iodine_print_http_msg2(void *www, void *port) {
690
- if (getpid() != facil_parent_pid())
691
- goto finish;
692
- struct {
693
- void *www;
694
- void *port;
695
- } data = {.www = www, .port = port};
696
- RubyCaller.call_c(iodine_print_http_msg2_in_gvl, (void *)&data);
697
- finish:
698
- if (www) {
699
- Registry.remove((VALUE)www);
700
- }
701
- Registry.remove((VALUE)port);
813
+ IodineStore.remove((VALUE)port);
702
814
  }
703
815
 
704
816
  static void free_iodine_http(http_settings_s *s) {
705
- Registry.remove((VALUE)s->udata);
817
+ IodineStore.remove((VALUE)s->udata);
706
818
  }
819
+
820
+ // clang-format off
707
821
  /**
708
822
  Listens to incoming HTTP connections and handles incoming requests using the
709
823
  Rack specification.
@@ -714,6 +828,8 @@ specifications.
714
828
 
715
829
  Accepts a single Hash argument with the following properties:
716
830
 
831
+ (it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash)
832
+
717
833
  app:: the Rack application that handles incoming requests. Default: `nil`.
718
834
  port:: the port to listen to. Default: 3000.
719
835
  address:: the address to bind to. Default: binds to all possible addresses.
@@ -721,6 +837,7 @@ log:: enable response logging (Hijacked sockets aren't logged). Default: off.
721
837
  public:: The root public folder for static file service. Default: none.
722
838
  timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
723
839
  max_body:: The maximum body size for incoming HTTP messages. Default: ~50Mib.
840
+ max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib.
724
841
  max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
725
842
  ping:: The Websocket `ping` interval. Default: 40 seconds.
726
843
 
@@ -740,60 +857,89 @@ timeouts will be dynamically managed by Iodine. The `timeout` option is only
740
857
  relevant to HTTP/1.x connections.
741
858
  */
742
859
  VALUE iodine_http_listen(VALUE self, VALUE opt) {
743
- static int called_once = 0;
860
+ // clang-format on
744
861
  uint8_t log_http = 0;
745
862
  size_t ping = 0;
746
863
  size_t max_body = 0;
747
864
  size_t max_headers = 0;
748
865
  size_t max_msg = 0;
749
866
  Check_Type(opt, T_HASH);
867
+ /* copy from deafult hash */
868
+ /* test arguments */
750
869
  VALUE app = rb_hash_aref(opt, ID2SYM(rb_intern("app")));
751
870
  VALUE www = rb_hash_aref(opt, ID2SYM(rb_intern("public")));
752
871
  VALUE port = rb_hash_aref(opt, ID2SYM(rb_intern("port")));
753
872
  VALUE address = rb_hash_aref(opt, ID2SYM(rb_intern("address")));
754
873
  VALUE tout = rb_hash_aref(opt, ID2SYM(rb_intern("timeout")));
874
+ if (www == Qnil) {
875
+ www = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("public")));
876
+ }
877
+ if (port == Qnil) {
878
+ port = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("port")));
879
+ }
880
+ if (address == Qnil) {
881
+ address = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("address")));
882
+ }
883
+ if (tout == Qnil) {
884
+ tout = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("timeout")));
885
+ }
755
886
 
756
887
  VALUE tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_msg")));
888
+ if (tmp == Qnil) {
889
+ tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_msg")));
890
+ }
757
891
  if (tmp != Qnil && tmp != Qfalse) {
758
892
  Check_Type(tmp, T_FIXNUM);
759
893
  max_msg = FIX2ULONG(tmp);
760
894
  }
761
895
 
762
896
  tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_body")));
897
+ if (tmp == Qnil) {
898
+ tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_body")));
899
+ }
763
900
  if (tmp != Qnil && tmp != Qfalse) {
764
901
  Check_Type(tmp, T_FIXNUM);
765
902
  max_body = FIX2ULONG(tmp);
766
903
  }
767
904
  tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_headers")));
905
+ if (tmp == Qnil) {
906
+ tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("max_headers")));
907
+ }
768
908
  if (tmp != Qnil && tmp != Qfalse) {
769
909
  Check_Type(tmp, T_FIXNUM);
770
910
  max_headers = FIX2ULONG(tmp);
771
911
  }
772
912
 
773
913
  tmp = rb_hash_aref(opt, ID2SYM(rb_intern("ping")));
914
+ if (tmp == Qnil) {
915
+ tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("ping")));
916
+ }
774
917
  if (tmp != Qnil && tmp != Qfalse) {
775
918
  Check_Type(tmp, T_FIXNUM);
776
919
  ping = FIX2ULONG(tmp);
777
920
  }
778
921
  if (ping > 255) {
779
922
  fprintf(stderr, "Iodine Warning: Websocket timeout value "
780
- "is over 255 and is silently ignored.\n");
923
+ "is over 255 and will be ignored.\n");
781
924
  ping = 0;
782
925
  }
783
926
 
784
927
  tmp = rb_hash_aref(opt, ID2SYM(rb_intern("log")));
928
+ if (tmp == Qnil) {
929
+ tmp = rb_hash_aref(iodine_default_args, ID2SYM(rb_intern("log")));
930
+ }
785
931
  if (tmp != Qnil && tmp != Qfalse)
786
932
  log_http = 1;
787
933
 
788
934
  if ((app == Qnil || app == Qfalse) && (www == Qnil || www == Qfalse)) {
789
935
  fprintf(stderr, "Iodine Warning: HTTP without application or public folder "
790
- "(is silently ignored).\n");
936
+ "(ignored).\n");
791
937
  return Qfalse;
792
938
  }
793
939
 
794
940
  if ((www != Qnil && www != Qfalse)) {
795
941
  Check_Type(www, T_STRING);
796
- Registry.add(www);
942
+ IodineStore.add(www);
797
943
  rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
798
944
  rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
799
945
  support_xsendfile = 1;
@@ -822,16 +968,16 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
822
968
  "The `port` property MUST be either a String or a Number");
823
969
  if (RB_TYPE_P(port, T_FIXNUM))
824
970
  port = rb_funcall2(port, iodine_to_s_method_id, 0, NULL);
825
- Registry.add(port);
971
+ IodineStore.add(port);
826
972
  } else if (port == Qfalse)
827
973
  port = 0;
828
974
  else {
829
975
  port = rb_str_new("3000", 4);
830
- Registry.add(port);
976
+ IodineStore.add(port);
831
977
  }
832
978
 
833
979
  if ((app != Qnil && app != Qfalse))
834
- Registry.add(app);
980
+ IodineStore.add(app);
835
981
  else
836
982
  app = 0;
837
983
 
@@ -855,12 +1001,7 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
855
1001
  "static files.\n",
856
1002
  (port ? StringValueCStr(port) : "3000"));
857
1003
  }
858
- if (called_once) {
859
- defer(iodine_print_http_msg2, (www ? (void *)www : NULL), (void *)port);
860
- } else {
861
- called_once = 1;
862
- defer(iodine_print_http_msg1, (www ? (void *)www : NULL), (void *)port);
863
- }
1004
+ defer(iodine_print_http_msg, (www ? (void *)www : NULL), (void *)port);
864
1005
 
865
1006
  return Qtrue;
866
1007
  (void)self;
@@ -870,7 +1011,7 @@ static void initialize_env_template(void) {
870
1011
  if (env_template_no_upgrade)
871
1012
  return;
872
1013
  env_template_no_upgrade = rb_hash_new();
873
- rb_global_variable(&env_template_no_upgrade);
1014
+ IodineStore.add(env_template_no_upgrade);
874
1015
 
875
1016
  #define add_str_to_env(env, key, value) \
876
1017
  { \
@@ -890,10 +1031,6 @@ static void initialize_env_template(void) {
890
1031
  /* Set global template */
891
1032
  rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE_Q, Qnil);
892
1033
  rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE, Qnil);
893
- rb_hash_aset(env_template_no_upgrade, UPGRADE_WEBSOCKET_Q, Qnil);
894
- rb_hash_aset(env_template_no_upgrade, UPGRADE_WEBSOCKET, Qnil);
895
- rb_hash_aset(env_template_no_upgrade, UPGRADE_TCP_Q, Qnil);
896
- rb_hash_aset(env_template_no_upgrade, UPGRADE_TCP, Qnil);
897
1034
  {
898
1035
  /* add the rack.version */
899
1036
  static VALUE rack_version = 0;
@@ -927,26 +1064,32 @@ static void initialize_env_template(void) {
927
1064
 
928
1065
  /* WebSocket upgrade support */
929
1066
  env_template_websockets = rb_hash_dup(env_template_no_upgrade);
930
- rb_global_variable(&env_template_websockets);
931
- rb_hash_aset(env_template_websockets, UPGRADE_WEBSOCKET_Q, Qtrue);
1067
+ IodineStore.add(env_template_websockets);
932
1068
  rb_hash_aset(env_template_websockets, RACK_UPGRADE_Q, RACK_UPGRADE_WEBSOCKET);
933
- rb_hash_aset(env_template_websockets, UPGRADE_TCP_Q, Qtrue);
934
1069
 
935
1070
  /* SSE upgrade support */
936
1071
  env_template_sse = rb_hash_dup(env_template_no_upgrade);
937
- rb_global_variable(&env_template_sse);
1072
+ IodineStore.add(env_template_sse);
938
1073
  rb_hash_aset(env_template_sse, RACK_UPGRADE_Q, RACK_UPGRADE_SSE);
939
1074
 
940
1075
  #undef add_value_to_env
941
1076
  #undef add_str_to_env
942
1077
  }
1078
+
943
1079
  /* *****************************************************************************
944
1080
  Initialization
945
1081
  ***************************************************************************** */
946
1082
 
947
- void Iodine_init_http(void) {
1083
+ void iodine_init_http(void) {
1084
+
1085
+ rb_define_module_function(IodineModule, "listen2http", iodine_http_listen, 1);
1086
+
1087
+ /** Used by {listen2http} to set missing arguments. */
1088
+ iodine_default_args = rb_hash_new();
948
1089
 
949
- rb_define_module_function(Iodine, "listen2http", iodine_http_listen, 1);
1090
+ /** Used by {listen2http} to set missing arguments. */
1091
+ rb_const_set(IodineModule, rb_intern2("DEFAULT_HTTP_ARGS", 17),
1092
+ iodine_default_args);
950
1093
 
951
1094
  rack_autoset(REQUEST_METHOD);
952
1095
  rack_autoset(PATH_INFO);
@@ -972,22 +1115,22 @@ void Iodine_init_http(void) {
972
1115
  rack_set(IODINE_R_HIJACK, "rack.hijack");
973
1116
  rack_set(IODINE_R_HIJACK_CB, "iodine.hijack_cb");
974
1117
 
975
- rack_set(UPGRADE_TCP, "upgrade.tcp");
976
- rack_set(UPGRADE_WEBSOCKET, "upgrade.websocket");
977
-
978
- rack_set(UPGRADE_TCP_Q, "upgrade.tcp?");
979
- rack_set(UPGRADE_WEBSOCKET_Q, "upgrade.websocket?");
980
1118
  rack_set(RACK_UPGRADE, "rack.upgrade");
981
1119
  rack_set(RACK_UPGRADE_Q, "rack.upgrade?");
982
1120
  rack_set_sym(RACK_UPGRADE_SSE, "sse");
983
1121
  rack_set_sym(RACK_UPGRADE_WEBSOCKET, "websocket");
984
1122
 
1123
+ UPGRADE_TCP = IodineStore.add(rb_str_new("upgrade.tcp", 11));
1124
+
985
1125
  hijack_func_sym = ID2SYM(rb_intern("_hijack"));
986
1126
  close_method_id = rb_intern("close");
987
1127
  each_method_id = rb_intern("each");
988
1128
  attach_method_id = rb_intern("attach_fd");
1129
+ iodine_to_s_method_id = rb_intern("to_s");
1130
+ iodine_call_proc_id = rb_intern("call");
1131
+
1132
+ IodineUTF8Encoding = rb_enc_find("UTF-8");
1133
+ IodineBinaryEncoding = rb_enc_find("binary");
989
1134
 
990
- IodineRackIO.init();
991
1135
  initialize_env_template();
992
- Iodine_init_json();
993
1136
  }