nyara 0.0.1.pre.2 → 0.0.1.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/accept.c +29 -33
- data/ext/event.c +218 -22
- data/ext/extconf.rb +3 -3
- data/ext/hashes.c +2 -1
- data/ext/http_parser.c +6 -0
- data/ext/inc/epoll.h +11 -9
- data/ext/inc/kqueue.h +14 -17
- data/ext/inc/str_intern.h +2 -2
- data/ext/mime.c +2 -0
- data/ext/multipart_parser.c +2 -0
- data/ext/nyara.c +2 -0
- data/ext/nyara.h +15 -9
- data/ext/request.c +88 -266
- data/ext/request.h +43 -0
- data/ext/request_parse.c +123 -0
- data/ext/route.cc +2 -0
- data/ext/url_encoded.c +66 -6
- data/hello.rb +8 -2
- data/lib/nyara/controller.rb +29 -10
- data/lib/nyara/cookie.rb +3 -2
- data/lib/nyara/nyara.rb +41 -27
- data/lib/nyara/patch_tcp_socket.rb +22 -0
- data/lib/nyara/request.rb +1 -3
- data/lib/nyara/view.rb +1 -0
- data/nyara.gemspec +1 -1
- data/rakefile +63 -42
- data/readme.md +34 -5
- data/spec/apps/connect.rb +14 -0
- data/spec/evented_io_spec.rb +23 -0
- data/spec/ext_parse_spec.rb +26 -7
- data/spec/performance/layout_render.rb +52 -0
- data/spec/performance/parse_accept_value.rb +13 -0
- data/spec/performance/parse_param.rb +18 -0
- data/spec/performance/performance_helper.rb +25 -0
- data/spec/performance_spec.rb +33 -0
- data/spec/request_delegate_spec.rb +41 -16
- data/spec/request_spec.rb +4 -7
- data/spec/spec_helper.rb +20 -10
- metadata +15 -6
- /data/lib/nyara/{config_hash.rb → hashes/config_hash.rb} +0 -0
- /data/lib/nyara/{header_hash.rb → hashes/header_hash.rb} +0 -0
- /data/lib/nyara/{param_hash.rb → hashes/param_hash.rb} +0 -0
data/ext/request.c
CHANGED
@@ -1,187 +1,17 @@
|
|
1
|
+
/* request parsing and request object */
|
2
|
+
|
1
3
|
#include "nyara.h"
|
2
4
|
#include <ruby/encoding.h>
|
3
|
-
#include
|
4
|
-
|
5
|
-
#ifndef write
|
6
|
-
#include <unistd.h>
|
7
|
-
#endif
|
8
|
-
|
9
|
-
typedef struct {
|
10
|
-
http_parser hparser;
|
11
|
-
multipart_parser* mparser;
|
12
|
-
enum http_method method;
|
13
|
-
VALUE header;
|
14
|
-
VALUE accept; // mime array sorted with q
|
15
|
-
VALUE format; // string ext without dot
|
16
|
-
VALUE fiber;
|
17
|
-
VALUE scope; // mapped prefix
|
18
|
-
VALUE path_with_query;
|
19
|
-
VALUE path;
|
20
|
-
VALUE query;
|
21
|
-
VALUE last_field;
|
22
|
-
VALUE last_value;
|
23
|
-
VALUE self;
|
24
|
-
int fd;
|
25
|
-
|
26
|
-
// response
|
27
|
-
int status;
|
28
|
-
VALUE response_content_type;
|
29
|
-
VALUE response_header;
|
30
|
-
VALUE response_header_extra_lines;
|
31
|
-
} Request;
|
32
|
-
|
33
|
-
// typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
34
|
-
// typedef int (*http_cb) (http_parser*);
|
35
|
-
|
36
|
-
static ID id_not_found;
|
5
|
+
#include "request.h"
|
6
|
+
|
37
7
|
static VALUE str_html;
|
38
8
|
static rb_encoding* u8_encoding;
|
39
9
|
static VALUE request_class;
|
40
|
-
static VALUE
|
41
|
-
static VALUE str_accept;
|
42
|
-
static VALUE nyara_http_methods;
|
43
|
-
|
44
|
-
static VALUE fd_request_map;
|
45
|
-
#define MAX_RECEIVE_DATA 65536
|
46
|
-
static char received_data[MAX_RECEIVE_DATA];
|
47
|
-
|
48
|
-
static VALUE _fiber_func(VALUE _, VALUE args) {
|
49
|
-
VALUE instance = rb_ary_pop(args);
|
50
|
-
VALUE meth = rb_ary_pop(args);
|
51
|
-
rb_apply(instance, SYM2ID(meth), args);
|
52
|
-
return Qnil;
|
53
|
-
}
|
54
|
-
|
55
|
-
static void _upcase_method(VALUE str) {
|
56
|
-
char* s = RSTRING_PTR(str);
|
57
|
-
long len = RSTRING_LEN(str);
|
58
|
-
for (long i = 0; i < len; i++) {
|
59
|
-
if (s[i] >= 'a' && s[i] <= 'z') {
|
60
|
-
s[i] = 'A' + (s[i] - 'a');
|
61
|
-
}
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
static inline void _close_fd(int fd) {
|
66
|
-
rb_hash_delete(fd_request_map, INT2FIX(fd));
|
67
|
-
close(fd);
|
68
|
-
}
|
69
|
-
|
70
|
-
static int on_url(http_parser* parser, const char* s, size_t len) {
|
71
|
-
Request* p = (Request*)parser;
|
72
|
-
p->method = parser->method;
|
73
|
-
|
74
|
-
if (p->path_with_query == Qnil) {
|
75
|
-
p->path_with_query = rb_str_new(s, len);
|
76
|
-
} else {
|
77
|
-
rb_str_cat(p->path_with_query, s, len);
|
78
|
-
}
|
79
|
-
return 0;
|
80
|
-
}
|
81
|
-
|
82
|
-
static int on_message_complete(http_parser* parser) {
|
83
|
-
Request* p = (Request*)parser;
|
84
|
-
if (p->fiber == Qnil) {
|
85
|
-
return 1;
|
86
|
-
} else {
|
87
|
-
VALUE state = rb_fiber_resume(p->fiber, 0, NULL);
|
88
|
-
if (state == Qnil) { // terminated (todo check raise error)
|
89
|
-
if (p->status == 200) {
|
90
|
-
write(p->fd, "0\r\n\r\n", 5);
|
91
|
-
}
|
92
|
-
_close_fd(p->fd);
|
93
|
-
p->fd = 0;
|
94
|
-
} else if (SYM2ID(state) == rb_intern("term_close")) {
|
95
|
-
write(p->fd, "0\r\n\r\n", 5);
|
96
|
-
_close_fd(p->fd);
|
97
|
-
p->fd = 0;
|
98
|
-
}
|
99
|
-
return 0;
|
100
|
-
}
|
101
|
-
}
|
102
|
-
|
103
|
-
static int on_header_field(http_parser* parser, const char* s, size_t len) {
|
104
|
-
Request* p = (Request*)parser;
|
105
|
-
if (p->last_field == Qnil) {
|
106
|
-
p->last_field = rb_str_new(s, len);
|
107
|
-
p->last_value = Qnil;
|
108
|
-
} else {
|
109
|
-
rb_str_cat(p->last_field, s, len);
|
110
|
-
}
|
111
|
-
return 0;
|
112
|
-
}
|
113
|
-
|
114
|
-
static int on_header_value(http_parser* parser, const char* s, size_t len) {
|
115
|
-
Request* p = (Request*)parser;
|
116
|
-
if (p->last_field == Qnil) {
|
117
|
-
if (p->last_value == Qnil) {
|
118
|
-
rb_bug("on_header_value called when neither last_field nor last_value exist");
|
119
|
-
return 1;
|
120
|
-
}
|
121
|
-
rb_str_cat(p->last_value, s, len);
|
122
|
-
} else {
|
123
|
-
nyara_headerlize(p->last_field);
|
124
|
-
p->last_value = rb_str_new(s, len);
|
125
|
-
rb_hash_aset(p->header, p->last_field, p->last_value);
|
126
|
-
p->last_field = Qnil;
|
127
|
-
}
|
128
|
-
return 0;
|
129
|
-
}
|
130
|
-
|
131
|
-
// may override POST by _method in query
|
132
|
-
static void _parse_path_and_query(Request* p) {
|
133
|
-
char* s = RSTRING_PTR(p->path_with_query);
|
134
|
-
long len = RSTRING_LEN(p->path_with_query);
|
135
|
-
long query_i = nyara_parse_path(p->path, s, len);
|
136
|
-
if (query_i < len) {
|
137
|
-
nyara_parse_param(p->query, s + query_i, len - query_i);
|
138
|
-
|
139
|
-
// do method override with _method=xxx in query
|
140
|
-
if (p->method == HTTP_POST) {
|
141
|
-
VALUE meth = rb_hash_aref(p->query, method_override_key);
|
142
|
-
if (TYPE(meth) == T_STRING) {
|
143
|
-
_upcase_method(meth);
|
144
|
-
VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
|
145
|
-
if (meth_num != Qnil) {
|
146
|
-
p->method = FIX2INT(meth_num);
|
147
|
-
}
|
148
|
-
}
|
149
|
-
}
|
150
|
-
}
|
151
|
-
}
|
10
|
+
static VALUE sym_writing;
|
152
11
|
|
153
|
-
|
154
|
-
Request* p
|
155
|
-
|
156
|
-
p->last_value = Qnil;
|
157
|
-
|
158
|
-
_parse_path_and_query(p);
|
159
|
-
p->accept = ext_parse_accept_value(Qnil, rb_hash_aref(p->header, str_accept));
|
160
|
-
volatile RouteResult result = nyara_lookup_route(p->method, p->path, p->accept);
|
161
|
-
if (RTEST(result.controller)) {
|
162
|
-
rb_ary_push(result.args, rb_class_new_instance(1, &(p->self), result.controller));
|
163
|
-
// result.args is on stack, no need to worry gc
|
164
|
-
p->fiber = rb_fiber_new(_fiber_func, result.args);
|
165
|
-
p->scope = result.scope;
|
166
|
-
p->format = result.format;
|
167
|
-
p->response_header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
|
168
|
-
p->response_header_extra_lines = rb_ary_new();
|
169
|
-
return 0;
|
170
|
-
}
|
171
|
-
rb_funcall(p->self, id_not_found, 0);
|
172
|
-
return 1;
|
173
|
-
}
|
174
|
-
|
175
|
-
static http_parser_settings request_settings = {
|
176
|
-
.on_message_begin = NULL,
|
177
|
-
.on_url = on_url,
|
178
|
-
.on_status_complete = NULL,
|
179
|
-
.on_header_field = on_header_field,
|
180
|
-
.on_header_value = on_header_value,
|
181
|
-
.on_headers_complete = on_headers_complete,
|
182
|
-
.on_body = NULL, // data_cb
|
183
|
-
.on_message_complete = on_message_complete
|
184
|
-
};
|
12
|
+
#define P \
|
13
|
+
Request* p;\
|
14
|
+
Data_Get_Struct(self, Request, p);
|
185
15
|
|
186
16
|
static void request_mark(void* pp) {
|
187
17
|
Request* p = pp;
|
@@ -199,6 +29,7 @@ static void request_mark(void* pp) {
|
|
199
29
|
rb_gc_mark_maybe(p->response_content_type);
|
200
30
|
rb_gc_mark_maybe(p->response_header);
|
201
31
|
rb_gc_mark_maybe(p->response_header_extra_lines);
|
32
|
+
rb_gc_mark_maybe(p->watched_fds);
|
202
33
|
}
|
203
34
|
}
|
204
35
|
|
@@ -206,19 +37,25 @@ static void request_free(void* pp) {
|
|
206
37
|
Request* p = pp;
|
207
38
|
if (p) {
|
208
39
|
if (p->fd) {
|
209
|
-
|
40
|
+
nyara_detach_fd(p->fd);
|
210
41
|
}
|
211
42
|
xfree(p);
|
212
43
|
}
|
213
44
|
}
|
214
45
|
|
215
|
-
static Request*
|
46
|
+
static Request* _request_alloc() {
|
216
47
|
Request* p = ALLOC(Request);
|
217
48
|
http_parser_init(&(p->hparser), HTTP_REQUEST);
|
49
|
+
p->mparser = NULL;
|
50
|
+
|
51
|
+
p->method = HTTP_GET;
|
52
|
+
p->fd = 0;
|
53
|
+
p->parse_state = 0;
|
54
|
+
p->status = 200;
|
55
|
+
|
218
56
|
volatile VALUE header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
|
219
57
|
volatile VALUE path = rb_enc_str_new("", 0, u8_encoding);
|
220
58
|
volatile VALUE query = rb_class_new_instance(0, NULL, nyara_param_hash_class);
|
221
|
-
p->mparser = NULL;
|
222
59
|
p->header = header;
|
223
60
|
p->accept = Qnil;
|
224
61
|
p->format = Qnil;
|
@@ -229,66 +66,36 @@ static Request* request_alloc() {
|
|
229
66
|
p->query = query;
|
230
67
|
p->last_field = Qnil;
|
231
68
|
p->last_value = Qnil;
|
232
|
-
p->fd = 0;
|
233
|
-
p->status = 200;
|
234
69
|
p->response_content_type = Qnil;
|
235
70
|
p->response_header = Qnil;
|
236
71
|
p->response_header_extra_lines = Qnil;
|
72
|
+
|
73
|
+
volatile VALUE watched_fds = rb_ary_new();
|
74
|
+
p->watched_fds = watched_fds;
|
75
|
+
|
237
76
|
p->self = Data_Wrap_Struct(request_class, request_mark, request_free, p);
|
238
77
|
return p;
|
239
78
|
}
|
240
79
|
|
241
|
-
|
242
|
-
|
80
|
+
VALUE nyara_request_new(int fd) {
|
81
|
+
Request* p = _request_alloc();
|
82
|
+
p->fd = fd;
|
83
|
+
return p->self;
|
243
84
|
}
|
244
85
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
Request* p = NULL;
|
254
|
-
bool first_time = false;
|
255
|
-
|
256
|
-
{
|
257
|
-
VALUE v_fd = INT2FIX(fd);
|
258
|
-
VALUE request = rb_hash_aref(fd_request_map, v_fd);
|
259
|
-
if (request == Qnil) {
|
260
|
-
p = request_alloc();
|
261
|
-
p->fd = fd;
|
262
|
-
rb_hash_aset(fd_request_map, v_fd, p->self);
|
263
|
-
first_time = true;
|
264
|
-
} else {
|
265
|
-
Data_Get_Struct(request, Request, p);
|
266
|
-
}
|
267
|
-
}
|
268
|
-
|
269
|
-
long len = read(fd, received_data, MAX_RECEIVE_DATA);
|
270
|
-
if (len < 0) {
|
271
|
-
if (errno != EAGAIN) {
|
272
|
-
// todo log the bug
|
273
|
-
if (p->fd) {
|
274
|
-
_close_fd(p->fd);
|
275
|
-
p->fd = 0;
|
276
|
-
}
|
277
|
-
}
|
278
|
-
} else {
|
279
|
-
if (first_time && !len) {
|
280
|
-
// todo log this exception
|
281
|
-
return;
|
282
|
-
}
|
283
|
-
// note: when len == 0, means eof reached, that also informs http_parser the eof
|
284
|
-
http_parser_execute(&(p->hparser), &request_settings, received_data, len);
|
86
|
+
void nyara_request_term_close(VALUE self, bool write_last_chunk) {
|
87
|
+
P;
|
88
|
+
if (write_last_chunk || p->status == 200) {
|
89
|
+
// usually this succeeds, while not, it doesn't matter cause we are closing it
|
90
|
+
# pragma GCC diagnostic push
|
91
|
+
# pragma GCC diagnostic ignored "-Wunused-result"
|
92
|
+
write(p->fd, "0\r\n\r\n", 5);
|
93
|
+
# pragma GCC diagnostic pop
|
285
94
|
}
|
95
|
+
nyara_detach_fd(p->fd);
|
96
|
+
p->fd = 0;
|
286
97
|
}
|
287
98
|
|
288
|
-
#define P \
|
289
|
-
Request* p;\
|
290
|
-
Data_Get_Struct(self, Request, p);
|
291
|
-
|
292
99
|
static VALUE request_http_method(VALUE self) {
|
293
100
|
P;
|
294
101
|
return rb_str_new2(http_method_str(p->method));
|
@@ -361,50 +168,72 @@ static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
|
|
361
168
|
return n;
|
362
169
|
}
|
363
170
|
|
364
|
-
|
365
|
-
|
366
|
-
char* buf = RSTRING_PTR(data);
|
367
|
-
long len = RSTRING_LEN(data);
|
368
|
-
|
171
|
+
// return true if success
|
172
|
+
static bool _send_data(int fd, const char* buf, long len) {
|
369
173
|
while(len) {
|
370
|
-
long written = write(
|
371
|
-
if (written
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
174
|
+
long written = write(fd, buf, len);
|
175
|
+
if (written <= 0) {
|
176
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
177
|
+
rb_fiber_yield(1, &sym_writing);
|
178
|
+
} else {
|
179
|
+
return false;
|
180
|
+
}
|
181
|
+
} else {
|
182
|
+
buf += written;
|
183
|
+
len -= written;
|
184
|
+
if (len) {
|
185
|
+
rb_fiber_yield(1, &sym_writing);
|
376
186
|
}
|
377
|
-
return Qnil;
|
378
187
|
}
|
379
|
-
buf += written;
|
380
|
-
len -= written;
|
381
188
|
}
|
189
|
+
return true;
|
190
|
+
}
|
191
|
+
|
192
|
+
static VALUE ext_request_send_data(VALUE _, VALUE self, VALUE data) {
|
193
|
+
P;
|
194
|
+
char* buf = RSTRING_PTR(data);
|
195
|
+
long len = RSTRING_LEN(data);
|
196
|
+
_send_data(p->fd, buf, len);
|
382
197
|
return Qnil;
|
383
198
|
}
|
384
199
|
|
385
|
-
static VALUE
|
200
|
+
static VALUE ext_request_send_chunk(VALUE _, VALUE self, VALUE str) {
|
386
201
|
long len = RSTRING_LEN(str);
|
387
202
|
if (!len) {
|
388
203
|
return Qnil;
|
389
204
|
}
|
390
|
-
// todo len overflow?
|
391
205
|
P;
|
392
|
-
|
393
|
-
|
394
|
-
|
206
|
+
|
207
|
+
char pre_buf[20]; // enough space to hold a long + 2 chars
|
208
|
+
long pre_len = sprintf(pre_buf, "%lx\r\n", len);
|
209
|
+
if (pre_len <= 0) {
|
210
|
+
rb_raise(rb_eRuntimeError, "fail to format chunk length for len: %ld", len);
|
211
|
+
}
|
212
|
+
bool success = \
|
213
|
+
_send_data(p->fd, pre_buf, pre_len) &&
|
214
|
+
_send_data(p->fd, RSTRING_PTR(str), len) &&
|
215
|
+
_send_data(p->fd, "\r\n", 2);
|
216
|
+
|
217
|
+
if (!success) {
|
218
|
+
rb_sys_fail("write(2)");
|
395
219
|
}
|
220
|
+
|
396
221
|
return Qnil;
|
397
222
|
}
|
398
223
|
|
399
224
|
// for test: find or create a request with a fd
|
400
|
-
static VALUE
|
401
|
-
|
402
|
-
|
403
|
-
|
225
|
+
static VALUE ext_request_new(VALUE _) {
|
226
|
+
return _request_alloc()->self;
|
227
|
+
}
|
228
|
+
|
229
|
+
static VALUE ext_request_set_fd(VALUE _, VALUE self, VALUE vfd) {
|
230
|
+
P;
|
231
|
+
p->fd = NUM2INT(vfd);
|
232
|
+
return Qnil;
|
404
233
|
}
|
405
234
|
|
406
235
|
// set internal attrs in the request object
|
407
|
-
static VALUE
|
236
|
+
static VALUE ext_request_set_attrs(VALUE _, VALUE self, VALUE attrs) {
|
408
237
|
# define ATTR(key) rb_hash_delete(attrs, ID2SYM(rb_intern(key)))
|
409
238
|
# define HEADER_HASH_NEW rb_class_new_instance(0, NULL, nyara_header_hash_class)
|
410
239
|
P;
|
@@ -437,22 +266,14 @@ static VALUE ext_set_request_attrs(VALUE _, VALUE self, VALUE attrs) {
|
|
437
266
|
}
|
438
267
|
|
439
268
|
void Init_request(VALUE nyara, VALUE ext) {
|
440
|
-
id_not_found = rb_intern("not_found");
|
441
269
|
str_html = rb_str_new2("html");
|
442
270
|
OBJ_FREEZE(str_html);
|
443
271
|
rb_gc_register_mark_object(str_html);
|
444
272
|
u8_encoding = rb_utf8_encoding();
|
445
|
-
|
446
|
-
rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
|
447
|
-
nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
|
448
|
-
fd_request_map = rb_hash_new();
|
449
|
-
rb_gc_register_mark_object(fd_request_map);
|
450
|
-
str_accept = rb_str_new2("Accept");
|
451
|
-
rb_gc_register_mark_object(str_accept);
|
273
|
+
sym_writing = ID2SYM(rb_intern("writing"));
|
452
274
|
|
453
275
|
// request
|
454
276
|
request_class = rb_define_class_under(nyara, "Request", rb_cObject);
|
455
|
-
rb_define_alloc_func(request_class, request_alloc_func);
|
456
277
|
rb_define_method(request_class, "http_method", request_http_method, 0);
|
457
278
|
rb_define_method(request_class, "header", request_header, 0);
|
458
279
|
rb_define_method(request_class, "scope", request_scope, 0);
|
@@ -470,9 +291,10 @@ void Init_request(VALUE nyara, VALUE ext) {
|
|
470
291
|
|
471
292
|
// hide internal methods in ext
|
472
293
|
rb_define_singleton_method(ext, "request_set_status", ext_request_set_status, 2);
|
473
|
-
rb_define_singleton_method(ext, "
|
474
|
-
rb_define_singleton_method(ext, "
|
294
|
+
rb_define_singleton_method(ext, "request_send_data", ext_request_send_data, 2);
|
295
|
+
rb_define_singleton_method(ext, "request_send_chunk", ext_request_send_chunk, 2);
|
475
296
|
// for test
|
476
|
-
rb_define_singleton_method(ext, "
|
477
|
-
rb_define_singleton_method(ext, "
|
297
|
+
rb_define_singleton_method(ext, "request_new", ext_request_new, 0);
|
298
|
+
rb_define_singleton_method(ext, "request_set_fd", ext_request_set_fd, 2);
|
299
|
+
rb_define_singleton_method(ext, "request_set_attrs", ext_request_set_attrs, 2);
|
478
300
|
}
|
data/ext/request.h
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include "nyara.h"
|
4
|
+
#include <multipart_parser.h>
|
5
|
+
#include <errno.h>
|
6
|
+
#ifndef write
|
7
|
+
#include <unistd.h>
|
8
|
+
#endif
|
9
|
+
|
10
|
+
enum ParseState {
|
11
|
+
PS_INIT, PS_HEADERS_COMPLETE, PS_MESSAGE_COMPLETE, PS_ERROR
|
12
|
+
};
|
13
|
+
|
14
|
+
typedef struct {
|
15
|
+
http_parser hparser;
|
16
|
+
multipart_parser* mparser;
|
17
|
+
|
18
|
+
enum http_method method;
|
19
|
+
int fd;
|
20
|
+
enum ParseState parse_state;
|
21
|
+
int status; // response status
|
22
|
+
|
23
|
+
VALUE self;
|
24
|
+
|
25
|
+
// request
|
26
|
+
VALUE header;
|
27
|
+
VALUE accept; // mime array sorted with q
|
28
|
+
VALUE format; // string ext without dot
|
29
|
+
VALUE fiber;
|
30
|
+
VALUE scope; // mapped prefix
|
31
|
+
VALUE path_with_query;
|
32
|
+
VALUE path;
|
33
|
+
VALUE query;
|
34
|
+
VALUE last_field;
|
35
|
+
VALUE last_value;
|
36
|
+
|
37
|
+
// response
|
38
|
+
VALUE response_content_type;
|
39
|
+
VALUE response_header;
|
40
|
+
VALUE response_header_extra_lines;
|
41
|
+
|
42
|
+
VALUE watched_fds;
|
43
|
+
} Request;
|
data/ext/request_parse.c
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
/* request parse callbacks */
|
2
|
+
|
3
|
+
#include "nyara.h"
|
4
|
+
#include "request.h"
|
5
|
+
|
6
|
+
static VALUE str_accept;
|
7
|
+
static VALUE method_override_key;
|
8
|
+
static VALUE nyara_http_methods;
|
9
|
+
|
10
|
+
static int on_url(http_parser* parser, const char* s, size_t len) {
|
11
|
+
Request* p = (Request*)parser;
|
12
|
+
p->method = parser->method;
|
13
|
+
|
14
|
+
if (p->path_with_query == Qnil) {
|
15
|
+
p->path_with_query = rb_str_new(s, len);
|
16
|
+
} else {
|
17
|
+
rb_str_cat(p->path_with_query, s, len);
|
18
|
+
}
|
19
|
+
return 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
static int on_header_field(http_parser* parser, const char* s, size_t len) {
|
23
|
+
Request* p = (Request*)parser;
|
24
|
+
if (p->last_field == Qnil) {
|
25
|
+
p->last_field = rb_str_new(s, len);
|
26
|
+
p->last_value = Qnil;
|
27
|
+
} else {
|
28
|
+
rb_str_cat(p->last_field, s, len);
|
29
|
+
}
|
30
|
+
return 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
static int on_header_value(http_parser* parser, const char* s, size_t len) {
|
34
|
+
Request* p = (Request*)parser;
|
35
|
+
if (p->last_field == Qnil) {
|
36
|
+
if (p->last_value == Qnil) {
|
37
|
+
p->parse_state = PS_ERROR;
|
38
|
+
return 1;
|
39
|
+
}
|
40
|
+
rb_str_cat(p->last_value, s, len);
|
41
|
+
} else {
|
42
|
+
nyara_headerlize(p->last_field);
|
43
|
+
p->last_value = rb_str_new(s, len);
|
44
|
+
rb_hash_aset(p->header, p->last_field, p->last_value);
|
45
|
+
p->last_field = Qnil;
|
46
|
+
}
|
47
|
+
return 0;
|
48
|
+
}
|
49
|
+
|
50
|
+
static void _upcase_method(VALUE str) {
|
51
|
+
char* s = RSTRING_PTR(str);
|
52
|
+
long len = RSTRING_LEN(str);
|
53
|
+
for (long i = 0; i < len; i++) {
|
54
|
+
if (s[i] >= 'a' && s[i] <= 'z') {
|
55
|
+
s[i] = 'A' + (s[i] - 'a');
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
// may override POST by _method in query
|
61
|
+
static void _parse_path_and_query(Request* p) {
|
62
|
+
char* s = RSTRING_PTR(p->path_with_query);
|
63
|
+
long len = RSTRING_LEN(p->path_with_query);
|
64
|
+
long query_i = nyara_parse_path(p->path, s, len);
|
65
|
+
if (query_i < len) {
|
66
|
+
nyara_parse_param(p->query, s + query_i, len - query_i);
|
67
|
+
|
68
|
+
// do method override with _method=xxx in query
|
69
|
+
if (p->method == HTTP_POST) {
|
70
|
+
VALUE meth = rb_hash_aref(p->query, method_override_key);
|
71
|
+
if (TYPE(meth) == T_STRING) {
|
72
|
+
_upcase_method(meth);
|
73
|
+
VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
|
74
|
+
if (meth_num != Qnil) {
|
75
|
+
p->method = FIX2INT(meth_num);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
static int on_headers_complete(http_parser* parser) {
|
83
|
+
Request* p = (Request*)parser;
|
84
|
+
p->last_field = Qnil;
|
85
|
+
p->last_value = Qnil;
|
86
|
+
|
87
|
+
_parse_path_and_query(p);
|
88
|
+
p->accept = ext_parse_accept_value(Qnil, rb_hash_aref(p->header, str_accept));
|
89
|
+
p->parse_state = PS_HEADERS_COMPLETE;
|
90
|
+
return 0;
|
91
|
+
}
|
92
|
+
|
93
|
+
static int on_body(http_parser* parser, const char* s, size_t len) {
|
94
|
+
// todo
|
95
|
+
return 0;
|
96
|
+
}
|
97
|
+
|
98
|
+
static int on_message_complete(http_parser* parser) {
|
99
|
+
Request* p = (Request*)parser;
|
100
|
+
p->parse_state = PS_MESSAGE_COMPLETE;
|
101
|
+
return 0;
|
102
|
+
}
|
103
|
+
|
104
|
+
// used in event.c
|
105
|
+
http_parser_settings nyara_request_parse_settings = {
|
106
|
+
.on_message_begin = NULL,
|
107
|
+
.on_url = on_url,
|
108
|
+
.on_status_complete = NULL,
|
109
|
+
.on_header_field = on_header_field,
|
110
|
+
.on_header_value = on_header_value,
|
111
|
+
.on_headers_complete = on_headers_complete,
|
112
|
+
.on_body = on_body,
|
113
|
+
.on_message_complete = on_message_complete
|
114
|
+
};
|
115
|
+
|
116
|
+
void Init_request_parse(VALUE nyara) {
|
117
|
+
str_accept = rb_str_new2("Accept");
|
118
|
+
rb_gc_register_mark_object(str_accept);
|
119
|
+
method_override_key = rb_str_new2("_method");
|
120
|
+
OBJ_FREEZE(method_override_key);
|
121
|
+
rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
|
122
|
+
nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
|
123
|
+
}
|