iodine 0.4.19 → 0.5.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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
data/ext/iodine/iodine.c CHANGED
@@ -15,16 +15,24 @@ Feel free to copy, use and enjoy according to the license provided.
15
15
 
16
16
  VALUE Iodine;
17
17
  VALUE IodineBase;
18
- VALUE Iodine_Version;
18
+
19
+ VALUE iodine_force_var_id;
20
+ VALUE iodine_channel_var_id;
21
+ VALUE iodine_pattern_var_id;
22
+ VALUE iodine_text_var_id;
23
+ VALUE iodine_binary_var_id;
24
+ VALUE iodine_engine_var_id;
25
+ VALUE iodine_message_var_id;
19
26
 
20
27
  ID iodine_fd_var_id;
28
+ ID iodine_cdata_var_id;
21
29
  ID iodine_timeout_var_id;
22
30
  ID iodine_call_proc_id;
23
31
  ID iodine_new_func_id;
24
32
  ID iodine_on_open_func_id;
25
33
  ID iodine_on_message_func_id;
26
34
  ID iodine_on_data_func_id;
27
- ID iodine_on_ready_func_id;
35
+ ID iodine_on_drained_func_id;
28
36
  ID iodine_on_shutdown_func_id;
29
37
  ID iodine_on_close_func_id;
30
38
  ID iodine_ping_func_id;
