iodine 0.1.21 → 0.2.0

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

Potentially problematic release.


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

Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. data/lib/iodine/timers.rb +0 -95
@@ -0,0 +1,70 @@
1
+ /*
2
+ copyright: Boaz segev, 2016
3
+ license: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #ifndef IODINE_CORE_H
8
+ #define IODINE_CORE_H
9
+ #include "rb-registry.h"
10
+ #include "rb-call.h"
11
+ #include "libserver.h"
12
+ #include <ruby.h>
13
+ #include <ruby/thread.h>
14
+ #include <ruby/encoding.h>
15
+ #include <ruby/version.h>
16
+ #include <stdio.h>
17
+ #include <stdlib.h>
18
+ #include <string.h>
19
+
20
+ #ifndef __unused
21
+ #define __unused __attribute__((unused))
22
+ #endif
23
+
24
+ extern rb_encoding *BinaryEncoding;
25
+ extern rb_encoding *UTF8Encoding;
26
+ extern int BinaryEncodingIndex;
27
+ extern int UTF8EncodingIndex;
28
+
29
+ extern VALUE Iodine;
30
+ extern VALUE IodineBase;
31
+ extern VALUE Iodine_Version;
32
+ extern ID call_proc_id;
33
+ extern ID on_start_func_id;
34
+ extern ID on_finish_func_id;
35
+ extern ID new_func_id;
36
+ extern ID on_open_func_id;
37
+ extern ID on_message_func_id;
38
+ extern ID on_data_func_id;
39
+ extern ID on_ready_func_id;
40
+ extern ID on_shutdown_func_id;
41
+ extern ID on_close_func_id;
42
+ extern ID ping_func_id;
43
+ extern ID buff_var_id;
44
+ extern ID fd_var_id;
45
+ extern ID timeout_var_id;
46
+ extern ID to_s_method_id;
47
+
48
+ __unused static inline void iodine_set_fd(VALUE handler, intptr_t fd) {
49
+ rb_ivar_set(handler, fd_var_id, LONG2NUM((long)fd));
50
+ }
51
+ __unused static inline intptr_t iodine_get_fd(VALUE handler) {
52
+ return ((intptr_t)NUM2LONG(rb_ivar_get(handler, fd_var_id)));
53
+ }
54
+ /* *****************************************************************************
55
+ The Core dynamic Iodine protocol - all protocols using `each` should follow
56
+ this design.
57
+ */
58
+ typedef struct {
59
+ protocol_s protocol;
60
+ VALUE handler;
61
+ } dyn_protocol_s;
62
+
63
+ #define dyn_prot(protocol) ((dyn_protocol_s *)(protocol))
64
+
65
+ /* "upgrades" a connection to a dynamic generic protocol */
66
+ VALUE iodine_upgrade2basic(intptr_t fduuid, VALUE handler);
67
+ /* runs a task for each connection in the service. */
68
+ void iodine_run_each(intptr_t origin, const char *service, VALUE block);
69
+
70
+ #endif
@@ -0,0 +1,615 @@
1
+ #include "iodine_http.h"
2
+ #include "iodine_websocket.h"
3
+ #include "websockets.h"
4
+
5
+ /* the Iodine::Rack HTTP server class*/
6
+ VALUE IodineHttp;
7
+ /* these three are used also by rb-rack-io.c */
8
+ VALUE R_HIJACK;
9
+ VALUE R_HIJACK_IO;
10
+ VALUE R_HIJACK_CB;
11
+
12
+ VALUE UPGRADE_TCP;
13
+ VALUE UPGRADE_TCP_Q;
14
+ VALUE UPGRADE_WEBSOCKET;
15
+ VALUE UPGRADE_WEBSOCKET_Q;
16
+ /* backwards compatibility, temp */
17
+ VALUE IODINE_UPGRADE;
18
+ VALUE IODINE_WEBSOCKET;
19
+
20
+ static VALUE hijack_func_sym;
21
+ static ID to_fixnum_func_id;
22
+ static ID close_method_id;
23
+ static ID each_method_id;
24
+ static _Bool iodine_http_request_logging = 0;
25
+ static _Bool iodine_http_static_file_server = 0;
26
+ #define rack_declare(rack_name) static VALUE rack_name
27
+
28
+ #define rack_set(rack_name, str) \
29
+ (rack_name) = rb_enc_str_new((str), strlen((str)), BinaryEncoding); \
30
+ rb_global_variable(&(rack_name)); \
31
+ rb_obj_freeze(rack_name);
32
+
33
+ #define rack_autoset(rack_name) rack_set((rack_name), #rack_name)
34
+
35
+ static VALUE ENV_TEMPLATE;
36
+
37
+ rack_declare(HTTP_SCHEME);
38
+ rack_declare(HTTPS_SCHEME);
39
+ rack_declare(QUERY_ESTRING);
40
+ rack_declare(REQUEST_METHOD);
41
+ rack_declare(PATH_INFO);
42
+ rack_declare(QUERY_STRING);
43
+ rack_declare(QUERY_ESTRING);
44
+ rack_declare(SERVER_NAME);
45
+ rack_declare(SERVER_PORT);
46
+ rack_declare(CONTENT_LENGTH);
47
+ rack_declare(CONTENT_TYPE);
48
+ rack_declare(R_URL_SCHEME); // rack.url_scheme
49
+ rack_declare(R_INPUT); // rack.input
50
+ rack_declare(XSENDFILE); // for X-Sendfile support
51
+ rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support
52
+ // rack_declare(R_HIJACK); // rack.hijack
53
+ // rack_declare(R_HIJACK_CB);// rack.hijack_io
54
+
55
+ /* *****************************************************************************
56
+ HTTP Protocol initialization
57
+ */
58
+ /* allow quick access to the Rack app */
59
+ static VALUE rack_app_handler = 0;
60
+
61
+ #define to_upper(c) (((c) >= 'a' && (c) <= 'z') ? ((c) & ~32) : (c))
62
+
63
+ static inline VALUE copy2env(http_request_s *request) {
64
+ VALUE env = rb_hash_dup(ENV_TEMPLATE);
65
+ VALUE hname; /* will be used later, both as tmp and to iterate header names */
66
+ char *pos = NULL;
67
+ const char *reader = NULL;
68
+ Registry.add(env);
69
+ /* Copy basic data */
70
+ rb_hash_aset(
71
+ env, REQUEST_METHOD,
72
+ rb_enc_str_new(request->method, request->method_len, BinaryEncoding));
73
+
74
+ rb_hash_aset(env, PATH_INFO, rb_enc_str_new(request->path, request->path_len,
75
+ BinaryEncoding));
76
+ rb_hash_aset(
77
+ env, QUERY_STRING,
78
+ (request->query
79
+ ? rb_enc_str_new(request->query, request->query_len, BinaryEncoding)
80
+ : QUERY_ESTRING));
81
+
82
+ /* setup input IO + hijack support */
83
+ rb_hash_aset(env, R_INPUT, (hname = RackIO.new(request, env)));
84
+
85
+ /* publish upgrade support */
86
+ if (request->upgrade) {
87
+ rb_hash_aset(env, UPGRADE_WEBSOCKET_Q, Qtrue);
88
+ rb_hash_aset(env, UPGRADE_TCP_Q, Qtrue);
89
+ }
90
+
91
+ hname = rb_obj_method(hname, hijack_func_sym);
92
+ rb_hash_aset(env, R_HIJACK, hname);
93
+
94
+ /* handle the HOST header, including the possible host:#### format*/
95
+ pos = (char *)request->host;
96
+ while (*pos && *pos != ':')
97
+ pos++;
98
+ if (*pos == 0) {
99
+ rb_hash_aset(
100
+ env, SERVER_NAME,
101
+ rb_enc_str_new(request->host, request->host_len, BinaryEncoding));
102
+ rb_hash_aset(env, SERVER_PORT, QUERY_ESTRING);
103
+ } else {
104
+ rb_hash_aset(
105
+ env, SERVER_NAME,
106
+ rb_enc_str_new(request->host, pos - request->host, BinaryEncoding));
107
+ rb_hash_aset(env, SERVER_PORT,
108
+ rb_enc_str_new(pos + 1,
109
+ request->host_len - ((pos + 1) - request->host),
110
+ BinaryEncoding));
111
+ }
112
+
113
+ /* default schema to http, it might be updated later */
114
+ rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
115
+
116
+ /* add all headers, exclude special cases */
117
+ http_headers_s *header = request->headers;
118
+ for (size_t i = 0; i < request->headers_count; i++, header++) {
119
+ if (header->name_length == 14 &&
120
+ strncasecmp("content-length", header->name, 14) == 0) {
121
+ rb_hash_aset(
122
+ env, CONTENT_LENGTH,
123
+ rb_enc_str_new(header->value, header->value_length, BinaryEncoding));
124
+ continue;
125
+ } else if (header->name_length == 12 &&
126
+ strncasecmp("content-type", header->name, 12) == 0) {
127
+ rb_hash_aset(
128
+ env, CONTENT_TYPE,
129
+ rb_enc_str_new(header->value, header->value_length, BinaryEncoding));
130
+ continue;
131
+ } else if (header->name_length == 27 &&
132
+ strncasecmp("x-forwarded-proto", header->name, 27) == 0) {
133
+ if (header->value_length >= 5 &&
134
+ !strncasecmp(header->value, "https", 5)) {
135
+ rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
136
+ } else if (header->value_length == 4 &&
137
+ *((uint32_t *)header->value) == *((uint32_t *)"http")) {
138
+ rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
139
+ } else {
140
+ rb_hash_aset(env, R_URL_SCHEME,
141
+ rb_enc_str_new(header->value, header->value_length,
142
+ BinaryEncoding));
143
+ }
144
+ } else if (header->name_length == 9 &&
145
+ strncasecmp("forwarded", header->name, 9) == 0) {
146
+ pos = (char *)header->value;
147
+ if (pos) {
148
+ while (*pos) {
149
+ if (((*(pos++) | 32) == 'p') && ((*(pos++) | 32) == 'r') &&
150
+ ((*(pos++) | 32) == 'o') && ((*(pos++) | 32) == 't') &&
151
+ ((*(pos++) | 32) == 'o') && ((*(pos++) | 32) == '=')) {
152
+ if ((pos[0] | 32) == 'h' && (pos[1] | 32) == 't' &&
153
+ (pos[2] | 32) == 't' && (pos[3] | 32) == 'p') {
154
+ if ((pos[4] | 32) == 's') {
155
+ rb_hash_aset(env, R_URL_SCHEME, HTTPS_SCHEME);
156
+ } else {
157
+ rb_hash_aset(env, R_URL_SCHEME, HTTP_SCHEME);
158
+ }
159
+ } else {
160
+ char *tmp = pos;
161
+ while (*tmp && *tmp != ';')
162
+ tmp++;
163
+ rb_hash_aset(env, R_URL_SCHEME, rb_str_new(pos, tmp - pos));
164
+ }
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ hname = rb_str_buf_new(6 + header->name_length);
172
+ memcpy(RSTRING_PTR(hname), "HTTP_", 5);
173
+ pos = RSTRING_PTR(hname) + 5;
174
+ reader = header->name;
175
+ while (*reader) {
176
+ *(pos++) = *reader == '-' ? '_' : to_upper(*reader);
177
+ ++reader;
178
+ }
179
+ *pos = 0;
180
+ rb_str_set_len(hname, 5 + header->name_length);
181
+ rb_hash_aset(env, hname, rb_enc_str_new(header->value, header->value_length,
182
+ BinaryEncoding));
183
+ }
184
+ return env;
185
+ }
186
+
187
+ // itterate through the headers and add them to the response buffer
188
+ // (we are recycling the request's buffer)
189
+ static int for_each_header_data(VALUE key, VALUE val, VALUE _res) {
190
+ // fprintf(stderr, "For_each - headers\n");
191
+ if (TYPE(key) != T_STRING)
192
+ key = RubyCaller.call(key, to_s_method_id);
193
+ if (TYPE(key) != T_STRING)
194
+ return ST_CONTINUE;
195
+ if (TYPE(val) != T_STRING) {
196
+ val = RubyCaller.call(val, to_s_method_id);
197
+ if (TYPE(val) != T_STRING)
198
+ return ST_STOP;
199
+ }
200
+ char *val_s = RSTRING_PTR(val);
201
+ int val_len = RSTRING_LEN(val);
202
+ // scan the value for newline (\n) delimiters
203
+ int pos_s = 0, pos_e = 0;
204
+ while (pos_e < val_len) {
205
+ // scanning for newline (\n) delimiters
206
+ while (pos_e < val_len && val_s[pos_e] != '\n')
207
+ pos_e++;
208
+ http_response_write_header(
209
+ (void *)_res, .name = RSTRING_PTR(key), .name_length = RSTRING_LEN(key),
210
+ .value = val_s + pos_s, .value_length = pos_e - pos_s);
211
+ // fprintf(stderr, "For_each - headers: wrote header\n");
212
+ // move forward (skip the '\n' if exists)
213
+ pos_s = pos_e + 1;
214
+ pos_e++;
215
+ }
216
+ // no errors, return 0
217
+ return ST_CONTINUE;
218
+ }
219
+
220
+ // writes the body to the response object
221
+ static VALUE for_each_body_string(VALUE str, VALUE _res, int argc, VALUE argv) {
222
+ // fprintf(stderr, "For_each - body\n");
223
+ // write body
224
+ if (TYPE(str) != T_STRING) {
225
+ fprintf(stderr, "Iodine Server Error:"
226
+ "response body was not a String\n");
227
+ return Qfalse;
228
+ }
229
+ if (RSTRING_LEN(str)) {
230
+ if (http_response_write_body((void *)_res, RSTRING_PTR(str),
231
+ RSTRING_LEN(str))) {
232
+ // fprintf(stderr,
233
+ // "Iodine Server Error:"
234
+ // "couldn't write response to connection\n");
235
+ return Qfalse;
236
+ }
237
+ } else {
238
+ http_response_finish((void *)_res);
239
+ return Qfalse;
240
+ }
241
+ return Qtrue;
242
+ }
243
+
244
+ static inline int ruby2c_response_send(http_response_s *response,
245
+ VALUE rbresponse, VALUE env) {
246
+ VALUE body = rb_ary_entry(rbresponse, 2);
247
+ if (response->status < 200 || response->status == 204 ||
248
+ response->status == 304) {
249
+ if (rb_respond_to(body, close_method_id))
250
+ RubyCaller.call(body, close_method_id);
251
+ body = Qnil;
252
+ response->content_length = -1;
253
+ }
254
+
255
+ if (TYPE(body) == T_ARRAY && RARRAY_LEN(body) == 1) { // [String] is likely
256
+ body = rb_ary_entry(body, 0);
257
+ // fprintf(stderr, "Body was a single item array, unpacket to string\n");
258
+ }
259
+
260
+ if (TYPE(body) == T_STRING) {
261
+ // fprintf(stderr, "Review body as String\n");
262
+ if (RSTRING_LEN(body))
263
+ http_response_write_body(response, RSTRING_PTR(body), RSTRING_LEN(body));
264
+ http_response_finish(response);
265
+ return 0;
266
+ } else if (body == Qnil) {
267
+ http_response_finish(response);
268
+ return 0;
269
+ } else if (rb_respond_to(body, each_method_id)) {
270
+ // fprintf(stderr, "Review body as for-each ...\n");
271
+ if (!response->metadata.connection_written &&
272
+ !response->metadata.content_length_written) {
273
+ // close the connection to indicate message length...
274
+ // protection from bad code
275
+ response->metadata.should_close = 1;
276
+ response->content_length = -1;
277
+ }
278
+ rb_block_call(body, each_method_id, 0, NULL, for_each_body_string,
279
+ (VALUE)response);
280
+ // make sure the response is sent even if it was an empty collection
281
+ http_response_finish(response);
282
+ // we need to call `close` in case the object is an IO / BodyProxy
283
+ if (rb_respond_to(body, close_method_id))
284
+ RubyCaller.call(body, close_method_id);
285
+ return 0;
286
+ }
287
+ return -1;
288
+ }
289
+
290
+ static inline int ruby2c_review_upgrade(http_response_s *response,
291
+ VALUE rbresponse, VALUE env) {
292
+ VALUE handler;
293
+ if ((handler = rb_hash_aref(env, R_HIJACK_CB)) != Qnil) {
294
+ // send headers
295
+ http_response_finish(response);
296
+ // remove socket from libsock and libserver
297
+ server_hijack(response->metadata.request->metadata.fd);
298
+ // call the callback
299
+ VALUE io_ruby = RubyCaller.call(rb_hash_aref(env, R_HIJACK), call_proc_id);
300
+ RubyCaller.call2(handler, call_proc_id, 1, &io_ruby);
301
+ } else if ((handler = rb_hash_aref(env, R_HIJACK_IO)) != Qnil) {
302
+ // send nothing.
303
+ if (iodine_http_request_logging)
304
+ http_response_log_finish(response);
305
+ http_response_destroy(response);
306
+ // remove socket from libsock and libserver
307
+ server_hijack(response->metadata.request->metadata.fd);
308
+ } else if ((handler = rb_hash_aref(env, UPGRADE_WEBSOCKET)) != Qnil ||
309
+ (handler = rb_hash_aref(env, IODINE_WEBSOCKET)) != Qnil) {
310
+ // use response as existing base for native websocket upgrade
311
+ iodine_websocket_upgrade(response->metadata.request, response, handler);
312
+ } else if ((handler = rb_hash_aref(env, UPGRADE_TCP)) != Qnil ||
313
+ (handler = rb_hash_aref(env, IODINE_UPGRADE)) != Qnil) {
314
+ intptr_t fduuid = response->metadata.request->metadata.fd;
315
+ // send headers
316
+ http_response_finish(response);
317
+ // upgrade protocol
318
+ iodine_upgrade2basic(fduuid, handler);
319
+ // prevent response processing.
320
+ } else {
321
+ return 0;
322
+ }
323
+ // get body object to close it (if needed)
324
+ handler = rb_ary_entry(rbresponse, 2);
325
+ // we need to call `close` in case the object is an IO / BodyProxy
326
+ if (handler != Qnil && rb_respond_to(handler, close_method_id))
327
+ RubyCaller.call(handler, close_method_id);
328
+ return 1;
329
+ }
330
+
331
+ static void *on_rack_request_in_GVL(http_request_s *request) {
332
+ http_response_s response = http_response_init(request);
333
+ if (iodine_http_request_logging)
334
+ http_response_log_start(&response);
335
+ // create /register env variable
336
+ VALUE env = copy2env(request);
337
+ // will be used later
338
+ VALUE tmp;
339
+ // pass env variable to handler
340
+ VALUE rbresponse = RubyCaller.call2(rack_app_handler, call_proc_id, 1, &env);
341
+ if (rbresponse == 0 || rbresponse == Qnil)
342
+ goto internal_error;
343
+ Registry.add(rbresponse);
344
+ // set response status
345
+ tmp = rb_ary_entry(rbresponse, 0);
346
+ if (TYPE(tmp) == T_STRING)
347
+ tmp = rb_funcall2(tmp, to_fixnum_func_id, 0, NULL);
348
+ if (TYPE(tmp) != T_FIXNUM)
349
+ goto internal_error;
350
+ response.status = FIX2ULONG(tmp);
351
+ // handle header copy from ruby land to C land.
352
+ VALUE response_headers = rb_ary_entry(rbresponse, 1);
353
+ if (TYPE(response_headers) != T_HASH)
354
+ goto internal_error;
355
+ // extract the X-Sendfile header (never show original path)
356
+ // X-Sendfile support only present when iodine sercers static files.
357
+ VALUE xfiles = iodine_http_static_file_server
358
+ ? rb_hash_delete(response_headers, XSENDFILE)
359
+ : Qnil;
360
+ // remove XFile's content length headers, as this will be controled by Iodine
361
+ if (xfiles != Qnil) {
362
+ rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER);
363
+ }
364
+ // review each header and write it to the response.
365
+ rb_hash_foreach(response_headers, for_each_header_data, (VALUE)(&response));
366
+ // If the X-Sendfile header was provided, send the file directly and finish
367
+ if (xfiles != Qnil &&
368
+ http_response_sendfile2(&response, request, RSTRING_PTR(xfiles),
369
+ RSTRING_LEN(xfiles), NULL, 0, 1) == 0) {
370
+ goto finish;
371
+ }
372
+ // review for belated (post response headers) upgrade.
373
+ if (ruby2c_review_upgrade(&response, rbresponse, env) == 0) {
374
+ // send the request body.
375
+ if (ruby2c_response_send(&response, rbresponse, env))
376
+ goto internal_error;
377
+ // http_response_finish(&response);
378
+ }
379
+ finish:
380
+ Registry.remove(rbresponse);
381
+ Registry.remove(env);
382
+ http_response_destroy(&response);
383
+ return NULL;
384
+ internal_error:
385
+ Registry.remove(rbresponse);
386
+ Registry.remove(env);
387
+ http_response_destroy(&response);
388
+ response = http_response_init(request);
389
+ if (iodine_http_request_logging)
390
+ http_response_log_start(&response);
391
+ response.status = 500;
392
+ http_response_write_body(&response, "Error 500, Internal error.", 26);
393
+ http_response_finish(&response);
394
+ return NULL;
395
+ }
396
+
397
+ static void on_rack_request(http_request_s *request) {
398
+ // if (request->body_file)
399
+ // fprintf(stderr, "Request data is stored in a temporary file\n");
400
+ RubyCaller.call_c((void *(*)(void *))on_rack_request_in_GVL, request);
401
+ }
402
+
403
+ /* *****************************************************************************
404
+ Initializing basic ENV template
405
+ */
406
+
407
+ #define add_str_to_env(env, key, value) \
408
+ { \
409
+ VALUE k = rb_enc_str_new((key), strlen((key)), BinaryEncoding); \
410
+ rb_obj_freeze(k); \
411
+ VALUE v = rb_enc_str_new((value), strlen((value)), BinaryEncoding); \
412
+ rb_obj_freeze(v); \
413
+ rb_hash_aset(env, k, v); \
414
+ }
415
+ #define add_value_to_env(env, key, value) \
416
+ { \
417
+ VALUE k = rb_enc_str_new((key), strlen((key)), BinaryEncoding); \
418
+ rb_obj_freeze(k); \
419
+ rb_hash_aset(env, k, value); \
420
+ }
421
+
422
+ static void init_env_template(void) {
423
+ VALUE tmp;
424
+ ENV_TEMPLATE = rb_hash_new();
425
+ rb_global_variable(&ENV_TEMPLATE);
426
+
427
+ // Start with the stuff Iodine will review.
428
+ rb_hash_aset(ENV_TEMPLATE, UPGRADE_WEBSOCKET, Qnil);
429
+ rb_hash_aset(ENV_TEMPLATE, UPGRADE_TCP, Qnil);
430
+ if (iodine_http_static_file_server) {
431
+ add_value_to_env(ENV_TEMPLATE, "sendfile.type", XSENDFILE);
432
+ add_value_to_env(ENV_TEMPLATE, "HTTP_X_SENDFILE_TYPE", XSENDFILE);
433
+ }
434
+ rb_hash_aset(ENV_TEMPLATE, UPGRADE_WEBSOCKET_Q, Qnil);
435
+ rb_hash_aset(ENV_TEMPLATE, UPGRADE_TCP_Q, Qnil);
436
+
437
+ /* backwards compatibility, temp */
438
+ rb_hash_aset(ENV_TEMPLATE, IODINE_WEBSOCKET, Qnil);
439
+ rb_hash_aset(ENV_TEMPLATE, IODINE_UPGRADE, Qnil);
440
+
441
+ // add the rack.version
442
+ tmp = rb_ary_new(); // rb_ary_new is Ruby 2.0 compatible
443
+ rb_ary_push(tmp, INT2FIX(1));
444
+ rb_ary_push(tmp, INT2FIX(3));
445
+ // rb_ary_push(tmp, rb_enc_str_new("1", 1, BinaryEncoding));
446
+ // rb_ary_push(tmp, rb_enc_str_new("3", 1, BinaryEncoding));
447
+ add_value_to_env(ENV_TEMPLATE, "rack.version", tmp);
448
+ add_value_to_env(ENV_TEMPLATE, "rack.errors", rb_stderr);
449
+ add_value_to_env(ENV_TEMPLATE, "rack.multithread", Qtrue);
450
+ add_value_to_env(ENV_TEMPLATE, "rack.multiprocess", Qtrue);
451
+ add_value_to_env(ENV_TEMPLATE, "rack.run_once", Qfalse);
452
+ add_value_to_env(ENV_TEMPLATE, "rack.hijack?", Qtrue);
453
+ add_str_to_env(ENV_TEMPLATE, "SCRIPT_NAME", "");
454
+ }
455
+ #undef add_str_to_env
456
+ #undef add_value_to_env
457
+
458
+ /* *****************************************************************************
459
+ Rack object API
460
+ */
461
+
462
+ int iodine_http_review(void) {
463
+ rack_app_handler = rb_iv_get(IodineHttp, "@app");
464
+ if (rack_app_handler != Qnil &&
465
+ rb_respond_to(rack_app_handler, call_proc_id)) {
466
+ VALUE rbport = rb_iv_get(IodineHttp, "@port");
467
+ VALUE rbaddress = rb_iv_get(IodineHttp, "@address");
468
+ VALUE rbmaxbody = rb_iv_get(IodineHttp, "@max_body_size");
469
+ VALUE rbmaxmsg = rb_iv_get(IodineHttp, "@max_msg_size");
470
+ VALUE rbwww = rb_iv_get(IodineHttp, "@public");
471
+ VALUE rblog = rb_iv_get(IodineHttp, "@log");
472
+ VALUE rbtout = rb_iv_get(IodineHttp, "@timeout");
473
+ VALUE rbwstout = rb_iv_get(IodineHttp, "@ws_timeout");
474
+ const char *port = "3000";
475
+ const char *address = NULL;
476
+ const char *public_folder = NULL;
477
+ size_t max_body_size;
478
+ // review port
479
+ if (TYPE(rbport) != T_FIXNUM && TYPE(rbport) != T_STRING &&
480
+ TYPE(rbport) != Qnil)
481
+ rb_raise(rb_eTypeError,
482
+ "The port variable must be either a Fixnum or a String.");
483
+ if (TYPE(rbport) == T_FIXNUM)
484
+ rbport = rb_funcall2(rbport, rb_intern("to_s"), 0, NULL);
485
+ if (TYPE(rbport) == T_STRING)
486
+ port = StringValueCStr(rbport);
487
+ // review address
488
+ if (TYPE(rbaddress) != T_STRING && rbaddress != Qnil)
489
+ rb_raise(rb_eTypeError,
490
+ "The address variable must be either a String or `nil`.");
491
+ if (TYPE(address) == T_STRING)
492
+ address = StringValueCStr(rbaddress);
493
+ // review public folder
494
+ if (TYPE(rbwww) != T_STRING && rbwww != Qnil)
495
+ rb_raise(rb_eTypeError,
496
+ "The public folder variable `public` must be either a String or "
497
+ "`nil`.");
498
+ if (TYPE(rbwww) == T_STRING) {
499
+ public_folder = StringValueCStr(rbwww);
500
+ iodine_http_static_file_server = 1;
501
+ }
502
+ // review timeout
503
+ uint8_t timeout = (TYPE(rbtout) == T_FIXNUM) ? FIX2ULONG(rbtout) : 0;
504
+ if (FIX2ULONG(rbtout) > 255) {
505
+ fprintf(stderr,
506
+ "Iodine Warning: Iodine::Rack timeout value is over 255 and is "
507
+ "silently ignored.\n");
508
+ timeout = 0;
509
+ }
510
+ // review websocket timeout
511
+ iodine_websocket_timeout =
512
+ (TYPE(rbwstout) == T_FIXNUM) ? FIX2ULONG(rbwstout) : 0;
513
+ if (FIX2ULONG(rbwstout) > 255) {
514
+ fprintf(stderr, "Iodine Warning: Iodine::Rack Websocket timeout value "
515
+ "is over 255 and is silently ignored.\n");
516
+ iodine_websocket_timeout = 0;
517
+ }
518
+ // review max body size
519
+ max_body_size = (TYPE(rbmaxbody) == T_FIXNUM) ? FIX2ULONG(rbmaxbody) : 0;
520
+ // review max websocket message size
521
+ iodine_websocket_max_msg_size =
522
+ (TYPE(rbmaxmsg) == T_FIXNUM) ? FIX2ULONG(rbmaxmsg) : 0;
523
+ // review logging
524
+ iodine_http_request_logging = (rblog != Qnil && rblog != Qfalse);
525
+
526
+ // initialize the Rack env template
527
+ init_env_template();
528
+
529
+ // get Iodine concurrency info
530
+ VALUE rb_threads = rb_ivar_get(Iodine, rb_intern("@threads"));
531
+ int threads = rb_threads == Qnil ? 1 : (FIX2INT(rb_threads));
532
+ VALUE rb_processes = rb_ivar_get(Iodine, rb_intern("@processes"));
533
+ int processes = rb_processes == Qnil ? 1 : (FIX2INT(rb_processes));
534
+
535
+ // Write message
536
+ VALUE iodine_version = rb_const_get(Iodine, rb_intern("VERSION"));
537
+ VALUE ruby_version = rb_const_get(Iodine, rb_intern("RUBY_VERSION"));
538
+ if (public_folder)
539
+ fprintf(stderr, "Starting up Iodine Http Server:\n"
540
+ " * Ruby v.%s\n * Iodine v.%s \n"
541
+ " * %d processes X %d thread%s\n"
542
+ " * Serving static files from:\n"
543
+ " %s\n\n",
544
+ StringValueCStr(ruby_version), StringValueCStr(iodine_version),
545
+ processes, threads, (threads > 1 ? "s" : ""), public_folder);
546
+ else
547
+ fprintf(stderr, "Starting up Iodine Http Server:\n"
548
+ " * Ruby v.%s\n * Iodine v.%s \n"
549
+ " * %d processes X %d thread%s\n\n",
550
+ StringValueCStr(ruby_version), StringValueCStr(iodine_version),
551
+ processes, threads, (threads > 1 ? "s" : ""));
552
+
553
+ // listen
554
+ return http1_listen(port, address, .on_request = on_rack_request,
555
+ .log_static = iodine_http_request_logging,
556
+ .max_body_size = max_body_size,
557
+ .public_folder = public_folder, .timeout = timeout);
558
+ }
559
+ return 0;
560
+ }
561
+
562
+ /* *****************************************************************************
563
+ Initializing the library
564
+ */
565
+
566
+ void Init_iodine_http(void) {
567
+ rack_autoset(REQUEST_METHOD);
568
+ rack_autoset(PATH_INFO);
569
+ rack_autoset(QUERY_STRING);
570
+ rack_autoset(SERVER_NAME);
571
+ rack_autoset(SERVER_PORT);
572
+ rack_autoset(CONTENT_LENGTH);
573
+ rack_autoset(CONTENT_TYPE);
574
+ rack_set(HTTP_SCHEME, "http");
575
+ rack_set(HTTPS_SCHEME, "https");
576
+ rack_set(QUERY_ESTRING, "");
577
+ rack_set(R_URL_SCHEME, "rack.url_scheme");
578
+ rack_set(R_INPUT, "rack.input");
579
+ rack_set(XSENDFILE, "X-Sendfile");
580
+ rack_set(CONTENT_LENGTH_HEADER, "Content-Length");
581
+
582
+ rack_set(R_HIJACK_IO, "rack.hijack_io");
583
+ rack_set(R_HIJACK, "rack.hijack");
584
+ rack_set(R_HIJACK_CB, "iodine.hijack_cb");
585
+
586
+ rack_set(UPGRADE_TCP, "upgrade.tcp");
587
+ rack_set(UPGRADE_WEBSOCKET, "upgrade.websocket");
588
+
589
+ rack_set(UPGRADE_TCP_Q, "upgrade.tcp?");
590
+ rack_set(UPGRADE_WEBSOCKET_Q, "upgrade.websocket?");
591
+
592
+ /* backwards compatability, temp */
593
+ rack_set(IODINE_UPGRADE, "iodine.upgrade");
594
+ rack_set(IODINE_WEBSOCKET, "iodine.websocket");
595
+
596
+ rack_set(QUERY_ESTRING, "");
597
+ rack_set(QUERY_ESTRING, "");
598
+
599
+ hijack_func_sym = ID2SYM(rb_intern("_hijack"));
600
+ to_fixnum_func_id = rb_intern("to_i");
601
+ close_method_id = rb_intern("close");
602
+ each_method_id = rb_intern("each");
603
+
604
+ IodineHttp = rb_define_module_under(Iodine, "Rack");
605
+ RackIO.init();
606
+ Init_iodine_websocket();
607
+ }
608
+
609
+ // REQUEST_METHOD
610
+ // PATH_INFO
611
+ // QUERY_STRING
612
+ // SERVER_NAME
613
+ // SERVER_PORT
614
+ // CONTENT_LENGTH
615
+ // rack.url_scheme rack.input rack.hijack rack.hijack_io HTTP_ Variables