rage-iodine 1.7.58

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.github/workflows/ruby.yml +42 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +2 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1098 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +23 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/bates/README.md +3 -0
  25. data/examples/bates/config.ru +342 -0
  26. data/examples/bates/david+bold.pdf +0 -0
  27. data/examples/bates/public/drop-pdf.png +0 -0
  28. data/examples/bates/public/index.html +600 -0
  29. data/examples/config.ru +59 -0
  30. data/examples/echo.ru +59 -0
  31. data/examples/etag.ru +16 -0
  32. data/examples/hello.ru +29 -0
  33. data/examples/pubsub_engine.ru +81 -0
  34. data/examples/rack3.ru +12 -0
  35. data/examples/redis.ru +70 -0
  36. data/examples/shootout.ru +73 -0
  37. data/examples/sub-protocols.ru +90 -0
  38. data/examples/tcp_client.rb +66 -0
  39. data/examples/x-sendfile.ru +14 -0
  40. data/exe/iodine +280 -0
  41. data/ext/iodine/extconf.rb +110 -0
  42. data/ext/iodine/fio.c +12096 -0
  43. data/ext/iodine/fio.h +6390 -0
  44. data/ext/iodine/fio_cli.c +431 -0
  45. data/ext/iodine/fio_cli.h +189 -0
  46. data/ext/iodine/fio_json_parser.h +687 -0
  47. data/ext/iodine/fio_siphash.c +157 -0
  48. data/ext/iodine/fio_siphash.h +37 -0
  49. data/ext/iodine/fio_tls.h +129 -0
  50. data/ext/iodine/fio_tls_missing.c +649 -0
  51. data/ext/iodine/fio_tls_openssl.c +1056 -0
  52. data/ext/iodine/fio_tmpfile.h +50 -0
  53. data/ext/iodine/fiobj.h +44 -0
  54. data/ext/iodine/fiobj4fio.h +21 -0
  55. data/ext/iodine/fiobj_ary.c +333 -0
  56. data/ext/iodine/fiobj_ary.h +139 -0
  57. data/ext/iodine/fiobj_data.c +1185 -0
  58. data/ext/iodine/fiobj_data.h +167 -0
  59. data/ext/iodine/fiobj_hash.c +409 -0
  60. data/ext/iodine/fiobj_hash.h +176 -0
  61. data/ext/iodine/fiobj_json.c +622 -0
  62. data/ext/iodine/fiobj_json.h +68 -0
  63. data/ext/iodine/fiobj_mem.h +71 -0
  64. data/ext/iodine/fiobj_mustache.c +317 -0
  65. data/ext/iodine/fiobj_mustache.h +62 -0
  66. data/ext/iodine/fiobj_numbers.c +344 -0
  67. data/ext/iodine/fiobj_numbers.h +127 -0
  68. data/ext/iodine/fiobj_str.c +433 -0
  69. data/ext/iodine/fiobj_str.h +172 -0
  70. data/ext/iodine/fiobject.c +620 -0
  71. data/ext/iodine/fiobject.h +654 -0
  72. data/ext/iodine/hpack.h +1923 -0
  73. data/ext/iodine/http.c +2736 -0
  74. data/ext/iodine/http.h +1019 -0
  75. data/ext/iodine/http1.c +825 -0
  76. data/ext/iodine/http1.h +29 -0
  77. data/ext/iodine/http1_parser.h +1835 -0
  78. data/ext/iodine/http_internal.c +1279 -0
  79. data/ext/iodine/http_internal.h +248 -0
  80. data/ext/iodine/http_mime_parser.h +350 -0
  81. data/ext/iodine/iodine.c +1433 -0
  82. data/ext/iodine/iodine.h +64 -0
  83. data/ext/iodine/iodine_caller.c +218 -0
  84. data/ext/iodine/iodine_caller.h +27 -0
  85. data/ext/iodine/iodine_connection.c +941 -0
  86. data/ext/iodine/iodine_connection.h +55 -0
  87. data/ext/iodine/iodine_defer.c +420 -0
  88. data/ext/iodine/iodine_defer.h +6 -0
  89. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  90. data/ext/iodine/iodine_helpers.c +282 -0
  91. data/ext/iodine/iodine_helpers.h +12 -0
  92. data/ext/iodine/iodine_http.c +1280 -0
  93. data/ext/iodine/iodine_http.h +23 -0
  94. data/ext/iodine/iodine_json.c +302 -0
  95. data/ext/iodine/iodine_json.h +6 -0
  96. data/ext/iodine/iodine_mustache.c +567 -0
  97. data/ext/iodine/iodine_mustache.h +6 -0
  98. data/ext/iodine/iodine_pubsub.c +580 -0
  99. data/ext/iodine/iodine_pubsub.h +26 -0
  100. data/ext/iodine/iodine_rack_io.c +273 -0
  101. data/ext/iodine/iodine_rack_io.h +20 -0
  102. data/ext/iodine/iodine_store.c +142 -0
  103. data/ext/iodine/iodine_store.h +20 -0
  104. data/ext/iodine/iodine_tcp.c +346 -0
  105. data/ext/iodine/iodine_tcp.h +13 -0
  106. data/ext/iodine/iodine_tls.c +261 -0
  107. data/ext/iodine/iodine_tls.h +13 -0
  108. data/ext/iodine/mustache_parser.h +1546 -0
  109. data/ext/iodine/redis_engine.c +957 -0
  110. data/ext/iodine/redis_engine.h +79 -0
  111. data/ext/iodine/resp_parser.h +317 -0
  112. data/ext/iodine/scheduler.c +173 -0
  113. data/ext/iodine/scheduler.h +6 -0
  114. data/ext/iodine/websocket_parser.h +506 -0
  115. data/ext/iodine/websockets.c +752 -0
  116. data/ext/iodine/websockets.h +185 -0
  117. data/iodine.gemspec +50 -0
  118. data/lib/iodine/connection.rb +61 -0
  119. data/lib/iodine/json.rb +42 -0
  120. data/lib/iodine/mustache.rb +113 -0
  121. data/lib/iodine/pubsub.rb +55 -0
  122. data/lib/iodine/rack_utils.rb +43 -0
  123. data/lib/iodine/tls.rb +16 -0
  124. data/lib/iodine/version.rb +3 -0
  125. data/lib/iodine.rb +274 -0
  126. data/lib/rack/handler/iodine.rb +33 -0
  127. data/logo.png +0 -0
  128. metadata +284 -0