@@ -149,17 +157,11 @@ static VALUE iodine_run(VALUE self) {
149
157
  /* *****************************************************************************
150
158
  Idling
151
159
  ***************************************************************************** */
152
- #include "fio_list.h"
160
+ #include "fio_llist.h"
153
161
  #include "spnlock.inc"
154
162
 
155
- typedef struct {
156
- fio_list_s node;
157
- VALUE block;
158
- } iodine_idle_block_s;
159
-
160
163
  static spn_lock_i iodine_on_idle_lock = SPN_LOCK_INIT;
161
- static fio_list_s iodine_on_idle_list =
162
- FIO_LIST_INIT_STATIC(iodine_on_idle_list);
164
+ static fio_ls_s iodine_on_idle_list = FIO_LS_INIT(iodine_on_idle_list);
163
165
 
164
166
  /**
165
167
  Schedules a single occuring event for the next idle cycle.
@@ -174,22 +176,20 @@ i.e.
174
176
  */
175
177
  VALUE iodine_sched_on_idle(VALUE self) {
176
178
  rb_need_block();
177
- iodine_idle_block_s *b = malloc(sizeof(*b));
178
- b->block = rb_block_proc();
179
- Registry.add(b->block);
179
+ VALUE block = rb_block_proc();
180
+ Registry.add(block);
180
181
  spn_lock(&iodine_on_idle_lock);
181
- fio_list_push(iodine_idle_block_s, node, iodine_on_idle_list, b);
182
+ fio_ls_push(&iodine_on_idle_list, (void *)block);
182
183
  spn_unlock(&iodine_on_idle_lock);
183
- return b->block;
184
+ return block;
184
185
  (void)self;
185
186
  }
186
187
 
187
188
  static void iodine_on_idle(void) {
188
- iodine_idle_block_s *b;
189
189
  spn_lock(&iodine_on_idle_lock);
190
- while ((b = fio_list_shift(iodine_idle_block_s, node, iodine_on_idle_list))) {
191
- defer(iodine_perform_deferred, (void *)b->block, NULL);
192
- free(b);
190
+ while (fio_ls_any(&iodine_on_idle_list)) {
191
+ VALUE block = (VALUE)fio_ls_shift(&iodine_on_idle_list);
192
+ defer(iodine_perform_deferred, (void *)block, NULL);
193
193
  }
194
194
  spn_unlock(&iodine_on_idle_lock);
195
195
  }
@@ -202,14 +202,17 @@ Running the server
202
202
 
203
203
  static volatile int sock_io_thread = 0;
204
204
  static pthread_t sock_io_pthread;
205
+ typedef struct {
206
+ size_t threads;
207
+ size_t processes;
208
+ } iodine_start_settings_s;
205
209
 
206
210
  static void *iodine_io_thread(void *arg) {
207
211
  (void)arg;
208
212
  struct timespec tm;
209
- // static const struct timespec tm = {.tv_nsec = 524288UL};
210
213
  while (sock_io_thread) {
211
214
  sock_flush_all();
212
- tm = (struct timespec){.tv_nsec = 524288UL, .tv_sec = 1};
215
+ tm = (struct timespec){.tv_nsec = 0, .tv_sec = 1};
213
216
  nanosleep(&tm, NULL);
214
217
  }
215
218
  return NULL;
@@ -224,39 +227,17 @@ static void iodine_join_io_thread(void) {
224
227
  pthread_join(sock_io_pthread, NULL);
225
228
  }
226
229
 
227
- static void *srv_start_no_gvl(void *_) {
228
- (void)(_);
229
- // collect requested settings
230
- VALUE rb_th_i = rb_iv_get(Iodine, "@threads");
231
- VALUE rb_pr_i = rb_iv_get(Iodine, "@processes");
232
- ssize_t threads = (TYPE(rb_th_i) == T_FIXNUM) ? FIX2LONG(rb_th_i) : 0;
233
- ssize_t processes = (TYPE(rb_pr_i) == T_FIXNUM) ? FIX2LONG(rb_pr_i) : 0;
234
- // print a warnning if settings are sub optimal
235
- #ifdef _SC_NPROCESSORS_ONLN
236
- size_t cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
237
- if (processes <= 0)
238
- processes = 0;
239
- if (threads <= 0)
240
- threads = 0;
241
-
242
- if (processes && threads && cpu_count > 0 &&
243
- (((size_t)processes << 1) < cpu_count ||
244
- (size_t)processes > (cpu_count << 1)))
245
- fprintf(stderr,
246
- "\n* Performance warnning:\n"
247
- " - This computer reports %lu available CPUs... "
248
- "they will not be fully utilized.\n",
249
- cpu_count);
250
- #else
251
- if (processes <= 0)
252
- processes = 0;
253
- if (threads <= 0)
254
- threads = 0;
255
- #endif
230
+ static void *srv_start_no_gvl(void *s_) {
231
+ iodine_start_settings_s *s = s_;
256
232
  sock_io_thread = 1;
257
233
  defer(iodine_start_io_thread, NULL, NULL);
258
234
  fprintf(stderr, "\n");
259
- facil_run(.threads = threads, .processes = processes,
235
+ if (s->processes == 1 || (s->processes == 0 && s->threads > 0)) {
236
+ /* single worker */
237
+ RubyCaller.call(Iodine, rb_intern("before_fork"));
238
+ RubyCaller.call(Iodine, rb_intern("after_fork"));
239
+ }
240
+ facil_run(.threads = s->threads, .processes = s->processes,
260
241
  .on_idle = iodine_on_idle, .on_finish = iodine_join_io_thread);
261
242
  return NULL;
262
243
  }
@@ -296,8 +277,13 @@ static int iodine_review_rack_app(void) {
296
277
  rb_ivar_get(rack, rb_intern("@ws_timeout")));
297
278
  rb_hash_aset(opt, ID2SYM(rb_intern("timeout")),
298
279
  rb_ivar_get(rack, rb_intern("@ws_timeout")));
299
- if (rb_funcall2(Iodine, rb_intern("listen2http"), 1, &opt) == Qfalse)
280
+ rb_hash_aset(opt, ID2SYM(rb_intern("max_headers")),
281
+ rb_ivar_get(rack, rb_intern("@max_headers")));
282
+ if (rb_funcall2(Iodine, rb_intern("listen2http"), 1, &opt) == Qfalse) {
283
+ Registry.remove(opt);
300
284
  return -1;
285
+ }
286
+ Registry.remove(opt);
301
287
  return 0;
302
288
  }
303
289
 
@@ -308,12 +294,32 @@ Starts the Iodine event loop. This will hang the thread until an interrupt
308
294
  Returns the Iodine module.
309
295
  */
310
296
  static VALUE iodine_start(VALUE self) {
297
+ /* re-register the Rack::Push namespace to point at Iodine */
298
+ if (rb_const_defined(rb_cObject, rb_intern("Rack"))) {
299
+ VALUE rack = rb_const_get(rb_cObject, rb_intern("Rack"));
300
+ if (rack != Qnil) {
301
+ if (rb_const_defined(rack, rb_intern("PubSub"))) {
302
+ rb_const_remove(rack, rb_intern("PubSub"));
303
+ }
304
+ rb_const_set(rack, rb_intern("PubSub"), Iodine);
305
+ }
306
+ }
311
307
  /* for the special Iodine::Rack object and backwards compatibility */
312
308
  if (iodine_review_rack_app()) {
313
309
  fprintf(stderr, "ERROR: (iodine) cann't start Iodine::Rack.\n");
314
310
  return Qnil;
315
311
  }
316
- rb_thread_call_without_gvl2(srv_start_no_gvl, (void *)self, NULL, NULL);
312
+
313
+ VALUE rb_th_i = rb_iv_get(Iodine, "@threads");
314
+ VALUE rb_pr_i = rb_iv_get(Iodine, "@processes");
315
+
316
+ iodine_start_settings_s s = {
317
+ .threads = ((TYPE(rb_th_i) == T_FIXNUM) ? FIX2INT(rb_th_i) : 0),
318
+ .processes = ((TYPE(rb_pr_i) == T_FIXNUM) ? FIX2INT(rb_pr_i) : 0)};
319
+
320
+ RubyCaller.set_gvl_state(1);
321
+ RubyCaller.leave_gvl(srv_start_no_gvl, (void *)&s);
322
+
317
323
  return self;
318
324
  }
319
325
 
@@ -342,39 +348,52 @@ static void patch_env(void) {
342
348
  #ifdef __APPLE__
343
349
  /* patch for dealing with the High Sierra `fork` limitations */
344
350
  void *obj_c_runtime = dlopen("Foundation.framework/Foundation", RTLD_LAZY);
351
+ (void)obj_c_runtime;
345
352
  #endif
346
353
  }
347
354
 
348
- ////////////////////////////////////////////////////////////////////////
349
- // Ruby loads the library and invokes the Init_<lib_name> function...
350
- //
351
- // Here we connect all the C code to the Ruby interface, completing the bridge
352
- // between Lib-Server and Ruby.
355
+ /* *****************************************************************************
356
+ Ruby loads the library and invokes the Init_<lib_name> function...
357
+
358
+ Here we connect all the C code to the Ruby interface, completing the bridge
359
+ between Lib-Server and Ruby.
360
+ ***************************************************************************** */
353
361
  void Init_iodine(void) {
362
+ // Set GVL for main thread
363
+ RubyCaller.set_gvl_state(1);
354
364
  // load any environment specific patches
355
365
  patch_env();
356
366
  // initialize globally used IDs, for faster access to the Ruby layer.
357
- iodine_fd_var_id = rb_intern("scrtfd");
367
+ iodine_buff_var_id = rb_intern("scrtbuffer");
358
368
  iodine_call_proc_id = rb_intern("call");
369
+ iodine_cdata_var_id = rb_intern("iodine_cdata");
370
+ iodine_fd_var_id = rb_intern("iodine_fd");
359
371
  iodine_new_func_id = rb_intern("new");
360
- iodine_on_open_func_id = rb_intern("on_open");
361
- iodine_on_message_func_id = rb_intern("on_message");
372
+ iodine_on_close_func_id = rb_intern("on_close");
362
373
  iodine_on_data_func_id = rb_intern("on_data");
374
+ iodine_on_message_func_id = rb_intern("on_message");
375
+ iodine_on_open_func_id = rb_intern("on_open");
376
+ iodine_on_drained_func_id = rb_intern("on_drained");
363
377
  iodine_on_shutdown_func_id = rb_intern("on_shutdown");
364
- iodine_on_close_func_id = rb_intern("on_close");
365
- iodine_on_ready_func_id = rb_intern("on_ready");
366
378
  iodine_ping_func_id = rb_intern("ping");
367
- iodine_buff_var_id = rb_intern("scrtbuffer");
368
379
  iodine_timeout_var_id = rb_intern("@timeout");
369
- iodine_to_s_method_id = rb_intern("to_s");
370
380
  iodine_to_i_func_id = rb_intern("to_i");
381
+ iodine_to_s_method_id = rb_intern("to_s");
382
+
383
+ iodine_binary_var_id = ID2SYM(rb_intern("binary"));
384
+ iodine_channel_var_id = ID2SYM(rb_intern("channel"));
385
+ iodine_engine_var_id = ID2SYM(rb_intern("engine"));
386
+ iodine_force_var_id = ID2SYM(rb_intern("encoding"));
387
+ iodine_message_var_id = ID2SYM(rb_intern("message"));
388
+ iodine_pattern_var_id = ID2SYM(rb_intern("pattern"));
389
+ iodine_text_var_id = ID2SYM(rb_intern("text"));
371
390
 
372
391
  IodineBinaryEncodingIndex = rb_enc_find_index("binary");
373
392
  IodineUTF8EncodingIndex = rb_enc_find_index("UTF-8");
374
393
  IodineBinaryEncoding = rb_enc_find("binary");
375
394
  IodineUTF8Encoding = rb_enc_find("UTF-8");
376
395
 
377
- // The core Iodine module wraps libserver functionality and little more.
396
+ // The core Iodine module wraps facil.io functionality and little more.
378
397
  Iodine = rb_define_module("Iodine");
379
398
 
380
399
  // the Iodine singleton functions
@@ -386,8 +405,7 @@ void Init_iodine(void) {
386
405
  rb_define_module_function(Iodine, "run_every", iodine_run_every, -1);
387
406
  rb_define_module_function(Iodine, "on_idle", iodine_sched_on_idle, 0);
388
407
 
389
- // Every Protocol (and Server?) instance will hold a reference to the server
390
- // define the Server Ruby class.
408
+ /// Iodine::Base is for internal use.
391
409
  IodineBase = rb_define_module_under(Iodine, "Base");
392
410
  rb_define_module_function(IodineBase, "db_print_registry",
393
411
  iodine_print_registry, 0);
data/ext/iodine/iodine.h CHANGED
@@ -30,22 +30,30 @@ Feel free to copy, use and enjoy according to the license provided.
30
30
 
31
31
  extern VALUE Iodine;
32
32
  extern VALUE IodineBase;
33
- extern VALUE Iodine_Version;
34
33
 
35
- extern ID iodine_fd_var_id;
36
- extern ID iodine_timeout_var_id;
34
+ extern VALUE iodine_binary_var_id;
35
+ extern VALUE iodine_channel_var_id;
36
+ extern VALUE iodine_engine_var_id;
37
+ extern VALUE iodine_force_var_id;
38
+ extern VALUE iodine_message_var_id;
39
+ extern VALUE iodine_pattern_var_id;
40
+ extern VALUE iodine_text_var_id;
41
+
42
+ extern ID iodine_buff_var_id;
37
43
  extern ID iodine_call_proc_id;
44
+ extern ID iodine_cdata_var_id;
45
+ extern ID iodine_fd_var_id;
38
46
  extern ID iodine_new_func_id;
39
- extern ID iodine_on_open_func_id;
40
- extern ID iodine_on_message_func_id;
47
+ extern ID iodine_on_close_func_id;
41
48
  extern ID iodine_on_data_func_id;
42
- extern ID iodine_on_ready_func_id;
49
+ extern ID iodine_on_message_func_id;
50
+ extern ID iodine_on_open_func_id;
51
+ extern ID iodine_on_drained_func_id;
43
52
  extern ID iodine_on_shutdown_func_id;
44
- extern ID iodine_on_close_func_id;
45
53
  extern ID iodine_ping_func_id;
46
- extern ID iodine_buff_var_id;
47
- extern ID iodine_to_s_method_id;
54
+ extern ID iodine_timeout_var_id;
48
55
  extern ID iodine_to_i_func_id;
56
+ extern ID iodine_to_s_method_id;
49
57
 
50
58
  extern rb_encoding *IodineBinaryEncoding;
51
59
  extern rb_encoding *IodineUTF8Encoding;
@@ -53,10 +61,17 @@ extern int IodineBinaryEncodingIndex;
53
61
  extern int IodineUTF8EncodingIndex;
54
62
 
55
63
  UNUSED_FUNC static inline void iodine_set_fd(VALUE handler, intptr_t fd) {
56
- rb_ivar_set(handler, iodine_fd_var_id, LONG2NUM((long)fd));
64
+ rb_ivar_set(handler, iodine_fd_var_id, LL2NUM((long)fd));
57
65
  }
58
66
  UNUSED_FUNC static inline intptr_t iodine_get_fd(VALUE handler) {
59
- return ((intptr_t)NUM2LONG(rb_ivar_get(handler, iodine_fd_var_id)));
67
+ return ((intptr_t)NUM2LL(rb_ivar_get(handler, iodine_fd_var_id)));
68
+ }
69
+
70
+ UNUSED_FUNC static inline void iodine_set_cdata(VALUE handler, void *protocol) {
71
+ rb_ivar_set(handler, iodine_cdata_var_id, ULL2NUM((uintptr_t)protocol));
72
+ }
73
+ UNUSED_FUNC static inline void *iodine_get_cdata(VALUE handler) {
74
+ return ((void *)NUM2ULL(rb_ivar_get(handler, iodine_cdata_var_id)));
60
75
  }
61
76
 
62
77
  #endif
@@ -140,13 +140,14 @@ static VALUE date_str(int argc, VALUE *argv, VALUE self) {
140
140
  if (TYPE(argv[0]) != T_FIXNUM)
141
141
  argv[0] = rb_funcallv(argv[0], iodine_to_i_func_id, 0, NULL);
142
142
  Check_Type(argv[0], T_FIXNUM);
143
- last_tick = FIX2ULONG(argv[0]) ? FIX2ULONG(argv[0]) : facil_last_tick();
143
+ last_tick =
144
+ FIX2ULONG(argv[0]) ? FIX2ULONG(argv[0]) : facil_last_tick().tv_sec;
144
145
  } else
145
- last_tick = facil_last_tick();
146
+ last_tick = facil_last_tick().tv_sec;
146
147
  VALUE str = rb_str_buf_new(32);
147
148
  struct tm tm;
148
149
 
149
- http_gmtime(&last_tick, &tm);
150
+ http_gmtime(last_tick, &tm);
150
151
  size_t len = http_date2str(RSTRING_PTR(str), &tm);
151
152
  rb_str_set_len(str, len);
152
153
  return str;
@@ -168,11 +169,11 @@ Since Iodine uses time caching within it's reactor, using the default value
168
169
  static VALUE iodine_rfc2822(VALUE self, VALUE rtm) {
169
170
  time_t last_tick;
170
171
  rtm = rb_funcallv(rtm, iodine_to_i_func_id, 0, NULL);
171
- last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : facil_last_tick();
172
+ last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : facil_last_tick().tv_sec;
172
173
  VALUE str = rb_str_buf_new(34);
173
174
  struct tm tm;
174
175
 
175
- http_gmtime(&last_tick, &tm);
176
+ http_gmtime(last_tick, &tm);
176
177
  size_t len = http_date2rfc2822(RSTRING_PTR(str), &tm);
177
178
  rb_str_set_len(str, len);
178
179
  return str;
@@ -194,11 +195,11 @@ Since Iodine uses time caching within it's reactor, using the default value
194
195
  static VALUE iodine_rfc2109(VALUE self, VALUE rtm) {
195
196
  time_t last_tick;
196
197
  rtm = rb_funcallv(rtm, iodine_to_i_func_id, 0, NULL);
197
- last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : facil_last_tick();
198
+ last_tick = FIX2ULONG(rtm) ? FIX2ULONG(rtm) : facil_last_tick().tv_sec;
198
199
  VALUE str = rb_str_buf_new(32);
199
200
  struct tm tm;
200
201
 
201
- http_gmtime(&last_tick, &tm);
202
+ http_gmtime(last_tick, &tm);
202
203
  size_t len = http_date2rfc2109(RSTRING_PTR(str), &tm);
203
204
  rb_str_set_len(str, len);
204
205
  return str;
@@ -5,22 +5,26 @@ License: MIT
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
7
  #include "iodine_http.h"
8
+ #include "fio_mem.h"
9
+ #include "http.h"
10
+ #include "iodine_json.h"
8
11
  #include "iodine_websockets.h"
9
12
  #include "rb-rack-io.h"
10
13
 
11
14
  #include <arpa/inet.h>
15
+ #include <ctype.h>
16
+ #include <stdlib.h>
17
+ #include <string.h>
12
18
  #include <sys/socket.h>
13
19
 
14
20
  /* *****************************************************************************
15
21
  Available Globals
16
22
  ***************************************************************************** */
17
- VALUE IodineHTTP;
23
+ static VALUE IodineHTTP;
18
24
 
19
25
  typedef struct {
20
26
  VALUE app;
21
27
  VALUE env;
22
- unsigned long max_msg : 56;
23
- unsigned ping : 8;
24
28
  } iodine_http_settings_s;
25
29
 
26
30
  /* these three are used also by rb-rack-io.c */
@@ -28,23 +32,35 @@ VALUE IODINE_R_HIJACK;
28
32
  VALUE IODINE_R_HIJACK_IO;
29
33
  VALUE IODINE_R_HIJACK_CB;
30
34
 
31
- VALUE UPGRADE_TCP;
32
- VALUE UPGRADE_TCP_Q;
33
- VALUE UPGRADE_WEBSOCKET;
34
- VALUE UPGRADE_WEBSOCKET_Q;
35
+ static VALUE UPGRADE_TCP;
36
+ static VALUE UPGRADE_TCP_Q;
37
+ static VALUE UPGRADE_WEBSOCKET;
38
+ static VALUE UPGRADE_WEBSOCKET_Q;
39
+ static VALUE RACK_UPGRADE;
40
+ static VALUE RACK_UPGRADE_Q;
41
+ static VALUE RACK_UPGRADE_SSE;
42
+ static VALUE RACK_UPGRADE_WEBSOCKET;
35
43
 
36
44
  static VALUE hijack_func_sym;
37
- static ID to_fixnum_func_id;
38
45
  static ID close_method_id;
39
46
  static ID each_method_id;
40
47
  static ID attach_method_id;
41
48
 
49
+ static VALUE env_template_no_upgrade;
50
+ static VALUE env_template_websockets;
51
+ static VALUE env_template_sse;
52
+
53
+ static uint8_t support_xsendfile = 0;
54
+
42
55
  #define rack_declare(rack_name) static VALUE rack_name
43
56
 
44
57
  #define rack_set(rack_name, str) \
45
58
  (rack_name) = rb_enc_str_new((str), strlen((str)), IodineBinaryEncoding); \
46
59
  rb_global_variable(&(rack_name)); \
47
60
  rb_obj_freeze(rack_name);
61
+ #define rack_set_sym(rack_name, sym) \
62
+ (rack_name) = rb_id2sym(rb_intern2((sym), strlen((sym)))); \
63
+ rb_global_variable(&(rack_name));
48
64
 
49
65
  #define rack_autoset(rack_name) rack_set((rack_name), #rack_name)
50
66
 
@@ -67,9 +83,27 @@ rack_declare(CONTENT_TYPE);
67
83
  rack_declare(R_URL_SCHEME); // rack.url_scheme
68
84
  rack_declare(R_INPUT); // rack.input
69
85
  rack_declare(XSENDFILE); // for X-Sendfile support
86
+ rack_declare(XSENDFILE_TYPE); // for X-Sendfile support
87
+ rack_declare(XSENDFILE_TYPE_HEADER); // for X-Sendfile support
70
88
  rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support
71
- // rack_declare(IODINE_R_HIJACK); // rack.hijack
72
- // rack_declare(IODINE_R_HIJACK_CB);// rack.hijack_io
89
+
90
+ /* used internally to handle requests */
91
+ typedef struct {
92
+ http_s *h;
93
+ FIOBJ body;
94
+ enum iodine_http_response_type_enum {
95
+ IODINE_HTTP_NONE,
96
+ IODINE_HTTP_SENDBODY,
97
+ IODINE_HTTP_XSENDFILE,
98
+ IODINE_HTTP_EMPTY,
99
+ IODINE_HTTP_ERROR,
100
+ } type;
101
+ enum iodine_upgrade_type_enum {
102
+ IODINE_UPGRADE_NONE = 0,
103
+ IODINE_UPGRADE_WEBSOCKET,
104
+ IODINE_UPGRADE_SSE,
105
+ } upgrade;
106
+ } iodine_http_request_handle_s;
73
107
 
74
108
  /* *****************************************************************************
75
109
  Copying data from the C request to the Rack's ENV
@@ -77,116 +111,184 @@ Copying data from the C request to the Rack's ENV
77
111
 
78
112
  #define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? ((c) & ~32) : (c))
79
113
 
80
- static inline VALUE copy2env(http_request_s *request, VALUE template) {
81
- VALUE env = rb_hash_dup(template);
114
+ int iodine_copy2env_task(FIOBJ o, void *env_) {
115
+ VALUE env = (VALUE)env_;
116
+ FIOBJ name = fiobj_hash_key_in_loop();
117
+ fio_cstr_s tmp = fiobj_obj2cstr(name);
118
+ VALUE hname = (VALUE)0;
119
+ if (tmp.len > 59) {
120
+ char *buf = fio_malloc(tmp.len + 5);
121
+ memcpy(buf, "HTTP_", 5);
122
+ for (size_t i = 0; i < tmp.len; ++i) {
123
+ buf[i + 5] = (tmp.data[i] == '-') ? '_' : to_upper(tmp.data[i]);
124
+ }
125
+ hname = rb_enc_str_new(buf, tmp.len + 5, IodineBinaryEncoding);
126
+ fio_free(buf);
127
+ } else {
128
+ char buf[64];
129
+ memcpy(buf, "HTTP_", 5);
130
+ for (size_t i = 0; i < tmp.len; ++i) {
131
+ buf[i + 5] = (tmp.data[i] == '-') ? '_' : to_upper(tmp.data[i]);
132
+ }
133
+ hname = rb_enc_str_new(buf, tmp.len + 5, IodineBinaryEncoding);
134
+ }
135
+
136
+ if (FIOBJ_TYPE_IS(o, FIOBJ_T_STRING)) {
137
+ tmp = fiobj_obj2cstr(o);
138
+ rb_hash_aset(env, hname,
139
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
140
+
141
+ } else {
142
+ /* it's an array */
143
+ VALUE ary = rb_ary_new();
144
+ rb_hash_aset(env, hname, ary);
145
+ size_t count = fiobj_ary_count(o);
146
+ for (size_t i = 0; i < count; ++i) {
147
+ tmp = fiobj_obj2cstr(fiobj_ary_index(o, i));
148
+ rb_ary_push(ary, rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
149
+ }
150
+ }
151
+ return 0;
152
+ }
153
+ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
154
+ VALUE env;
155
+ http_s *h = handle->h;
156
+ switch (handle->upgrade) {
157
+ case IODINE_UPGRADE_WEBSOCKET:
158
+ env = rb_hash_dup(env_template_websockets);
159
+ break;
160
+ case IODINE_UPGRADE_SSE:
161
+ env = rb_hash_dup(env_template_sse);
162
+ break;
163
+ case IODINE_UPGRADE_NONE:
164
+ env = rb_hash_dup(env_template_no_upgrade);
165
+ break;
166
+ }
82
167
  Registry.add(env);
83
- VALUE hname; /* will be used later, both as tmp and to iterate header names */
168
+
169
+ fio_cstr_s tmp;
84
170
  char *pos = NULL;
85
- const char *reader = NULL;
86
171
  /* Copy basic data */
172
+ tmp = fiobj_obj2cstr(h->method);
87
173
  rb_hash_aset(env, REQUEST_METHOD,
88
- rb_enc_str_new(request->method, request->method_len,
89
- IodineBinaryEncoding));
90
-
91
- rb_hash_aset(
92
- env, PATH_INFO,
93
- rb_enc_str_new(request->path, request->path_len, IodineBinaryEncoding));
94
- rb_hash_aset(env, QUERY_STRING,
95
- (request->query
96
- ? rb_enc_str_new(request->query, request->query_len,
97
- IodineBinaryEncoding)
98
- : QUERY_ESTRING));
99
- rb_hash_aset(env, QUERY_STRING,
100
- (request->query
101
- ? rb_enc_str_new(request->query, request->query_len,
102
- IodineBinaryEncoding)
103
- : QUERY_ESTRING));
104
-
105
- hname = rb_enc_str_new(request->version, request->version_len,
106
- IodineBinaryEncoding);
107
- rb_hash_aset(env, SERVER_PROTOCOL, hname);
108
- rb_hash_aset(env, HTTP_VERSION, hname);
109
-
110
- // Suppoer for Ruby web-console.
111
- hname = rb_str_buf_new(64);
112
- sock_peer_addr_s addrinfo = sock_peer_addr(request->fd);
113
- if (addrinfo.addrlen &&
114
- inet_ntop(
115
- addrinfo.addr->sa_family,
116
- addrinfo.addr->sa_family == AF_INET
117
- ? (void *)&((struct sockaddr_in *)addrinfo.addr)->sin_addr
118
- : (void *)&((struct sockaddr_in6 *)addrinfo.addr)->sin6_addr,
119
- RSTRING_PTR(hname), 64)) {
120
- rb_str_set_len(hname, strlen(RSTRING_PTR(hname)));
121
- rb_hash_aset(env, REMOTE_ADDR, hname);
174
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
175
+ tmp = fiobj_obj2cstr(h->path);
176
+ rb_hash_aset(env, PATH_INFO,
177
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
178
+ if (h->query) {
179
+ tmp = fiobj_obj2cstr(h->query);
180
+ rb_hash_aset(env, QUERY_STRING,
181
+ tmp.len
182
+ ? rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)
183
+ : QUERY_ESTRING);
184
+ } else {
185
+ rb_hash_aset(env, QUERY_STRING, QUERY_ESTRING);
186
+ }
187
+ {
188
+ // HTTP version appears twice
189
+ tmp = fiobj_obj2cstr(h->version);
190
+ VALUE hname = rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding);
191
+ rb_hash_aset(env, SERVER_PROTOCOL, hname);
192
+ rb_hash_aset(env, HTTP_VERSION, hname);
122
193
  }
123
194
 
124
- /* setup input IO + hijack support */
125
- rb_hash_aset(env, R_INPUT, (hname = IodineRackIO.create(request, env)));
126
-
127
- /* publish upgrade support */
128
- if (request->upgrade) {
129
- rb_hash_aset(env, UPGRADE_WEBSOCKET_Q, Qtrue);
130
- rb_hash_aset(env, UPGRADE_TCP_Q, Qtrue);
195
+ { // Support for Ruby web-console.
196
+ char buf[64];
197
+ buf[63] = 0;
198
+ sock_peer_addr_s addrinfo = http_peer_addr(h);
199
+ if (addrinfo.addrlen &&
200
+ inet_ntop(
201
+ addrinfo.addr->sa_family,
202
+ addrinfo.addr->sa_family == AF_INET
203
+ ? (void *)&((struct sockaddr_in *)addrinfo.addr)->sin_addr
204
+ : (void *)&((struct sockaddr_in6 *)addrinfo.addr)->sin6_addr,
205
+ buf, 64)) {
206
+ rb_hash_aset(env, REMOTE_ADDR, rb_str_new(buf, strlen(buf)));
207
+ }
131
208
  }
132
209
 
133
- hname = rb_obj_method(hname, hijack_func_sym);
134
- rb_hash_aset(env, IODINE_R_HIJACK, hname);
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
+ }
135
217
 
136
218
  /* handle the HOST header, including the possible host:#### format*/
137
- pos = (char *)request->host;
219
+ static uint64_t host_hash = 0;
220
+ if (!host_hash)
221
+ host_hash = fio_siphash("host", 4);
222
+ tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, host_hash));
223
+ pos = tmp.data;
138
224
  while (*pos && *pos != ':')
139
225
  pos++;
140
226
  if (*pos == 0) {
141
- rb_hash_aset(
142
- env, SERVER_NAME,
143
- rb_enc_str_new(request->host, request->host_len, IodineBinaryEncoding));
227
+ rb_hash_aset(env, SERVER_NAME,
228
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
144
229
  rb_hash_aset(env, SERVER_PORT, QUERY_ESTRING);
145
230
  } else {
146
- rb_hash_aset(env, SERVER_NAME,
147
- rb_enc_str_new(request->host, pos - request->host,
148
- IodineBinaryEncoding));
149
- rb_hash_aset(env, SERVER_PORT,
150
- rb_enc_str_new(pos + 1,
151
- request->host_len - ((pos + 1) - request->host),
152
- IodineBinaryEncoding));
231
+ rb_hash_aset(
232
+ env, SERVER_NAME,
233
+ rb_enc_str_new(tmp.data, pos - tmp.data, IodineBinaryEncoding));
234
+ ++pos;
235
+ rb_hash_aset(
236
+ env, SERVER_PORT,
237
+ rb_enc_str_new(pos, tmp.len - (pos - tmp.data), IodineBinaryEncoding));
153
238
  }
154
239
 
155
- /* default schema to http, it might be updated later */
156
- rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
157
-
158
- /* add all headers, exclude special cases */
159
- http_header_s header = http_request_header_first(request);
160
- while (header.name) {
161
- if (header.name_len == 14 &&
162
- strncasecmp("content-length", header.name, 14) == 0) {
163
- rb_hash_aset(
164
- env, CONTENT_LENGTH,
165
- rb_enc_str_new(header.value, header.value_len, IodineBinaryEncoding));
166
- header = http_request_header_next(request);
167
- continue;
168
- } else if (header.name_len == 12 &&
169
- strncasecmp("content-type", header.name, 12) == 0) {
170
- rb_hash_aset(
171
- env, CONTENT_TYPE,
172
- rb_enc_str_new(header.value, header.value_len, IodineBinaryEncoding));
173
- header = http_request_header_next(request);
174
- continue;
175
- } else if (header.name_len == 27 &&
176
- strncasecmp("x-forwarded-proto", header.name, 27) == 0) {
177
- if (header.value_len >= 5 && !strncasecmp(header.value, "https", 5)) {
240
+ /* remove special headers */
241
+ {
242
+ static uint64_t content_length_hash = 0;
243
+ if (!content_length_hash)
244
+ content_length_hash = fio_siphash("content-length", 14);
245
+ FIOBJ cl = fiobj_hash_get2(h->headers, content_length_hash);
246
+ if (cl) {
247
+ tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, content_length_hash));
248
+ if (tmp.data) {
249
+ rb_hash_aset(env, CONTENT_LENGTH,
250
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
251
+ fiobj_hash_delete2(h->headers, content_length_hash);
252
+ }
253
+ }
254
+ }
255
+ {
256
+ static uint64_t content_type_hash = 0;
257
+ if (!content_type_hash)
258
+ content_type_hash = fio_siphash("content-type", 12);
259
+ FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash);
260
+ if (ct) {
261
+ tmp = fiobj_obj2cstr(ct);
262
+ if (tmp.len && tmp.data) {
263
+ fprintf(stderr, "Content type: %s\n", tmp.data);
264
+ rb_hash_aset(env, CONTENT_TYPE,
265
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
266
+ fiobj_hash_delete2(h->headers, content_type_hash);
267
+ }
268
+ }
269
+ }
270
+ /* handle scheme / sepcial forwarding headers */
271
+ {
272
+ FIOBJ objtmp;
273
+ static uint64_t xforward_hash = 0;
274
+ if (!xforward_hash)
275
+ xforward_hash = fio_siphash("x-forwarded-proto", 27);
276
+ static uint64_t forward_hash = 0;
277
+ if (!forward_hash)
278
+ forward_hash = fio_siphash("forwarded", 9);
279
+ if ((objtmp = fiobj_hash_get2(h->headers, xforward_hash))) {
280
+ tmp = fiobj_obj2cstr(objtmp);
281
+ if (tmp.len >= 5 && !strncasecmp(tmp.data, "https", 5)) {
178
282
  rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
179
- } else if (header.value_len == 4 &&
180
- *((uint32_t *)header.value) == *((uint32_t *)"http")) {
283
+ } else if (tmp.len == 4 && !strncasecmp(tmp.data, "http", 4)) {
181
284
  rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
182
285
  } else {
183
286
  rb_hash_aset(env, R_URL_SCHEME,
184
- rb_enc_str_new(header.value, header.value_len,
185
- IodineBinaryEncoding));
287
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
186
288
  }
187
- } else if (header.name_len == 9 &&
188
- strncasecmp("forwarded", header.name, 9) == 0) {
189
- pos = (char *)header.value;
289
+ } else if ((objtmp = fiobj_hash_get2(h->headers, forward_hash))) {
290
+ tmp = fiobj_obj2cstr(objtmp);
291
+ pos = tmp.data;
190
292
  if (pos) {
191
293
  while (*pos) {
192
294
  if (((*(pos++) | 32) == 'p') && ((*(pos++) | 32) == 'r') &&
@@ -209,25 +311,17 @@ static inline VALUE copy2env(http_request_s *request, VALUE template) {
209
311
  }
210
312
  }
211
313
  }
314
+ } else {
212
315
  }
213
-
214
- hname = rb_str_buf_new(6 + header.name_len);
215
- memcpy(RSTRING_PTR(hname), "HTTP_", 5);
216
- pos = RSTRING_PTR(hname) + 5;
217
- reader = header.name;
218
- while (*reader) {
219
- *(pos++) = *reader == '-' ? '_' : to_upper(*reader);
220
- ++reader;
221
- }
222
- *pos = 0;
223
- rb_str_set_len(hname, 5 + header.name_len);
224
- rb_hash_aset(
225
- env, hname,
226
- rb_enc_str_new(header.value, header.value_len, IodineBinaryEncoding));
227
- header = http_request_header_next(request);
228
316
  }
317
+
318
+ /* add all remianing headers */
319
+ fiobj_each1(h->headers, 0, iodine_copy2env_task, (void *)env);
229
320
  return env;
230
321
  }
322
+ #undef add_str_to_env
323
+ #undef add_value_to_env
324
+ #undef add_header_to_env
231
325
 
232
326
  /* *****************************************************************************
233
327
  Handling the HTTP response
@@ -235,7 +329,8 @@ Handling the HTTP response
235
329
 
236
330
  // itterate through the headers and add them to the response buffer
237
331
  // (we are recycling the request's buffer)
238
- static int for_each_header_data(VALUE key, VALUE val, VALUE _res) {
332
+ static int for_each_header_data(VALUE key, VALUE val, VALUE h_) {
333
+ http_s *h = (http_s *)h_;
239
334
  // fprintf(stderr, "For_each - headers\n");
240
335
  if (TYPE(key) != T_STRING)
241
336
  key = RubyCaller.call(key, iodine_to_s_method_id);
@@ -246,30 +341,37 @@ static int for_each_header_data(VALUE key, VALUE val, VALUE _res) {
246
341
  if (TYPE(val) != T_STRING)
247
342
  return ST_STOP;
248
343
  }
344
+ char *key_s = RSTRING_PTR(key);
345
+ int key_len = RSTRING_LEN(key);
249
346
  char *val_s = RSTRING_PTR(val);
250
347
  int val_len = RSTRING_LEN(val);
348
+ // make the headers lowercase
349
+ FIOBJ name = fiobj_str_new(key_s, key_len);
350
+ {
351
+ fio_cstr_s tmp = fiobj_obj2cstr(name);
352
+ for (int i = 0; i < key_len; ++i) {
353
+ tmp.data[i] = tolower(tmp.data[i]);
354
+ }
355
+ }
251
356
  // scan the value for newline (\n) delimiters
252
357
  int pos_s = 0, pos_e = 0;
253
358
  while (pos_e < val_len) {
254
359
  // scanning for newline (\n) delimiters
255
360
  while (pos_e < val_len && val_s[pos_e] != '\n')
256
361
  pos_e++;
257
- http_response_write_header(
258
- (void *)_res, .name = RSTRING_PTR(key), .name_len = RSTRING_LEN(key),
259
- .value = val_s + pos_s, .value_len = pos_e - pos_s);
362
+ http_set_header(h, name, fiobj_str_new(val_s + pos_s, pos_e - pos_s));
260
363
  // fprintf(stderr, "For_each - headers: wrote header\n");
261
364
  // move forward (skip the '\n' if exists)
262
- pos_s = pos_e + 1;
263
- pos_e++;
365
+ ++pos_e;
366
+ pos_s = pos_e;
264
367
  }
368
+ fiobj_free(name);
265
369
  // no errors, return 0
266
370
  return ST_CONTINUE;
267
371
  }
268
372
 
269
373
  // writes the body to the response object
270
- static VALUE for_each_body_string(VALUE str, VALUE _res, int argc, VALUE argv) {
271
- (void)(argv);
272
- (void)(argc);
374
+ static VALUE for_each_body_string(VALUE str, VALUE body_) {
273
375
  // fprintf(stderr, "For_each - body\n");
274
376
  // write body
275
377
  if (TYPE(str) != T_STRING) {
@@ -277,51 +379,45 @@ static VALUE for_each_body_string(VALUE str, VALUE _res, int argc, VALUE argv) {
277
379
  "response body was not a String\n");
278
380
  return Qfalse;
279
381
  }
280
- if (RSTRING_LEN(str)) {
281
- if (http_response_write_body((void *)_res, RSTRING_PTR(str),
282
- RSTRING_LEN(str))) {
283
- // fprintf(stderr, "Iodine Server Error:"
284
- // "couldn't write response to connection\n");
285
- return Qfalse;
286
- }
382
+ if (RSTRING_LEN(str) && RSTRING_PTR(str)) {
383
+ fiobj_str_write((FIOBJ)body_, RSTRING_PTR(str), RSTRING_LEN(str));
287
384
  }
288
385
  return Qtrue;
289
386
  }
290
387
 
291
- static inline int ruby2c_response_send(http_response_s *response,
388
+ static inline int ruby2c_response_send(iodine_http_request_handle_s *handle,
292
389
  VALUE rbresponse, VALUE env) {
293
390
  (void)(env);
294
391
  VALUE body = rb_ary_entry(rbresponse, 2);
295
- if (response->status < 200 || response->status == 204 ||
296
- response->status == 304) {
392
+ if (handle->h->status < 200 || handle->h->status == 204 ||
393
+ handle->h->status == 304) {
297
394
  if (rb_respond_to(body, close_method_id))
298
395
  RubyCaller.call(body, close_method_id);
299
396
  body = Qnil;
300
- response->content_length = -1;
397
+ handle->type = IODINE_HTTP_NONE;
398
+ return 0;
301
399
  }
302
-
303
- if (TYPE(body) == T_ARRAY && RARRAY_LEN(body) == 1) { // [String] is likely
304
- body = rb_ary_entry(body, 0);
305
- // fprintf(stderr, "Body was a single item array, unpacket to string\n");
400
+ if (TYPE(body) == T_ARRAY) {
401
+ if (RARRAY_LEN(body) == 0) { // only headers
402
+ handle->type = IODINE_HTTP_EMPTY;
403
+ } else if (RARRAY_LEN(body) == 1) { // [String] is likely
404
+ body = rb_ary_entry(body, 0);
405
+ // fprintf(stderr, "Body was a single item array, unpacket to string\n");
406
+ }
306
407
  }
307
408
 
308
409
  if (TYPE(body) == T_STRING) {
309
410
  // fprintf(stderr, "Review body as String\n");
310
411
  if (RSTRING_LEN(body))
311
- http_response_write_body(response, RSTRING_PTR(body), RSTRING_LEN(body));
312
- return 0;
313
- } else if (body == Qnil) {
412
+ handle->body = fiobj_str_new(RSTRING_PTR(body), RSTRING_LEN(body));
413
+ handle->type = IODINE_HTTP_SENDBODY;
314
414
  return 0;
315
415
  } else if (rb_respond_to(body, each_method_id)) {
316
416
  // fprintf(stderr, "Review body as for-each ...\n");
317
- if (!response->connection_written && !response->content_length_written) {
318
- // close the connection to indicate message length...
319
- // protection from bad code
320
- response->should_close = 1;
321
- response->content_length = -1;
322
- }
417
+ handle->body = fiobj_str_buf(1);
418
+ handle->type = IODINE_HTTP_SENDBODY;
323
419
  rb_block_call(body, each_method_id, 0, NULL, for_each_body_string,
324
- (VALUE)response);
420
+ (VALUE)handle->body);
325
421
  // we need to call `close` in case the object is an IO / BodyProxy
326
422
  if (rb_respond_to(body, close_method_id))
327
423
  RubyCaller.call(body, close_method_id);
@@ -334,36 +430,37 @@ static inline int ruby2c_response_send(http_response_s *response,
334
430
  Handling Upgrade cases
335
431
  ***************************************************************************** */
336
432
 
337
- static inline int ruby2c_review_upgrade(http_response_s *response,
433
+ static inline int ruby2c_review_upgrade(iodine_http_request_handle_s *req,
338
434
  VALUE rbresponse, VALUE env) {
435
+ http_s *h = req->h;
339
436
  VALUE handler;
340
437
  if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_CB)) != Qnil) {
341
438
  // send headers
342
- http_response_finish(response);
343
- // remove socket from facil.io
344
- facil_attach(response->fd, NULL);
439
+ http_finish(h);
345
440
  // call the callback
346
441
  VALUE io_ruby = RubyCaller.call(rb_hash_aref(env, IODINE_R_HIJACK),
347
442
  iodine_call_proc_id);
348
443
  RubyCaller.call2(handler, iodine_call_proc_id, 1, &io_ruby);
349
444
  } else if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_IO)) != Qnil) {
350
- // send nothing.
351
- http_response_destroy(response);
352
- // remove socket from facil.io
353
- facil_attach(response->fd, NULL);
354
- } else if ((handler = rb_hash_aref(env, UPGRADE_WEBSOCKET)) != Qnil) {
355
- iodine_http_settings_s *settings = response->request->settings->udata;
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)) {
356
449
  // use response as existing base for native websocket upgrade
357
- iodine_websocket_upgrade(response->request, response, handler,
358
- settings->max_msg, settings->ping);
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);
359
455
  } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil) {
360
- intptr_t fduuid = response->fd;
456
+ // hijack post headers (might be very bad)
457
+ intptr_t uuid = http_hijack(h, NULL);
361
458
  // send headers
362
- http_response_finish(response);
459
+ http_finish(h);
363
460
  // upgrade protocol
364
- VALUE args[2] = {(ULONG2NUM(sock_uuid2fd(fduuid))), handler};
461
+ VALUE args[2] = {(ULONG2NUM(sock_uuid2fd(uuid))), handler};
365
462
  RubyCaller.call2(Iodine, attach_method_id, 2, args);
366
- // prevent response processing.
463
+ // nothing left to do to prevent response processing.
367
464
  } else {
368
465
  return 0;
369
466
  }
@@ -379,176 +476,155 @@ static inline int ruby2c_review_upgrade(http_response_s *response,
379
476
  Handling HTTP requests
380
477
  ***************************************************************************** */
381
478
 
382
- static void *on_rack_request_in_GVL(http_request_s *request) {
383
- http_response_s *response = http_response_create(request);
384
- iodine_http_settings_s *settings = request->settings->udata;
385
- if (request->settings->log_static)
386
- http_response_log_start(response);
387
- if (!settings->app)
479
+ static inline void *iodine_handle_request_in_GVL(void *handle_) {
480
+ iodine_http_request_handle_s *handle = handle_;
481
+ VALUE rbresponse = 0;
482
+ VALUE env = 0;
483
+ http_s *h = handle->h;
484
+ if (!h->udata)
388
485
  goto err_not_found;
389
486
 
390
- // create /register env variable
391
- VALUE env = copy2env(request, settings->env);
487
+ // create / register env variable
488
+ env = copy2env(handle);
392
489
  // will be used later
393
490
  VALUE tmp;
394
491
  // pass env variable to handler
395
- VALUE rbresponse =
396
- RubyCaller.call2(settings->app, iodine_call_proc_id, 1, &env);
492
+ rbresponse = RubyCaller.call2((VALUE)h->udata, iodine_call_proc_id, 1, &env);
493
+ // test handler's return value
397
494
  if (rbresponse == 0 || rbresponse == Qnil)
398
495
  goto internal_error;
399
496
  Registry.add(rbresponse);
497
+
400
498
  // set response status
401
499
  tmp = rb_ary_entry(rbresponse, 0);
402
- if (TYPE(tmp) == T_STRING)
403
- tmp = rb_funcall2(tmp, to_fixnum_func_id, 0, NULL);
404
- if (TYPE(tmp) != T_FIXNUM)
500
+ if (TYPE(tmp) == T_STRING) {
501
+ char *data = RSTRING_PTR(tmp);
502
+ h->status = fio_atol(&data);
503
+ } else if (TYPE(tmp) == T_FIXNUM) {
504
+ h->status = FIX2ULONG(tmp);
505
+ } else {
405
506
  goto internal_error;
406
- response->status = FIX2ULONG(tmp);
507
+ }
508
+
407
509
  // handle header copy from ruby land to C land.
408
510
  VALUE response_headers = rb_ary_entry(rbresponse, 1);
409
511
  if (TYPE(response_headers) != T_HASH)
410
512
  goto internal_error;
411
513
  // extract the X-Sendfile header (never show original path)
412
- // X-Sendfile support only present when iodine sercers static files.
514
+ // X-Sendfile support only present when iodine serves static files.
413
515
  VALUE xfiles;
414
- if (request->settings->public_folder &&
516
+ if (support_xsendfile &&
415
517
  (xfiles = rb_hash_aref(response_headers, XSENDFILE)) != Qnil &&
416
518
  TYPE(xfiles) == T_STRING) {
417
- int fr = 0;
418
519
  if (OBJ_FROZEN(response_headers)) {
419
520
  response_headers = rb_hash_dup(response_headers);
420
- Registry.add(response_headers);
421
- fr = 1;
422
521
  }
522
+ Registry.add(response_headers);
523
+ handle->body = fiobj_str_new(RSTRING_PTR(xfiles), RSTRING_LEN(xfiles));
524
+ handle->type = IODINE_HTTP_XSENDFILE;
423
525
  rb_hash_delete(response_headers, XSENDFILE);
424
- // remove XFile's content length headers, as this will be controled by
425
- // Iodine
526
+ // remove content length headers, as this will be controled by iodine
426
527
  rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER);
427
528
  // review each header and write it to the response.
428
- rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(response));
429
- if (fr)
430
- Registry.remove(response_headers);
529
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h));
530
+ Registry.remove(response_headers);
431
531
  // send the file directly and finish
432
- if (http_response_sendfile2(response, request, RSTRING_PTR(xfiles),
433
- RSTRING_LEN(xfiles), NULL, 0, 1)) {
434
- http_response_destroy(response);
435
- response = http_response_create(request);
436
- if (request->settings->log_static)
437
- http_response_log_start(response);
438
- Registry.remove(rbresponse);
439
- Registry.remove(env);
440
- goto err_not_found;
441
- }
442
- goto external_done;
532
+ return NULL;
443
533
  }
444
534
  // review each header and write it to the response.
445
- rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(response));
446
- // If the X-Sendfile header was provided, send the file directly and finish
447
- // review for belated (post response headers) upgrade.
448
- if (ruby2c_review_upgrade(response, rbresponse, env))
535
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h));
536
+ // review for upgrade.
537
+ if (h->status < 300 && ruby2c_review_upgrade(handle, rbresponse, env))
449
538
  goto external_done;
