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.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- 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
|