iodine 0.7.59 → 0.8.0

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