450
539
  // send the request body.
451
- if (ruby2c_response_send(response, rbresponse, env))
540
+ if (ruby2c_response_send(handle, rbresponse, env))
452
541
  goto internal_error;
453
542
 
454
543
  Registry.remove(rbresponse);
455
544
  Registry.remove(env);
456
- http_response_finish(response);
457
545
  return NULL;
458
546
 
459
547
  external_done:
460
548
  Registry.remove(rbresponse);
461
549
  Registry.remove(env);
550
+ handle->type = IODINE_HTTP_NONE;
462
551
  return NULL;
463
552
 
464
553
  err_not_found:
465
- response->status = 404;
466
- if (!request->settings->public_folder ||
467
- http_response_sendfile2(response, request,
468
- request->settings->public_folder,
469
- request->settings->public_folder_length,
470
- "404.html", 8, request->settings->log_static)) {
471
- http_response_write_body(response, "Error 404, Page Not Found.", 26);
472
- http_response_finish(response);
473
- }
554
+ Registry.remove(rbresponse);
555
+ Registry.remove(env);
556
+ h->status = 404;
557
+ handle->type = IODINE_HTTP_ERROR;
474
558
  return NULL;
475
559
 
476
560
  internal_error:
477
- if (rbresponse && rbresponse != Qnil)
478
- Registry.remove(rbresponse);
561
+ Registry.remove(rbresponse);
479
562
  Registry.remove(env);
