isomorfeus-iodine 0.7.44

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