@@ -0,0 +1,1280 @@
1
+ /*
2
+ Copyright: Boaz segev, 2016-2017
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #include "iodine.h"
8
+
9
+ #include "http.h"
10
+ #include "iodine_caller.h"
11
+ #include "iodine_store.h"
12
+
13
+ #include <ruby/encoding.h>
14
+ #include <ruby/io.h>
15
+ #include <stdio.h>
16
+ // #include "iodine_websockets.h"
17
+
18
+ #ifndef __MINGW32__
19
+ #include <arpa/inet.h>
20
+ #endif
21
+ #include <ctype.h>
22
+ #include <stdlib.h>
23
+ #include <string.h>
24
+ #ifndef __MINGW32__
25
+ #include <sys/socket.h>
26
+ #endif
27
+
28
+ /* *****************************************************************************
29
+ Available Globals
30
+ ***************************************************************************** */
31
+
32
+ typedef struct {
33
+ VALUE app;
34
+ VALUE env;
35
+ } iodine_http_settings_s;
36
+
37
+ /* these three are used also by iodin_rack_io.c */
38
+ VALUE IODINE_R_INPUT_DEFAULT;
39
+ VALUE IODINE_R_INPUT;
40
+ VALUE IODINE_R_HIJACK;
41
+ VALUE IODINE_R_HIJACK_IO;
42
+ VALUE IODINE_R_HIJACK_CB;
43
+
44
+ static VALUE RACK_UPGRADE;
45
+ static VALUE RACK_UPGRADE_Q;
46
+ static VALUE RACK_UPGRADE_SSE;
47
+ static VALUE RACK_UPGRADE_WEBSOCKET;
48
+ static VALUE UPGRADE_TCP;
49
+
50
+ static VALUE HTTP_ACCEPT;
51
+ static VALUE HTTP_USER_AGENT;
52
+ static VALUE HTTP_ACCEPT_ENCODING;
53
+ static VALUE HTTP_ACCEPT_LANGUAGE;
54
+ static VALUE HTTP_CONNECTION;
55
+ static VALUE HTTP_HOST;
56
+
57
+ static VALUE hijack_func_sym;
58
+ static ID close_method_id;
59
+ static ID each_method_id;
60
+ static ID attach_method_id;
61
+ static ID iodine_call_proc_id;
62
+ static ID fiber_result_var_id;
63
+ static VALUE http_wait_directive;
64
+
65
+ static VALUE env_template_no_upgrade;
66
+ static VALUE env_template_websockets;
67
+ static VALUE env_template_sse;
68
+
69
+ static rb_encoding *IodineUTF8Encoding;
70
+ static rb_encoding *IodineBinaryEncoding;
71
+
72
+ static uint8_t support_xsendfile = 0;
73
+
74
+ #define rack_declare(rack_name) static VALUE rack_name
75
+
76
+ #define rack_set(rack_name, str) \
77
+ (rack_name) = rb_enc_str_new((str), strlen((str)), IodineBinaryEncoding); \
78
+ rb_global_variable(&(rack_name)); \
79
+ rb_obj_freeze(rack_name);
80
+ #define rack_set_sym(rack_name, sym) \
81
+ (rack_name) = rb_id2sym(rb_intern2((sym), strlen((sym)))); \
82
+ rb_global_variable(&(rack_name));
83
+
84
+ #define rack_autoset(rack_name) rack_set((rack_name), #rack_name)
85
+
86
+ // static uint8_t IODINE_IS_DEVELOPMENT_MODE = 0;
87
+
88
+ rack_declare(HTTP_SCHEME);
89
+ rack_declare(HTTPS_SCHEME);
90
+ rack_declare(QUERY_ESTRING);
91
+ rack_declare(REQUEST_METHOD);
92
+ rack_declare(PATH_INFO);
93
+ rack_declare(QUERY_STRING);
94
+ rack_declare(QUERY_ESTRING);
95
+ rack_declare(SERVER_NAME);
96
+ rack_declare(SERVER_PORT);
97
+ rack_declare(SERVER_PROTOCOL);
98
+ rack_declare(HTTP_VERSION);
99
+ rack_declare(REMOTE_ADDR);
100
+ rack_declare(CONTENT_LENGTH);
101
+ rack_declare(CONTENT_TYPE);
102
+ rack_declare(R_URL_SCHEME); // rack.url_scheme
103
+ rack_declare(R_INPUT); // rack.input
104
+ rack_declare(XSENDFILE); // for X-Sendfile support
105
+ rack_declare(XSENDFILE_TYPE); // for X-Sendfile support
106
+ rack_declare(XSENDFILE_TYPE_HEADER); // for X-Sendfile support
107
+ rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support
108
+ rack_declare(IODINE_REQUEST_ID);
109
+
110
+ /* used internally to handle requests */
111
+ typedef struct {
112
+ http_s *h;
113
+ FIOBJ body;
114
+ enum iodine_http_response_type_enum {
115
+ IODINE_HTTP_NONE,
116
+ IODINE_HTTP_SENDBODY,
117
+ IODINE_HTTP_XSENDFILE,
118
+ IODINE_HTTP_EMPTY,
119
+ IODINE_HTTP_ERROR,
120
+ IODINE_HTTP_WAIT,
121
+ } type;
122
+ enum iodine_upgrade_type_enum {
123
+ IODINE_UPGRADE_NONE = 0,
124
+ IODINE_UPGRADE_WEBSOCKET,
125
+ IODINE_UPGRADE_SSE,
126
+ } upgrade;
127
+ } iodine_http_request_handle_s;
128
+
129
+ /* *****************************************************************************
130
+ WebSocket support
131
+ ***************************************************************************** */
132
+
133
+ typedef struct {
134
+ char *data;
135
+ size_t size;
136
+ uint8_t is_text;
137
+ VALUE io;
138
+ } iodine_msg2ruby_s;
139
+
140
+ static void *iodine_ws_fire_message(void *msg_) {
141
+ iodine_msg2ruby_s *msg = msg_;
142
+ VALUE data = rb_enc_str_new(
143
+ msg->data, msg->size,
144
+ (msg->is_text ? rb_utf8_encoding() : rb_ascii8bit_encoding()));
145
+ iodine_connection_fire_event(msg->io, IODINE_CONNECTION_ON_MESSAGE, data);
146
+ return NULL;
147
+ }
148
+
149
+ static void iodine_ws_on_message(ws_s *ws, fio_str_info_s data,
150
+ uint8_t is_text) {
151
+ iodine_msg2ruby_s msg = {
152
+ .data = data.data,
153
+ .size = data.len,
154
+ .is_text = is_text,
155
+ .io = (VALUE)websocket_udata_get(ws),
156
+ };
157
+ IodineCaller.enterGVL(iodine_ws_fire_message, &msg);
158
+ }
159
+ /**
160
+ * The (optional) on_open callback will be called once the websocket
161
+ * connection is established and before is is registered with `facil`, so no
162
+ * `on_message` events are raised before `on_open` returns.
163
+ */
164
+ static void iodine_ws_on_open(ws_s *ws) {
165
+ VALUE h = (VALUE)websocket_udata_get(ws);
166
+ iodine_connection_s *c = iodine_connection_CData(h);
167
+ c->arg = ws;
168
+ c->uuid = websocket_uuid(ws);
169
+ iodine_connection_fire_event(h, IODINE_CONNECTION_ON_OPEN, Qnil);
170
+ }
171
+ /**
172
+ * The (optional) on_ready callback will be after a the underlying socket's
173
+ * buffer changes it's state from full to empty.
174
+ *
175
+ * If the socket's buffer is never used, the callback is never called.
176
+ */
177
+ static void iodine_ws_on_ready(ws_s *ws) {
178
+ iodine_connection_fire_event((VALUE)websocket_udata_get(ws),
179
+ IODINE_CONNECTION_ON_DRAINED, Qnil);
180
+ }
181
+ /**
182
+ * The (optional) on_shutdown callback will be called if a websocket
183
+ * connection is still open while the server is shutting down (called before
184
+ * `on_close`).
185
+ */
186
+ static void iodine_ws_on_shutdown(ws_s *ws) {
187
+ iodine_connection_fire_event((VALUE)websocket_udata_get(ws),
188
+ IODINE_CONNECTION_ON_SHUTDOWN, Qnil);
189
+ }
190
+ /**
191
+ * The (optional) on_close callback will be called once a websocket connection
192
+ * is terminated or failed to be established.
193
+ *
194
+ * The `uuid` is the connection's unique ID that can identify the Websocket. A
195
+ * value of `uuid == 0` indicates the Websocket connection wasn't established
196
+ * (an error occured).
197
+ *
198
+ * The `udata` is the user data as set during the upgrade or using the
199
+ * `websocket_udata_set` function.
200
+ */
201
+ static void iodine_ws_on_close(intptr_t uuid, void *udata) {
202
+ iodine_connection_fire_event((VALUE)udata, IODINE_CONNECTION_ON_CLOSE, Qnil);
203
+ (void)uuid;
204
+ }
205
+
206
+ static void iodine_ws_attach(http_s *h, VALUE handler, VALUE env) {
207
+ VALUE io =
208
+ iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL,
209
+ .handler = handler, .env = env, .uuid = 0);
210
+ if (io == Qnil)
211
+ return;
212
+
213
+ http_upgrade2ws(h, .on_message = iodine_ws_on_message,
214
+ .on_open = iodine_ws_on_open, .on_ready = iodine_ws_on_ready,
215
+ .on_shutdown = iodine_ws_on_shutdown,
216
+ .on_close = iodine_ws_on_close, .udata = (void *)io);
217
+ }
218
+
219
+ /* *****************************************************************************
220
+ SSE support
221
+ ***************************************************************************** */
222
+
223
+ static void iodine_sse_on_ready(http_sse_s *sse) {
224
+ iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_DRAINED,
225
+ Qnil);
226
+ }
227
+
228
+ static void iodine_sse_on_shutdown(http_sse_s *sse) {
229
+ iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_SHUTDOWN,
230
+ Qnil);
231
+ }
232
+ static void iodine_sse_on_close(http_sse_s *sse) {
233
+ iodine_connection_fire_event((VALUE)sse->udata, IODINE_CONNECTION_ON_CLOSE,
234
+ Qnil);
235
+ }
236
+
237
+ static void iodine_sse_on_open(http_sse_s *sse) {
238
+ VALUE h = (VALUE)sse->udata;
239
+ iodine_connection_s *c = iodine_connection_CData(h);
240
+ c->arg = sse;
241
+ c->uuid = http_sse2uuid(sse);
242
+ iodine_connection_fire_event(h, IODINE_CONNECTION_ON_OPEN, Qnil);
243
+ sse->on_ready = iodine_sse_on_ready;
244
+ fio_force_event(c->uuid, FIO_EVENT_ON_READY);
245
+ }
246
+
247
+ static void iodine_sse_attach(http_s *h, VALUE handler, VALUE env) {
248
+ VALUE io = iodine_connection_new(.type = IODINE_CONNECTION_SSE, .arg = NULL,
249
+ .handler = handler, .env = env, .uuid = 0);
250
+ if (io == Qnil)
251
+ return;
252
+
253
+ http_upgrade2sse(h, .on_open = iodine_sse_on_open,
254
+ .on_ready = NULL /* will be set after the on_open */,
255
+ .on_shutdown = iodine_sse_on_shutdown,
256
+ .on_close = iodine_sse_on_close, .udata = (void *)io);
257
+ }
258
+
259
+ /* *****************************************************************************
260
+ Copying data from the C request to the Rack's ENV
261
+ ***************************************************************************** */
262
+
263
+ #define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? ((c) & ~32) : (c))
264
+
265
+ static int iodine_copy2env_task(FIOBJ o, void *env_) {
266
+ VALUE env = (VALUE)env_;
267
+ FIOBJ name = fiobj_hash_key_in_loop();
268
+ fio_str_info_s tmp = fiobj_obj2cstr(name);
269
+ VALUE hname = (VALUE)0;
270
+ /* test for common header names, using pre-allocated memory */
271
+ if (tmp.len == 6 && !memcmp("accept", tmp.data, 6)) {
272
+ hname = HTTP_ACCEPT;
273
+ } else if (tmp.len == 10 && !memcmp("user-agent", tmp.data, 10)) {
274
+ hname = HTTP_USER_AGENT;
275
+ } else if (tmp.len == 15 && !memcmp("accept-encoding", tmp.data, 15)) {
276
+ hname = HTTP_ACCEPT_ENCODING;
277
+ } else if (tmp.len == 15 && !memcmp("accept-language", tmp.data, 15)) {
278
+ hname = HTTP_ACCEPT_LANGUAGE;
279
+ } else if (tmp.len == 10 && !memcmp("connection", tmp.data, 10)) {
280
+ hname = HTTP_CONNECTION;
281
+ } else if (tmp.len == 4 && !memcmp("host", tmp.data, 4)) {
282
+ hname = HTTP_HOST;
283
+ } else if (tmp.len > 123) {
284
+ char *buf = fio_malloc(tmp.len + 5);
285
+ memcpy(buf, "HTTP_", 5);
286
+ for (size_t i = 0; i < tmp.len; ++i) {
287
+ buf[i + 5] = (tmp.data[i] == '-') ? '_' : to_upper(tmp.data[i]);
288
+ }
289
+ hname = rb_enc_str_new(buf, tmp.len + 5, IodineBinaryEncoding);
290
+ fio_free(buf);
291
+ } else {
292
+ char buf[128];
293
+ memcpy(buf, "HTTP_", 5);
294
+ for (size_t i = 0; i < tmp.len; ++i) {
295
+ buf[i + 5] = (tmp.data[i] == '-') ? '_' : to_upper(tmp.data[i]);
296
+ }
297
+ hname = rb_enc_str_new(buf, tmp.len + 5, IodineBinaryEncoding);
298
+ }
299
+
300
+ if (FIOBJ_TYPE_IS(o, FIOBJ_T_STRING)) {
301
+ tmp = fiobj_obj2cstr(o);
302
+ rb_hash_aset(env, hname,
303
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
304
+
305
+ } else {
306
+ /* it's an array */
307
+ size_t count = fiobj_ary_count(o);
308
+ VALUE ary = rb_ary_new2(count);
309
+ rb_hash_aset(env, hname, ary);
310
+ for (size_t i = 0; i < count; ++i) {
311
+ tmp = fiobj_obj2cstr(fiobj_ary_index(o, i));
312
+ rb_ary_push(ary, rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
313
+ }
314
+ }
315
+ return 0;
316
+ }
317
+
318
+ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
319
+ VALUE env;
320
+ http_s *h = handle->h;
321
+ switch (handle->upgrade) {
322
+ case IODINE_UPGRADE_WEBSOCKET:
323
+ env = rb_hash_dup(env_template_websockets);
324
+ break;
325
+ case IODINE_UPGRADE_SSE:
326
+ env = rb_hash_dup(env_template_sse);
327
+ break;
328
+ case IODINE_UPGRADE_NONE: /* fallthrough */
329
+ default:
330
+ env = rb_hash_dup(env_template_no_upgrade);
331
+ break;
332
+ }
333
+ IodineStore.add(env);
334
+
335
+ fio_str_info_s tmp;
336
+ char *pos = NULL;
337
+ /* Copy basic data */
338
+ tmp = fiobj_obj2cstr(h->method);
339
+ rb_hash_aset(env, REQUEST_METHOD,
340
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
341
+ tmp = fiobj_obj2cstr(h->path);
342
+ rb_hash_aset(env, PATH_INFO,
343
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
344
+ if (h->query) {
345
+ tmp = fiobj_obj2cstr(h->query);
346
+ rb_hash_aset(env, QUERY_STRING,
347
+ tmp.len
348
+ ? rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding)
349
+ : QUERY_ESTRING);
350
+ } else {
351
+ rb_hash_aset(env, QUERY_STRING, QUERY_ESTRING);
352
+ }
353
+ {
354
+ // HTTP version appears twice
355
+ tmp = fiobj_obj2cstr(h->version);
356
+ VALUE hname = rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding);
357
+ rb_hash_aset(env, SERVER_PROTOCOL, hname);
358
+ rb_hash_aset(env, HTTP_VERSION, hname);
359
+ }
360
+
361
+ { // Support for Ruby web-console.
362
+ fio_str_info_s peer = http_peer_addr(h);
363
+ if (peer.len) {
364
+ rb_hash_aset(env, REMOTE_ADDR, rb_str_new(peer.data, peer.len));
365
+ }
366
+ }
367
+
368
+ /* handle the HOST header, including the possible host:#### format*/
369
+ static uint64_t host_hash = 0;
370
+ if (!host_hash)
371
+ host_hash = fiobj_hash_string("host", 4);
372
+ tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, host_hash));
373
+ pos = tmp.data;
374
+ while (*pos && *pos != ':')
375
+ pos++;
376
+ if (*pos == 0) {
377
+ rb_hash_aset(env, SERVER_NAME,
378
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
379
+ } else {
380
+ rb_hash_aset(
381
+ env, SERVER_NAME,
382
+ rb_enc_str_new(tmp.data, pos - tmp.data, IodineBinaryEncoding));
383
+ ++pos;
384
+ rb_hash_aset(
385
+ env, SERVER_PORT,
386
+ rb_enc_str_new(pos, tmp.len - (pos - tmp.data), IodineBinaryEncoding));
387
+ }
388
+
389
+ /* remove special headers */
390
+ {
391
+ static uint64_t content_length_hash = 0;
392
+ if (!content_length_hash)
393
+ content_length_hash = fiobj_hash_string("content-length", 14);
394
+ FIOBJ cl = fiobj_hash_get2(h->headers, content_length_hash);
395
+ if (cl) {
396
+ tmp = fiobj_obj2cstr(fiobj_hash_get2(h->headers, content_length_hash));
397
+ if (tmp.data) {
398
+ rb_hash_aset(env, CONTENT_LENGTH,
399
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
400
+ fiobj_hash_delete2(h->headers, content_length_hash);
401
+ }
402
+ }
403
+ }
404
+ {
405
+ static uint64_t content_type_hash = 0;
406
+ if (!content_type_hash)
407
+ content_type_hash = fiobj_hash_string("content-type", 12);
408
+ FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash);
409
+ if (ct) {
410
+ tmp = fiobj_obj2cstr(ct);
411
+ if (tmp.len && tmp.data) {
412
+ rb_hash_aset(env, CONTENT_TYPE,
413
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
414
+ fiobj_hash_delete2(h->headers, content_type_hash);
415
+ }
416
+ }
417
+ }
418
+ /* handle scheme / sepcial forwarding headers */
419
+ {
420
+ FIOBJ objtmp;
421
+ static uint64_t xforward_hash = 0;
422
+ if (!xforward_hash)
423
+ xforward_hash = fiobj_hash_string("x-forwarded-proto", 27);
424
+ static uint64_t forward_hash = 0;
425
+ if (!forward_hash)
426
+ forward_hash = fiobj_hash_string("forwarded", 9);
427
+ if ((objtmp = fiobj_hash_get2(h->headers, xforward_hash))) {
428
+ tmp = fiobj_obj2cstr(objtmp);
429
+ if (tmp.len >= 5 && !strncasecmp(tmp.data, "https", 5)) {
430
+ rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
431
+ } else if (tmp.len == 4 && !strncasecmp(tmp.data, "http", 4)) {
432
+ rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
433
+ } else {
434
+ rb_hash_aset(env, R_URL_SCHEME,
435
+ rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
436
+ }
437
+ } else if ((objtmp = fiobj_hash_get2(h->headers, forward_hash))) {
438
+ tmp = fiobj_obj2cstr(objtmp);
439
+ pos = tmp.data;
440
+ if (pos) {
441
+ while (*pos) {
442
+ if (((*(pos++) | 32) == 'p') && ((*(pos++) | 32) == 'r') &&
443
+ ((*(pos++) | 32) == 'o') && ((*(pos++) | 32) == 't') &&
444
+ ((*(pos++) | 32) == 'o') && ((*(pos++) | 32) == '=')) {
445
+ if ((pos[0] | 32) == 'h' && (pos[1] | 32) == 't' &&
446
+ (pos[2] | 32) == 't' && (pos[3] | 32) == 'p') {
447
+ if ((pos[4] | 32) == 's') {
448
+ rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
449
+ } else {
450
+ rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
451
+ }
452
+ } else {
453
+ char *tmp = pos;
454
+ while (*tmp && *tmp != ';')
455
+ tmp++;
456
+ rb_hash_aset(env, R_URL_SCHEME, rb_str_new(pos, tmp - pos));
457
+ }
458
+ break;
459
+ }
460
+ }
461
+ }
462
+ } else if (http_settings(h)->tls) {
463
+ /* no forwarding information, but we do have TLS */
464
+ rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
465
+ } else {
466
+ /* no TLS, no forwarding, assume `http`, which is the default */
467
+ }
468
+ }
469
+ {
470
+ rb_hash_aset(env, IODINE_REQUEST_ID, rb_str_new_cstr(fiobj_obj2cstr(handle->h->request_id).data));
471
+ }
472
+
473
+ /* add all remaining headers */
474
+ fiobj_each1(h->headers, 0, iodine_copy2env_task, (void *)env);
475
+ return env;
476
+ }
477
+ #undef add_str_to_env
478
+ #undef add_value_to_env
479
+ #undef add_header_to_env
480
+
481
+ /* *****************************************************************************
482
+ Handling the HTTP response
483
+ ***************************************************************************** */
484
+
485
+ // itterate through the headers and add them to the response buffer
486
+ // (we are recycling the request's buffer)
487
+ static int for_each_header_data(VALUE key, VALUE val, VALUE h_) {
488
+ http_s *h = (http_s *)h_;
489
+ // fprintf(stderr, "For_each - headers\n");
490
+ if (TYPE(val) == T_NIL || TYPE(key) == T_NIL)
491
+ return ST_CONTINUE;
492
+ if (TYPE(val) == T_ARRAY)
493
+ goto array_style_multi_value;
494
+ if (TYPE(key) != T_STRING)
495
+ key = IodineCaller.call(key, iodine_to_s_id);
496
+ if (TYPE(key) != T_STRING)
497
+ return ST_CONTINUE;
498
+ if (TYPE(val) != T_STRING) {
499
+ val = IodineCaller.call(val, iodine_to_s_id);
500
+ if (TYPE(val) != T_STRING)
501
+ return ST_STOP;
502
+ }
503
+ char *key_s = RSTRING_PTR(key);
504
+ int key_len = RSTRING_LEN(key);
505
+ char *val_s = RSTRING_PTR(val);
506
+ int val_len = RSTRING_LEN(val);
507
+ // make the headers lowercase
508
+
509
+ FIOBJ name = fiobj_str_new(key_s, key_len);
510
+ {
511
+ fio_str_info_s tmp = fiobj_obj2cstr(name);
512
+ for (int i = 0; i < key_len; ++i) {
513
+ tmp.data[i] = tolower(tmp.data[i]);
514
+ }
515
+ }
516
+ // scan the value for newline (\n) delimiters
517
+ char *pos_s = val_s;
518
+ char *pos_e = val_s + val_len;
519
+ while (pos_s < pos_e) {
520
+ // scanning for newline (\n) delimiters
521
+ char *const start = pos_s;
522
+ pos_s = memchr(pos_s, '\n', pos_e - pos_s);
523
+ if (!pos_s)
524
+ pos_s = pos_e;
525
+ http_set_header(h, name, fiobj_str_new(start, (pos_s - start)));
526
+ // move forward (skip the '\n' if exists)
527
+ ++pos_s;
528
+ }
529
+ fiobj_free(name);
530
+ // no errors, return 0
531
+ return ST_CONTINUE;
532
+
533
+ array_style_multi_value:
534
+ for (size_t i = 0, end = RARRAY_LEN(val); i < end; ++i) {
535
+ if (for_each_header_data(key, RARRAY_AREF(val, i), h_) == ST_CONTINUE)
536
+ continue;
537
+ return ST_STOP;
538
+ }
539
+ return ST_CONTINUE;
540
+ }
541
+
542
+ // writes the body to the response object
543
+ static VALUE for_each_body_string(VALUE str, VALUE body_, int argc,
544
+ VALUE *argv) {
545
+ // fprintf(stderr, "For_each - body\n");
546
+ // write body
547
+ if (TYPE(str) != T_STRING) {
548
+ FIO_LOG_ERROR("(Iodine) response body not a String\n");
549
+ return Qfalse;
550
+ }
551
+ if (RSTRING_LEN(str) && RSTRING_PTR(str)) {
552
+ fiobj_str_write((FIOBJ)body_, RSTRING_PTR(str), RSTRING_LEN(str));
553
+ }
554
+ return Qtrue;
555
+ (void)argc;
556
+ (void)argv;
557
+ }
558
+
559
+ static inline int ruby2c_response_send(iodine_http_request_handle_s *handle,
560
+ VALUE rbresponse, VALUE env) {
561
+ (void)(env);
562
+ VALUE body = rb_ary_entry(rbresponse, 2);
563
+ if (handle->h->status < 200 || handle->h->status == 204 ||
564
+ handle->h->status == 304) {
565
+ if (body && rb_respond_to(body, close_method_id))
566
+ IodineCaller.call(body, close_method_id);
567
+ body = Qnil;
568
+ handle->type = IODINE_HTTP_NONE;
569
+ return 0;
570
+ }
571
+ if (TYPE(body) == T_ARRAY) {
572
+ if (RARRAY_LEN(body) == 0) { // only headers
573
+ handle->type = IODINE_HTTP_EMPTY;
574
+ return 0;
575
+ } else if (RARRAY_LEN(body) == 1) { // [String] is likely
576
+ body = rb_ary_entry(body, 0);
577
+ // fprintf(stderr, "Body was a single item array, unpacket to string\n");
578
+ }
579
+ }
580
+
581
+ if (TYPE(body) == T_STRING) {
582
+ // fprintf(stderr, "Review body as String\n");
583
+ handle->type = IODINE_HTTP_NONE;
584
+ if (RSTRING_LEN(body)) {
585
+ handle->body = fiobj_str_new(RSTRING_PTR(body), RSTRING_LEN(body));
586
+ handle->type = IODINE_HTTP_SENDBODY;
587
+ }
588
+ return 0;
589
+ } else if (rb_respond_to(body, each_method_id)) {
590
+ // fprintf(stderr, "Review body as for-each ...\n");
591
+ handle->body = fiobj_str_buf(1);
592
+ handle->type = IODINE_HTTP_SENDBODY;
593
+ IodineCaller.call_with_block(body, each_method_id, 0, NULL,
594
+ (VALUE)handle->body, for_each_body_string);
595
+ // we need to call `close` in case the object is an IO / BodyProxy
596
+ if (rb_respond_to(body, close_method_id))
597
+ IodineCaller.call(body, close_method_id);
598
+ return 0;
599
+ }
600
+ return -1;
601
+ }
602
+
603
+ /* *****************************************************************************
604
+ Handling Upgrade cases
605
+ ***************************************************************************** */
606
+
607
+ static inline int ruby2c_review_upgrade(iodine_http_request_handle_s *req,
608
+ VALUE rbresponse, VALUE env) {
609
+ http_s *h = req->h;
610
+ VALUE handler;
611
+ if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_CB)) != Qnil) {
612
+ // send headers
613
+ http_finish(h);
614
+ // call the callback
615
+ VALUE io_ruby = IodineCaller.call(rb_hash_aref(env, IODINE_R_HIJACK),
616
+ iodine_call_proc_id);
617
+ IodineCaller.call2(handler, iodine_call_proc_id, 1, &io_ruby);
618
+ goto upgraded;
619
+ } else if ((handler = rb_hash_aref(env, IODINE_R_HIJACK_IO)) != Qnil) {
620
+ // do nothing, just cleanup
621
+ goto upgraded;
622
+ } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil) {
623
+ goto tcp_ip_upgrade;
624
+ } else {
625
+ switch (req->upgrade) {
626
+ case IODINE_UPGRADE_WEBSOCKET:
627
+ if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
628
+ // use response as existing base for native websocket upgrade
629
+ iodine_ws_attach(h, handler, env);
630
+ goto upgraded;
631
+ }
632
+ break;
633
+ case IODINE_UPGRADE_SSE:
634
+ if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
635
+ // use response as existing base for SSE upgrade
636
+ iodine_sse_attach(h, handler, env);
637
+ goto upgraded;
638
+ }
639
+ break;
640
+ default:
641
+ if ((handler = rb_hash_aref(env, RACK_UPGRADE)) != Qnil) {
642
+ tcp_ip_upgrade : {
643
+ // use response as existing base for raw TCP/IP upgrade
644
+ intptr_t uuid = http_hijack(h, NULL);
645
+ // send headers
646
+ http_finish(h);
647
+ // upgrade protocol to raw TCP/IP
648
+ iodine_tcp_attch_uuid(uuid, handler);
649
+ goto upgraded;
650
+ }
651
+ }
652
+ break;
653
+ }
654
+ }
655
+ return 0;
656
+
657
+ upgraded:
658
+ // get body object to close it (if needed)
659
+ handler = rb_ary_entry(rbresponse, 2);
660
+ // we need to call `close` in case the object is an IO / BodyProxy
661
+ if (handler != Qnil && rb_respond_to(handler, close_method_id))
662
+ IodineCaller.call(handler, close_method_id);
663
+ return 1;
664
+ }
665
+
666
+ /* *****************************************************************************
667
+ Handling HTTP requests
668
+ ***************************************************************************** */
669
+
670
+ static inline void *iodine_handle_request_in_GVL(void *handle_) {
671
+ iodine_http_request_handle_s *handle = handle_;
672
+ VALUE rbresponse = 0;
673
+ VALUE env = 0;
674
+ http_s *h = handle->h;
675
+ if (!h->udata)
676
+ goto err_not_found;
677
+
678
+ // create / register env variable
679
+ env = copy2env(handle);
680
+ // create rack.io
681
+ VALUE tmp = IodineRackIO.create(h, env);
682
+ // pass env variable to handler
683
+ rbresponse =
684
+ IodineCaller.call2((VALUE)h->udata, iodine_call_proc_id, 1, &env);
685
+ // close rack.io
686
+ IodineRackIO.close(tmp);
687
+ // test handler's return value
688
+ if (rbresponse == 0 || rbresponse == Qnil || TYPE(rbresponse) != T_ARRAY) {
689
+ goto internal_error;
690
+ }
691
+
692
+ tmp = rb_ary_entry(rbresponse, 0);
693
+ // rack will return `[:__http_defer__, fiber_to_wait_on]` in case the request needs to be paused
694
+ if (TYPE(tmp) == T_SYMBOL && tmp == http_wait_directive) {
695
+ h->fiber = (void *)IodineStore.add(rb_ary_entry(rbresponse, 1));
696
+ goto defer;
697
+ }
698
+
699
+ IodineStore.add(rbresponse);
700
+ // set response status
701
+ if (TYPE(tmp) == T_STRING) {
702
+ char *data = RSTRING_PTR(tmp);
703
+ h->status = fio_atol(&data);
704
+ } else if (TYPE(tmp) == T_FIXNUM) {
705
+ h->status = FIX2ULONG(tmp);
706
+ } else {
707
+ goto internal_error;
708
+ }
709
+
710
+ // handle header copy from ruby land to C land.
711
+ VALUE response_headers = rb_ary_entry(rbresponse, 1);
712
+ if (TYPE(response_headers) != T_HASH)
713
+ goto internal_error;
714
+ // extract the X-Sendfile header (never show original path)
715
+ // X-Sendfile support only present when iodine serves static files.
716
+ VALUE xfiles;
717
+ if (support_xsendfile &&
718
+ (xfiles = rb_hash_aref(response_headers, XSENDFILE)) != Qnil &&
719
+ TYPE(xfiles) == T_STRING) {
720
+ if (OBJ_FROZEN(response_headers)) {
721
+ response_headers = rb_hash_dup(response_headers);
722
+ }
723
+ IodineStore.add(response_headers);
724
+ handle->body = fiobj_str_new(RSTRING_PTR(xfiles), RSTRING_LEN(xfiles));
725
+ handle->type = IODINE_HTTP_XSENDFILE;
726
+ rb_hash_delete(response_headers, XSENDFILE);
727
+ // remove content length headers, as this will be controled by iodine
728
+ rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER);
729
+ // review each header and write it to the response.
730
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h));
731
+ IodineStore.remove(response_headers);
732
+ // send the file directly and finish
733
+ goto finish;
734
+ }
735
+ // review each header and write it to the response.
736
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h));
737
+ // review for upgrade.
738
+ if ((intptr_t)h->status < 300 &&
739
+ ruby2c_review_upgrade(handle, rbresponse, env))
740
+ goto external_done;
741
+ // send the request body.
742
+ if (ruby2c_response_send(handle, rbresponse, env))
743
+ goto internal_error;
744
+
745
+ finish:
746
+ IodineStore.remove(rbresponse);
747
+ IodineStore.remove(env);
748
+ return NULL;
749
+
750
+ external_done:
751
+ IodineStore.remove(rbresponse);
752
+ IodineStore.remove(env);
753
+ handle->type = IODINE_HTTP_NONE;
754
+ return NULL;
755
+
756
+ err_not_found:
757
+ IodineStore.remove(rbresponse);
758
+ IodineStore.remove(env);
759
+ h->status = 404;
760
+ handle->type = IODINE_HTTP_ERROR;
761
+ return NULL;
762
+
763
+ internal_error:
764
+ IodineStore.remove(rbresponse);
765
+ IodineStore.remove(env);
766
+ h->status = 500;
767
+ handle->type = IODINE_HTTP_ERROR;
768
+ return NULL;
769
+
770
+ defer:
771
+ IodineStore.remove(env);
772
+ handle->type = IODINE_HTTP_WAIT;
773
+ return NULL;
774
+ }
775
+
776
+ // called once a request that was paused previously needs to be resumed
777
+ static inline void *iodine_handle_deferred_request_in_GVL(void *handle_) {
778
+ iodine_http_request_handle_s *handle = handle_;
779
+ http_s *h = handle->h;
780
+
781
+ VALUE rbresponse = rb_ivar_get((VALUE)h->fiber, fiber_result_var_id);
782
+ VALUE tmp = rb_ary_entry(rbresponse, 0);
783
+
784
+ // set response status
785
+ if (TYPE(tmp) == T_STRING) {
786
+ char *data = RSTRING_PTR(tmp);
787
+ h->status = fio_atol(&data);
788
+ } else if (TYPE(tmp) == T_FIXNUM) {
789
+ h->status = FIX2ULONG(tmp);
790
+ } else {
791
+ goto internal_error;
792
+ }
793
+
794
+ // handle header copy from ruby land to C land.
795
+ VALUE response_headers = rb_ary_entry(rbresponse, 1);
796
+
797
+ // review each header and write it to the response.
798
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(h));
799
+
800
+ // send the request body.
801
+ if (ruby2c_response_send(handle, rbresponse, 0))
802
+ goto internal_error;
803
+
804
+ return NULL;
805
+
806
+ internal_error:
807
+ h->status = 500;
808
+ handle->type = IODINE_HTTP_ERROR;
809
+ return NULL;
810
+ }
811
+
812
+ static inline void
813
+ iodine_perform_handle_action(iodine_http_request_handle_s handle) {
814
+ switch (handle.type) {
815
+ case IODINE_HTTP_SENDBODY: {
816
+ fio_str_info_s data = fiobj_obj2cstr(handle.body);
817
+ http_send_body(handle.h, data.data, data.len);
818
+ fiobj_free(handle.body);
819
+ break;
820
+ }
821
+ case IODINE_HTTP_XSENDFILE: {
822
+ /* remove chunked content-encoding header, if any (Rack issue #1266) */
823
+ if (fiobj_obj2cstr(
824
+ fiobj_hash_get2(handle.h->private_data.out_headers,
825
+ fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING)))
826
+ .len == 7)
827
+ fiobj_hash_delete2(handle.h->private_data.out_headers,
828
+ fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING));
829
+ fio_str_info_s data = fiobj_obj2cstr(handle.body);
830
+ if (http_sendfile2(handle.h, data.data, data.len, NULL, 0)) {
831
+ http_send_error(handle.h, 404);
832
+ }
833
+ fiobj_free(handle.body);
834
+ break;
835
+ }
836
+ case IODINE_HTTP_EMPTY:
837
+ http_finish(handle.h);
838
+ fiobj_free(handle.body);
839
+ break;
840
+ case IODINE_HTTP_NONE:
841
+ /* nothing to do - this had to be performed within the Ruby GIL :-( */
842
+ break;
843
+ case IODINE_HTTP_ERROR:
844
+ http_send_error(handle.h, handle.h->status);
845
+ fiobj_free(handle.body);
846
+ break;
847
+ }
848
+ }
849
+
850
+ // gets called by `http_resume`
851
+ static inline void http_resume_deferred_request_handler(http_s *h) {
852
+ iodine_http_request_handle_s handle = (iodine_http_request_handle_s){
853
+ .h = h,
854
+ .upgrade = IODINE_UPGRADE_NONE,
855
+ };
856
+
857
+ IodineCaller.enterGVL((void *(*)(void *))iodine_handle_deferred_request_in_GVL,
858
+ &handle);
859
+
860
+ iodine_perform_handle_action(handle);
861
+ }
862
+
863
+ static inline void on_iodine_request_id_message(fio_msg_s *msg) {
864
+ http_resume((http_pause_handle_s *)msg->udata1, http_resume_deferred_request_handler, NULL);
865
+ }
866
+
867
+ static inline void http_close_deferred_request_handler(void *sub) {
868
+ fio_unsubscribe((subscription_s *)sub);
869
+ }
870
+
871
+ // when Ruby sends a message into the `request_id` channel means the fiber attached to
872
+ // to the `http_s h` var can be resumed
873
+ static inline void http_pause_request_handler(http_pause_handle_s *s) {
874
+ subscription_s *sub = fio_subscribe(.channel = fiobj_obj2cstr(s->h->request_id),
875
+ .on_message = on_iodine_request_id_message,
876
+ .udata1 = (void *)s);
877
+ fio_uuid_link(s->uuid, (void *)sub, http_close_deferred_request_handler);
878
+ }
879
+
880
+ static void on_rack_request(http_s *h) {
881
+ iodine_http_request_handle_s handle = (iodine_http_request_handle_s){
882
+ .h = h,
883
+ .upgrade = IODINE_UPGRADE_NONE,
884
+ };
885
+ IodineCaller.enterGVL((void *(*)(void *))iodine_handle_request_in_GVL,
886
+ &handle);
887
+
888
+ if (handle.type == IODINE_HTTP_WAIT) {
889
+ http_pause(handle.h, http_pause_request_handler);
890
+ } else {
891
+ iodine_perform_handle_action(handle);
892
+ }
893
+ }
894
+
895
+ static void on_rack_upgrade(http_s *h, char *proto, size_t len) {
896
+ iodine_http_request_handle_s handle = (iodine_http_request_handle_s){.h = h};
897
+ if (len == 9 && (proto[1] == 'e' || proto[1] == 'E')) {
898
+ handle.upgrade = IODINE_UPGRADE_WEBSOCKET;
899
+ } else if (len == 3 && proto[0] == 's') {
900
+ handle.upgrade = IODINE_UPGRADE_SSE;
901
+ }
902
+ /* when we stop supporting custom Upgrade headers: */
903
+ // else {
904
+ // http_send_error(h, 400);
905
+ // return;
906
+ // }
907
+ IodineCaller.enterGVL(iodine_handle_request_in_GVL, &handle);
908
+ iodine_perform_handle_action(handle);
909
+ (void)proto;
910
+ (void)len;
911
+ }
912
+
913
+ /* *****************************************************************************
914
+ Rack `env` Template Initialization
915
+ ***************************************************************************** */
916
+
917
+ static void initialize_env_template(void) {
918
+ if (env_template_no_upgrade)
919
+ return;
920
+ env_template_no_upgrade = rb_hash_new();
921
+ IodineStore.add(env_template_no_upgrade);
922
+
923
+ #define add_str_to_env(env, key, value) \
924
+ { \
925
+ VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
926
+ rb_obj_freeze(k); \
927
+ VALUE v = rb_enc_str_new((value), strlen((value)), IodineBinaryEncoding); \
928
+ rb_obj_freeze(v); \
929
+ rb_hash_aset(env, k, v); \
930
+ }
931
+ #define add_value_to_env(env, key, value) \
932
+ { \
933
+ VALUE k = rb_enc_str_new((key), strlen((key)), IodineBinaryEncoding); \
934
+ rb_obj_freeze(k); \
935
+ rb_hash_aset((env), k, value); \
936
+ }
937
+
938
+ /* Set global template */
939
+ rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE_Q, Qnil);
940
+ rb_hash_aset(env_template_no_upgrade, RACK_UPGRADE, Qnil);
941
+ {
942
+ /* add the rack.version */
943
+ static VALUE rack_version = 0;
944
+ if (!rack_version) {
945
+ rack_version = rb_ary_new(); // rb_ary_new is Ruby 2.0 compatible
946
+ rb_ary_push(rack_version, INT2FIX(1));
947
+ rb_ary_push(rack_version, INT2FIX(3));
948
+ rb_global_variable(&rack_version);
949
+ rb_ary_freeze(rack_version);
950
+ }
951
+ add_value_to_env(env_template_no_upgrade, "rack.version", rack_version);
952
+ }
953
+
954
+ {
955
+ const char *sn = getenv("SCRIPT_NAME");
956
+ if (!sn || (sn[0] == '/' && sn[1] == 0)) {
957
+ sn = "";
958
+ }
959
+ add_str_to_env(env_template_no_upgrade, "SCRIPT_NAME", sn);
960
+ }
961
+ rb_hash_aset(env_template_no_upgrade, IODINE_R_INPUT, IODINE_R_INPUT_DEFAULT);
962
+ add_value_to_env(env_template_no_upgrade, "rack.errors", rb_stderr);
963
+ add_value_to_env(env_template_no_upgrade, "rack.hijack?", Qtrue);
964
+ add_value_to_env(env_template_no_upgrade, "rack.multiprocess", Qtrue);
965
+ add_value_to_env(env_template_no_upgrade, "rack.multithread", Qtrue);
966
+ add_value_to_env(env_template_no_upgrade, "rack.run_once", Qfalse);
967
+ /* default schema to http, it might be updated later */
968
+ rb_hash_aset(env_template_no_upgrade, R_URL_SCHEME, HTTP_SCHEME);
969
+ /* placeholders... minimize rehashing*/
970
+ rb_hash_aset(env_template_no_upgrade, HTTP_VERSION, QUERY_STRING);
971
+ rb_hash_aset(env_template_no_upgrade, IODINE_R_HIJACK, QUERY_STRING);
972
+ rb_hash_aset(env_template_no_upgrade, PATH_INFO, QUERY_STRING);
973
+ rb_hash_aset(env_template_no_upgrade, QUERY_STRING, QUERY_STRING);
974
+ rb_hash_aset(env_template_no_upgrade, REMOTE_ADDR, QUERY_STRING);
975
+ rb_hash_aset(env_template_no_upgrade, REQUEST_METHOD, QUERY_STRING);
976
+ rb_hash_aset(env_template_no_upgrade, SERVER_NAME, QUERY_STRING);
977
+ rb_hash_aset(env_template_no_upgrade, SERVER_PROTOCOL, QUERY_STRING);
978
+ rb_hash_aset(env_template_no_upgrade, IODINE_REQUEST_ID, QUERY_STRING);
979
+
980
+ /* WebSocket upgrade support */
981
+ env_template_websockets = rb_hash_dup(env_template_no_upgrade);
982
+ IodineStore.add(env_template_websockets);
983
+ rb_hash_aset(env_template_websockets, RACK_UPGRADE_Q, RACK_UPGRADE_WEBSOCKET);
984
+
985
+ /* SSE upgrade support */
986
+ env_template_sse = rb_hash_dup(env_template_no_upgrade);
987
+ IodineStore.add(env_template_sse);
988
+ rb_hash_aset(env_template_sse, RACK_UPGRADE_Q, RACK_UPGRADE_SSE);
989
+
990
+ #undef add_value_to_env
991
+ #undef add_str_to_env
992
+ }
993
+
994
+ /* *****************************************************************************
995
+ Listenninng to HTTP
996
+ *****************************************************************************
997
+ */
998
+
999
+ static void free_iodine_http(http_settings_s *s) {
1000
+ IodineStore.remove((VALUE)s->udata);
1001
+ }
1002
+
1003
+ // clang-format off
1004
+ /**
1005
+ Listens to incoming HTTP connections and handles incoming requests using the
1006
+ Rack specification.
1007
+
1008
+ This is delegated to a lower level C HTTP and Websocket implementation, no
1009
+ Ruby object will be crated except the `env` object required by the Rack
1010
+ specifications.
1011
+
1012
+ Accepts a single Hash argument with the following properties:
1013
+
1014
+ (it's possible to set default values using the {Iodine::DEFAULT_HTTP_ARGS} Hash)
1015
+
1016
+ app:: the Rack application that handles incoming requests. Default: `nil`.
1017
+ port:: the port to listen to. Default: 3000.
1018
+ address:: the address to bind to. Default: binds to all possible addresses.
1019
+ log:: enable response logging (Hijacked sockets aren't logged). Default: off.
1020
+ public:: The root public folder for static file service. Default: none.
1021
+ timeout:: Timeout for inactive HTTP/1.x connections. Defaults: 40 seconds.
1022
+ max_body:: The maximum body size for incoming HTTP messages in bytes. Default: ~50Mib.
1023
+ max_headers:: The maximum total header length for incoming HTTP messages. Default: ~64Kib.
1024
+ max_msg:: The maximum Websocket message size allowed. Default: ~250Kib.
1025
+ ping:: The Websocket `ping` interval. Default: 40 seconds.
1026
+
1027
+ Either the `app` or the `public` properties are required. If niether exists,
1028
+ the function will fail. If both exist, Iodine will serve static files as well
1029
+ as dynamic requests.
1030
+
1031
+ When using the static file server, it's possible to serve `gzip` versions of
1032
+ the static files by saving a compressed version with the `gz` extension (i.e.
1033
+ `styles.css.gz`).
1034
+
1035
+ `gzip` will only be served to clients tat support the `gzip` transfer
1036
+ encoding.
1037
+
1038
+ Once HTTP/2 is supported (planned, but probably very far away), HTTP/2
1039
+ timeouts will be dynamically managed by Iodine. The `timeout` option is only
1040
+ relevant to HTTP/1.x connections.
1041
+ */
1042
+ intptr_t iodine_http_listen(iodine_connection_args_s args){
1043
+ // clang-format on
1044
+ if (args.public.data) {
1045
+ rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE, XSENDFILE);
1046
+ rb_hash_aset(env_template_no_upgrade, XSENDFILE_TYPE_HEADER, XSENDFILE);
1047
+ support_xsendfile = 1;
1048
+ }
1049
+ IodineStore.add(args.handler);
1050
+ #ifdef __MINGW32__
1051
+ intptr_t uuid = http_listen(
1052
+ args.port.data, args.address.data, .on_request = on_rack_request,
1053
+ .on_upgrade = on_rack_upgrade, .udata = (void *)args.handler,
1054
+ .timeout = args.timeout, .ws_timeout = args.ping,
1055
+ .ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers,
1056
+ .on_finish = free_iodine_http, .log = args.log,
1057
+ .max_body_size = args.max_body, .public_folder = args.public.data);
1058
+ #else
1059
+ intptr_t uuid = http_listen(
1060
+ args.port.data, args.address.data, .on_request = on_rack_request,
1061
+ .on_upgrade = on_rack_upgrade, .udata = (void *)args.handler,
1062
+ .tls = args.tls, .timeout = args.timeout, .ws_timeout = args.ping,
1063
+ .ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers,
1064
+ .on_finish = free_iodine_http, .log = args.log,
1065
+ .max_body_size = args.max_body, .public_folder = args.public.data);
1066
+ #endif
1067
+ if (uuid == -1)
1068
+ return uuid;
1069
+
1070
+ if ((args.handler == Qnil || args.handler == Qfalse)) {
1071
+ FIO_LOG_WARNING("(listen) no handler / app, the HTTP service on port %s "
1072
+ "will only serve "
1073
+ "static files.",
1074
+ args.port.data ? args.port.data : args.address.data);
1075
+ }
1076
+ if (args.public.data) {
1077
+ FIO_LOG_INFO("Serving static files from %s", args.public.data);
1078
+ }
1079
+
1080
+ return uuid;
1081
+ }
1082
+
1083
+ /* *****************************************************************************
1084
+ HTTP Websocket Connect
1085
+ ***************************************************************************** */
1086
+
1087
+ typedef struct {
1088
+ FIOBJ method;
1089
+ FIOBJ headers;
1090
+ FIOBJ cookies;
1091
+ FIOBJ body;
1092
+ VALUE io;
1093
+ } request_data_s;
1094
+
1095
+ static request_data_s *request_data_create(iodine_connection_args_s *args) {
1096
+ request_data_s *r = fio_malloc(sizeof(*r));
1097
+ FIO_ASSERT_ALLOC(r);
1098
+ VALUE io =
1099
+ iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL,
1100
+ .handler = args->handler, .env = Qnil, .uuid = 0);
1101
+
1102
+ *r = (request_data_s){
1103
+ .method = fiobj_str_new(args->method.data, args->method.len),
1104
+ .headers = fiobj_dup(args->headers),
1105
+ .cookies = fiobj_dup(args->cookies),
1106
+ .body = fiobj_str_new(args->body.data, args->body.len),
1107
+ .io = io,
1108
+ };
1109
+ return r;
1110
+ }
1111
+
1112
+ static void request_data_destroy(request_data_s *r) {
1113
+ fiobj_free(r->method);
1114
+ fiobj_free(r->body);
1115
+ fiobj_free(r->headers);
1116
+ fiobj_free(r->cookies);
1117
+ fio_free(r);
1118
+ }
1119
+
1120
+ static int each_header_ws_client_task(FIOBJ val, void *h_) {
1121
+ http_s *h = h_;
1122
+ FIOBJ key = fiobj_hash_key_in_loop();
1123
+ http_set_header(h, key, fiobj_dup(val));
1124
+ return 0;
1125
+ }
1126
+ static int each_cookie_ws_client_task(FIOBJ val, void *h_) {
1127
+ http_s *h = h_;
1128
+ FIOBJ key = fiobj_hash_key_in_loop();
1129
+ fio_str_info_s n = fiobj_obj2cstr(key);
1130
+ fio_str_info_s v = fiobj_obj2cstr(val);
1131
+ http_set_cookie(h, .name = n.data, .name_len = n.len, .value = v.data,
1132
+ .value_len = v.len);
1133
+ return 0;
1134
+ }
1135
+
1136
+ static void ws_client_http_connected(http_s *h) {
1137
+ request_data_s *s = h->udata;
1138
+ if (!s)
1139
+ return;
1140
+ h->udata = http_settings(h)->udata = NULL;
1141
+ if (!h->path) {
1142
+ h->path = fiobj_str_new("/", 1);
1143
+ }
1144
+ /* TODO: add headers and cookies */
1145
+ fiobj_each1(s->headers, 0, each_header_ws_client_task, h);
1146
+ fiobj_each1(s->headers, 0, each_cookie_ws_client_task, h);
1147
+ if (s->io && s->io != Qnil)
1148
+ http_upgrade2ws(
1149
+ h, .on_message = iodine_ws_on_message, .on_open = iodine_ws_on_open,
1150
+ .on_ready = iodine_ws_on_ready, .on_shutdown = iodine_ws_on_shutdown,
1151
+ .on_close = iodine_ws_on_close, .udata = (void *)s->io);
1152
+ request_data_destroy(s);
1153
+ }
1154
+
1155
+ static void ws_client_http_connection_finished(http_settings_s *settings) {
1156
+ if (!settings)
1157
+ return;
1158
+ request_data_s *s = settings->udata;
1159
+ if (s) {
1160
+ if (s->io && s->io != Qnil)
1161
+ iodine_connection_fire_event(s->io, IODINE_CONNECTION_ON_CLOSE, Qnil);
1162
+ request_data_destroy(s);
1163
+ }
1164
+ }
1165
+
1166
+ /** Connects to a (remote) WebSocket service. */
1167
+ intptr_t iodine_ws_connect(iodine_connection_args_s args) {
1168
+ // http_connect(url, unixaddr, struct http_settings_s)
1169
+ uint8_t is_unix_socket = 0;
1170
+ if (memchr(args.address.data, '/', args.address.len)) {
1171
+ is_unix_socket = 1;
1172
+ }
1173
+ FIOBJ url_tmp = FIOBJ_INVALID;
1174
+ if (!args.url.data) {
1175
+ url_tmp = fiobj_str_buf(64);
1176
+ #ifndef __MINGW32__
1177
+ if (args.tls)
1178
+ fiobj_str_write(url_tmp, "wss://", 6);
1179
+ else
1180
+ #endif
1181
+ fiobj_str_write(url_tmp, "ws://", 5);
1182
+ if (!is_unix_socket) {
1183
+ fiobj_str_write(url_tmp, args.address.data, args.address.len);
1184
+ if (args.port.data) {
1185
+ fiobj_str_write(url_tmp, ":", 1);
1186
+ fiobj_str_write(url_tmp, args.port.data, args.port.len);
1187
+ }
1188
+ }
1189
+ if (args.path.data)
1190
+ fiobj_str_write(url_tmp, args.path.data, args.path.len);
1191
+ else
1192
+ fiobj_str_write(url_tmp, "/", 1);
1193
+ args.url = fiobj_obj2cstr(url_tmp);
1194
+ }
1195
+
1196
+ #ifdef __MINGW32__
1197
+ intptr_t uuid =
1198
+ http_connect(args.url.data, (is_unix_socket ? args.address.data : NULL),
1199
+ .udata = request_data_create(&args),
1200
+ .on_response = ws_client_http_connected,
1201
+ .on_finish = ws_client_http_connection_finished);
1202
+ #else
1203
+ intptr_t uuid = http_connect(
1204
+ args.url.data, (is_unix_socket ? args.address.data : NULL),
1205
+ .udata = request_data_create(&args),
1206
+ .on_response = ws_client_http_connected,
1207
+ .on_finish = ws_client_http_connection_finished, .tls = args.tls);
1208
+ #endif
1209
+ fiobj_free(url_tmp);
1210
+ return uuid;
1211
+ }
1212
+
1213
+ /* *****************************************************************************
1214
+ Initialization
1215
+ ***************************************************************************** */
1216
+
1217
+ void iodine_init_http(void) {
1218
+
1219
+ rack_autoset(REQUEST_METHOD);
1220
+ rack_autoset(PATH_INFO);
1221
+ rack_autoset(QUERY_STRING);
1222
+ rack_autoset(SERVER_NAME);
1223
+ rack_autoset(SERVER_PORT);
1224
+ rack_autoset(CONTENT_LENGTH);
1225
+ rack_autoset(CONTENT_TYPE);
1226
+ rack_autoset(SERVER_PROTOCOL);
1227
+ rack_autoset(HTTP_VERSION);
1228
+ rack_autoset(REMOTE_ADDR);
1229
+
1230
+ rack_autoset(HTTP_ACCEPT);
1231
+ rack_autoset(HTTP_USER_AGENT);
1232
+ rack_autoset(HTTP_ACCEPT_ENCODING);
1233
+ rack_autoset(HTTP_ACCEPT_LANGUAGE);
1234
+ rack_autoset(HTTP_CONNECTION);
1235
+ rack_autoset(HTTP_HOST);
1236
+
1237
+ rack_autoset(IODINE_REQUEST_ID);
1238
+
1239
+ rack_set(HTTP_SCHEME, "http");
1240
+ rack_set(HTTPS_SCHEME, "https");
1241
+ rack_set(QUERY_ESTRING, "");
1242
+ rack_set(R_URL_SCHEME, "rack.url_scheme");
1243
+ rack_set(R_INPUT, "rack.input");
1244
+ rack_set(XSENDFILE, "X-Sendfile");
1245
+ rack_set(XSENDFILE_TYPE, "sendfile.type");
1246
+ rack_set(XSENDFILE_TYPE_HEADER, "HTTP_X_SENDFILE_TYPE");
1247
+ rack_set(CONTENT_LENGTH_HEADER, "Content-Length");
1248
+
1249
+ rack_set(IODINE_R_INPUT, "rack.input");
1250
+ rack_set(IODINE_R_HIJACK_IO, "rack.hijack_io");
1251
+ rack_set(IODINE_R_HIJACK, "rack.hijack");
1252
+ rack_set(IODINE_R_HIJACK_CB, "iodine.hijack_cb");
1253
+
1254
+ rack_set(RACK_UPGRADE, "rack.upgrade");
1255
+ rack_set(RACK_UPGRADE_Q, "rack.upgrade?");
1256
+ rack_set_sym(RACK_UPGRADE_SSE, "sse");
1257
+ rack_set_sym(RACK_UPGRADE_WEBSOCKET, "websocket");
1258
+
1259
+ UPGRADE_TCP = IodineStore.add(rb_str_new("upgrade.tcp", 11));
1260
+
1261
+ hijack_func_sym = ID2SYM(rb_intern("_hijack"));
1262
+ close_method_id = rb_intern("close");
1263
+ each_method_id = rb_intern("each");
1264
+ attach_method_id = rb_intern("attach_fd");
1265
+ iodine_call_proc_id = rb_intern("call");
1266
+ fiber_result_var_id = rb_intern("@__result");
1267
+ http_wait_directive = ID2SYM(rb_intern("__http_defer__"));
1268
+
1269
+ IodineUTF8Encoding = rb_enc_find("UTF-8");
1270
+ IodineBinaryEncoding = rb_enc_find("binary");
1271
+
1272
+ {
1273
+ VALUE STRIO_CLASS = rb_const_get(rb_cObject, rb_intern("StringIO"));
1274
+ IODINE_R_INPUT_DEFAULT = rb_str_new_static("", 0);
1275
+ IODINE_R_INPUT_DEFAULT =
1276
+ rb_funcallv(STRIO_CLASS, rb_intern("new"), 1, &IODINE_R_INPUT_DEFAULT);
1277
+ rb_global_variable(&IODINE_R_INPUT_DEFAULT);
1278
+ }
1279
+ initialize_env_template();
1280
+ }