480
- http_response_destroy(response);
481
- response = http_response_create(request);
482
- if (request->settings->log_static)
483
- http_response_log_start(response);
484
- response->status = 500;
485
- if (!request->settings->public_folder ||
486
- http_response_sendfile2(response, request,
487
- request->settings->public_folder,
488
- request->settings->public_folder_length,
489
- "500.html", 8, request->settings->log_static)) {
490
- http_response_write_body(response, "Error 500, Internal error.", 26);
491
- http_response_finish(response);
492
- }
563
+ h->status = 500;
564
+ handle->type = IODINE_HTTP_ERROR;
493
565
  return NULL;
494
566
  }
495
567
 
496
- static void on_rack_request(http_request_s *request) {
497
- // if (request->body_file)
498
- // fprintf(stderr, "Request data is stored in a temporary file\n");
499
- RubyCaller.call_c((void *(*)(void *))on_rack_request_in_GVL, request);
500
- }
501
-
502
- /* *****************************************************************************
503
- Initializing basic Rack ENV template
504
- ***************************************************************************** */
505
-
506
- #define add_str_to_env(env, key, value) \
507
- { \
508
- VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
509
- rb_obj_freeze(k); \
510
- VALUE v = rb_enc_str_new((value), strlen((value)), IodineBinaryEncoding); \
511
- rb_obj_freeze(v); \
512
- rb_hash_aset(env, k, v); \
568
+ static inline void
569
+ iodine_perform_handle_action(iodine_http_request_handle_s handle) {
570
+ switch (handle.type) {
571
+ case IODINE_HTTP_SENDBODY: {
572
+ fio_cstr_s data = fiobj_obj2cstr(handle.body);
573
+ http_send_body(handle.h, data.data, data.len);
574
+ fiobj_free(handle.body);
575
+ break;
513
576
  }
514
- #define add_value_to_env(env, key, value) \
515
- { \
516
- VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
517
- rb_obj_freeze(k); \
518
- rb_hash_aset((env), k, value); \
577
+ case IODINE_HTTP_XSENDFILE: {
578
+ /* remove chunked content-encoding header, if any (Rack issue #1266) */
579
+ if (fiobj_obj2cstr(
580
+ fiobj_hash_get2(handle.h->private_data.out_headers,
581
+ fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING)))
582
+ .len == 7)
583
+ fiobj_hash_delete2(handle.h->private_data.out_headers,
584
+ fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING));
585
+ fio_cstr_s data = fiobj_obj2cstr(handle.body);
586
+ if (http_sendfile2(handle.h, data.data, data.len, NULL, 0)) {
587
+ http_send_error(handle.h, 404);
588
+ }
589
+ fiobj_free(handle.body);
590
+ break;
519
591
  }
592
+ case IODINE_HTTP_EMPTY:
593
+ http_finish(handle.h);
594
+ fiobj_free(handle.body);
595
+ break;
596
+ case IODINE_HTTP_NONE:
597
+ /* nothing to do - this had to be performed within the Ruby GIL :-( */
598
+ break;
599
+ case IODINE_HTTP_ERROR:
600
+ http_send_error(handle.h, handle.h->status);
601
+ fiobj_free(handle.body);
602
+ break;
603
+ }
604
+ }
605
+ static void on_rack_request(http_s *h) {
606
+ iodine_http_request_handle_s handle = (iodine_http_request_handle_s){
607
+ .h = h, .upgrade = IODINE_UPGRADE_NONE,
608
+ };
609
+ RubyCaller.call_c((void *(*)(void *))iodine_handle_request_in_GVL, &handle);
610
+ iodine_perform_handle_action(handle);
611
+ }
520
612
 
521
- static void init_env_template(iodine_http_settings_s *set, uint8_t xsendfile) {
522
- VALUE tmp;
523
- set->env = rb_hash_new();
524
- Registry.add(set->env);
525
-
526
- // Start with the stuff Iodine will review.
527
- rb_hash_aset(set->env, UPGRADE_WEBSOCKET, Qnil);
528
- rb_hash_aset(set->env, UPGRADE_TCP, Qnil);
529
- if (xsendfile) {
530
- add_value_to_env(set->env, "sendfile.type", XSENDFILE);
531
- add_value_to_env(set->env, "HTTP_X_SENDFILE_TYPE", XSENDFILE);
532
- }
533
- rb_hash_aset(set->env, UPGRADE_WEBSOCKET_Q, Qnil);
534
- rb_hash_aset(set->env, UPGRADE_TCP_Q, Qnil);
535
-
536
- // add the rack.version
537
- tmp = rb_ary_new(); // rb_ary_new is Ruby 2.0 compatible
538
- rb_ary_push(tmp, INT2FIX(1));
539
- rb_ary_push(tmp, INT2FIX(3));
540
- // rb_ary_push(tmp, rb_enc_str_new("1", 1, IodineBinaryEncoding));
541
- // rb_ary_push(tmp, rb_enc_str_new("3", 1, IodineBinaryEncoding));
542
- add_value_to_env(set->env, "rack.version", tmp);
543
- add_value_to_env(set->env, "rack.errors", rb_stderr);
544
- add_value_to_env(set->env, "rack.multithread", Qtrue);
545
- add_value_to_env(set->env, "rack.multiprocess", Qtrue);
546
- add_value_to_env(set->env, "rack.run_once", Qfalse);
547
- add_value_to_env(set->env, "rack.hijack?", Qtrue);
548
- add_str_to_env(set->env, "SCRIPT_NAME", "");
613
+ static void on_rack_upgrade(http_s *h, char *proto, size_t len) {
614
+ iodine_http_request_handle_s handle = (iodine_http_request_handle_s){.h = h};
615
+ if (len == 9 && proto[1] == 'e') {
616
+ handle.upgrade = IODINE_UPGRADE_WEBSOCKET;
617
+ } else if (len == 3 && proto[0] == 's') {
618
+ handle.upgrade = IODINE_UPGRADE_SSE;
619
+ } else {
620
+ http_send_error(h, 400);
621
+ return;
622
+ }
623
+ RubyCaller.call_c(iodine_handle_request_in_GVL, &handle);
624
+ iodine_perform_handle_action(handle);
625
+ (void)proto;
626
+ (void)len;
549
627
  }
550
- #undef add_str_to_env
551
- #undef add_value_to_env
552
628
 
553
629
  /* *****************************************************************************
554
630
  Listenninng to HTTP
@@ -566,9 +642,7 @@ void *iodine_print_http_msg2_in_gvl(void *d_) {
566
642
  "Iodine HTTP Server on port %s:\n"
567
643
  " * Serving static files from %s\n\n",
568
644
  StringValueCStr(arg->port), StringValueCStr(arg->www));
569
- Registry.remove(arg->www);
570
645
  }
571
- Registry.remove(arg->port);
572
646
  return NULL;
573
647
  }
574
648
 
@@ -584,49 +658,51 @@ void *iodine_print_http_msg_in_gvl(void *d_) {
584
658
  fprintf(stderr,
585
659
  "\nStarting up Iodine HTTP Server on port %s:\n"
586
660
  " * Ruby v.%s\n * Iodine v.%s \n"
587
- " * %lu max concurrent connections / open files\n"
588
661
  " * Serving static files from %s\n\n",
589
662
  StringValueCStr(arg->port), StringValueCStr(ruby_version),
590
- StringValueCStr(iodine_version), (size_t)sock_max_capacity(),
591
- StringValueCStr(arg->www));
592
- Registry.remove(arg->www);
593
- } else
663
+ StringValueCStr(iodine_version), StringValueCStr(arg->www));
664
+ } else {
594
665
  fprintf(stderr,
595
666
  "\nStarting up Iodine HTTP Server on port %s:\n"
596
- " * Ruby v.%s\n * Iodine v.%s \n"
597
- " * %lu max concurrent connections / open files\n\n",
667
+ " * Ruby v.%s\n * Iodine v.%s \n\n",
598
668
  StringValueCStr(arg->port), StringValueCStr(ruby_version),
599
- StringValueCStr(iodine_version), (size_t)sock_max_capacity());
600
- Registry.remove(arg->port);
669
+ StringValueCStr(iodine_version));
670
+ }
601
671
 
602
672
  return NULL;
603
673
  }
604
674
 
605
675
  static void iodine_print_http_msg1(void *www, void *port) {
606
- if (defer_fork_pid())
607
- return;
676
+ if (getpid() != facil_parent_pid())
677
+ goto finish;
608
678
  struct {
609
679
  void *www;
610
680
  void *port;
611
681
  } data = {.www = www, .port = port};
612
682
  RubyCaller.call_c(iodine_print_http_msg_in_gvl, (void *)&data);
683
+ finish:
684
+ if (www) {
685
+ Registry.remove((VALUE)www);
686
+ }
687
+ Registry.remove((VALUE)port);
613
688
  }
614
689
  static void iodine_print_http_msg2(void *www, void *port) {
615
- if (defer_fork_pid())
616
- return;
690
+ if (getpid() != facil_parent_pid())
691
+ goto finish;
617
692
  struct {
618
693
  void *www;
619
694
  void *port;
620
695
  } data = {.www = www, .port = port};
621
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);
622
702
  }
623
703
 
624
- static void free_iodine_http(intptr_t uuid, void *set_) {
625
- iodine_http_settings_s *set = set_;
626
- Registry.remove(set->app);
627
- Registry.remove(set->env);
628
- free(set);
629
- (void)uuid;
704
+ static void free_iodine_http(http_settings_s *s) {
705
+ Registry.remove((VALUE)s->udata);
630
706
  }
631
707
  /**
632
708
  Listens to incoming HTTP connections and handles incoming requests using the
@@ -643,10 +719,10 @@ port:: the port to listen to. Default: 3000.
643
719
  address:: the address to bind to. Default: binds to all possible addresses.
644
720
  log:: enable response logging (Hijacked sockets aren't logged). Default: off.
645
721
  public:: The root public folder for static file service. Default: none.
646
- timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 5 seconds.
722
+ timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
647
723
  max_body:: The maximum body size for incoming HTTP messages. Default: ~50Mib.
648
724
  max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
649
- ping:: The Websocket `ping` interval. Default: 40 sec.
725
+ ping:: The Websocket `ping` interval. Default: 40 seconds.
650
726
 
651
727
  Either the `app` or the `public` properties are required. If niether exists,
652
728
  the function will fail. If both exist, Iodine will serve static files as well
@@ -668,6 +744,7 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
668
744
  uint8_t log_http = 0;
669
745
  size_t ping = 0;
670
746
  size_t max_body = 0;
747
+ size_t max_headers = 0;
671
748
  size_t max_msg = 0;
672
749
  Check_Type(opt, T_HASH);
673
750
  VALUE app = rb_hash_aref(opt, ID2SYM(rb_intern("app")));
@@ -687,6 +764,11 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
687
764
  Check_Type(tmp, T_FIXNUM);
688
765
  max_body = FIX2ULONG(tmp);
689
766
  }
767
+ tmp = rb_hash_aref(opt, ID2SYM(rb_intern("max_headers")));
768
+ if (tmp != Qnil && tmp != Qfalse) {
769
+ Check_Type(tmp, T_FIXNUM);
770
+ max_headers = FIX2ULONG(tmp);
771
+ }
690
772
 
691
773
  tmp = rb_hash_aref(opt, ID2SYM(rb_intern("ping")));
692
774
  if (tmp != Qnil && tmp != Qfalse) {
@@ -712,6 +794,9 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
712
794
  if ((www != Qnil && www != Qfalse)) {
713
795
  Check_Type(www, T_STRING);
714
796
  Registry.add(www);
797
+ rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
798
+ rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
799
+ support_xsendfile = 1;
715
800
  } else
716
801
  www = 0;
717
802
 
@@ -737,27 +822,27 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
737
822
  "The `port` property MUST be either a String or a Number");
738
823
  if (RB_TYPE_P(port, T_FIXNUM))
739
824
  port = rb_funcall2(port, iodine_to_s_method_id, 0, NULL);
740
- } else
825
+ Registry.add(port);
826
+ } else if (port == Qfalse)
827
+ port = 0;
828
+ else {
741
829
  port = rb_str_new("3000", 4);
742
- Registry.add(port);
830
+ Registry.add(port);
831
+ }
743
832
 
744
833
  if ((app != Qnil && app != Qfalse))
745
834
  Registry.add(app);
746
835
  else
747
836
  app = 0;
748
837
 
749
- iodine_http_settings_s *set = malloc(sizeof(*set));
750
- *set = (iodine_http_settings_s){.app = app, .ping = ping, .max_msg = max_msg};
751
-
752
- init_env_template(set, (www ? 1 : 0));
753
-
754
- if (http_listen(StringValueCStr(port),
755
- (address ? StringValueCStr(address) : NULL),
756
- .on_request = on_rack_request, .udata = set,
757
- .timeout = (tout ? FIX2INT(tout) : tout),
758
- .on_finish = free_iodine_http, .log_static = log_http,
759
- .max_body_size = max_body,
760
- .public_folder = (www ? StringValueCStr(www) : NULL))) {
838
+ if (http_listen(
839
+ StringValueCStr(port), (address ? StringValueCStr(address) : NULL),
840
+ .on_request = on_rack_request, .on_upgrade = on_rack_upgrade,
841
+ .udata = (void *)app, .timeout = (tout ? FIX2INT(tout) : tout),
842
+ .ws_timeout = ping, .ws_max_msg_size = max_msg,
843
+ .max_header_size = max_headers, .on_finish = free_iodine_http,
844
+ .log = log_http, .max_body_size = max_body,
845
+ .public_folder = (www ? StringValueCStr(www) : NULL))) {
761
846
  fprintf(stderr,
762
847
  "ERROR: Failed to initialize a listening HTTP socket for port %s\n",
763
848
  port ? StringValueCStr(port) : "3000");
@@ -770,9 +855,9 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
770
855
  "static files.\n",
771
856
  (port ? StringValueCStr(port) : "3000"));
772
857
  }
773
- if (called_once)
858
+ if (called_once) {
774
859
  defer(iodine_print_http_msg2, (www ? (void *)www : NULL), (void *)port);
775
- else {
860
+ } else {
776
861
  called_once = 1;
777
862
  defer(iodine_print_http_msg1, (www ? (void *)www : NULL), (void *)port);
778
863
  }
@@ -781,10 +866,83 @@ VALUE iodine_http_listen(VALUE self, VALUE opt) {
781
866
  (void)self;
782
867
  }
783
868
 
869
+ static void initialize_env_template(void) {
870
+ if (env_template_no_upgrade)
871
+ return;
872
+ env_template_no_upgrade = rb_hash_new();
873
+ rb_global_variable(&env_template_no_upgrade);
874
+
875
+ #define add_str_to_env(env, key, value) \
876
+ { \
877
+ VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
878
+ rb_obj_freeze(k); \
879
+ VALUE v = rb_enc_str_new((value), strlen((value)), IodineBinaryEncoding); \
880
+ rb_obj_freeze(v); \
881
+ rb_hash_aset(env, k, v); \
882
+ }
883
+ #define add_value_to_env(env, key, value) \
884
+ { \
885
+ VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
886
+ rb_obj_freeze(k); \
887
+ rb_hash_aset((env), k, value); \
888
+ }
889
+
890
+ /* Set global template */
891
+ rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE_Q, Qnil);
892
+ 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
+ {
898
+ /* add the rack.version */
899
+ static VALUE rack_version = 0;
900
+ if (!rack_version) {
901
+ rack_version = rb_ary_new(); // rb_ary_new is Ruby 2.0 compatible
902
+ rb_ary_push(rack_version, INT2FIX(1));
903
+ rb_ary_push(rack_version, INT2FIX(3));
904
+ rb_global_variable(&rack_version);
905
+ rb_ary_freeze(rack_version);
906
+ }
907
+ add_value_to_env(env_template_no_upgrade, "rack.version", rack_version);
908
+ }
909
+ add_str_to_env(env_template_no_upgrade, "SCRIPT_NAME", "");
910
+ add_value_to_env(env_template_no_upgrade, "rack.errors", rb_stderr);
911
+ add_value_to_env(env_template_no_upgrade, "rack.hijack?", Qtrue);
912
+ add_value_to_env(env_template_no_upgrade, "rack.multiprocess", Qtrue);
913
+ add_value_to_env(env_template_no_upgrade, "rack.multithread", Qtrue);
914
+ add_value_to_env(env_template_no_upgrade, "rack.run_once", Qfalse);
915
+ /* default schema to http, it might be updated later */
916
+ rb_hash_aset(env_template_no_upgrade, R_URL_SCHEME, HTTP_SCHEME);
917
+ /* placeholders... minimize rehashing*/
918
+ rb_hash_aset(env_template_no_upgrade, HTTP_VERSION, QUERY_STRING);
919
+ rb_hash_aset(env_template_no_upgrade, IODINE_R_HIJACK, QUERY_STRING);
920
+ rb_hash_aset(env_template_no_upgrade, PATH_INFO, QUERY_STRING);
921
+ rb_hash_aset(env_template_no_upgrade, QUERY_STRING, QUERY_STRING);
922
+ rb_hash_aset(env_template_no_upgrade, REMOTE_ADDR, QUERY_STRING);
923
+ rb_hash_aset(env_template_no_upgrade, REQUEST_METHOD, QUERY_STRING);
924
+ rb_hash_aset(env_template_no_upgrade, SERVER_NAME, QUERY_STRING);
925
+ rb_hash_aset(env_template_no_upgrade, SERVER_PORT, QUERY_ESTRING);
926
+ rb_hash_aset(env_template_no_upgrade, SERVER_PROTOCOL, QUERY_STRING);
927
+
928
+ /* WebSocket upgrade support */
929
+ 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);
932
+ rb_hash_aset(env_template_websockets, RACK_UPGRADE_Q, RACK_UPGRADE_WEBSOCKET);
933
+ rb_hash_aset(env_template_websockets, UPGRADE_TCP_Q, Qtrue);
934
+
935
+ /* SSE upgrade support */
936
+ env_template_sse = rb_hash_dup(env_template_no_upgrade);
937
+ rb_global_variable(&env_template_sse);
938
+ rb_hash_aset(env_template_sse, RACK_UPGRADE_Q, RACK_UPGRADE_SSE);
939
+
940
+ #undef add_value_to_env
941
+ #undef add_str_to_env
942
+ }
784
943
  /* *****************************************************************************
785
944
  Initialization
786
- *****************************************************************************
787
- */
945
+ ***************************************************************************** */
788
946
 
789
947
  void Iodine_init_http(void) {
790
948
 
@@ -806,6 +964,8 @@ void Iodine_init_http(void) {
806
964
  rack_set(R_URL_SCHEME, "rack.url_scheme");
807
965
  rack_set(R_INPUT, "rack.input");
808
966
  rack_set(XSENDFILE, "X-Sendfile");
967
+ rack_set(XSENDFILE_TYPE, "sendfile.type");
968
+ rack_set(XSENDFILE_TYPE_HEADER, "HTTP_X_SENDFILE_TYPE");
809
969
  rack_set(CONTENT_LENGTH_HEADER, "Content-Length");
810
970
 
811
971
  rack_set(IODINE_R_HIJACK_IO, "rack.hijack_io");
@@ -817,9 +977,10 @@ void Iodine_init_http(void) {
817
977
 
818
978
  rack_set(UPGRADE_TCP_Q, "upgrade.tcp?");
819
979
  rack_set(UPGRADE_WEBSOCKET_Q, "upgrade.websocket?");
820
-
821
- rack_set(QUERY_ESTRING, "");
822
- rack_set(QUERY_ESTRING, "");
980
+ rack_set(RACK_UPGRADE, "rack.upgrade");
981
+ rack_set(RACK_UPGRADE_Q, "rack.upgrade?");
982
+ rack_set_sym(RACK_UPGRADE_SSE, "sse");
983
+ rack_set_sym(RACK_UPGRADE_WEBSOCKET, "websocket");
823
984
 
824
985
  hijack_func_sym = ID2SYM(rb_intern("_hijack"));
825
986
  close_method_id = rb_intern("close");
@@ -827,4 +988,6 @@ void Iodine_init_http(void) {
827
988
  attach_method_id = rb_intern("attach_fd");
828
989
 
829
990
  IodineRackIO.init();
991
+ initialize_env_template();
992
+ Iodine_init_json();
830
993
  }