nyara 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/example/design.rb +62 -0
  3. data/example/fib.rb +15 -0
  4. data/example/hello.rb +5 -0
  5. data/example/stream.rb +10 -0
  6. data/ext/accept.c +133 -0
  7. data/ext/event.c +89 -0
  8. data/ext/extconf.rb +34 -0
  9. data/ext/hashes.c +130 -0
  10. data/ext/http-parser/AUTHORS +41 -0
  11. data/ext/http-parser/CONTRIBUTIONS +4 -0
  12. data/ext/http-parser/LICENSE-MIT +23 -0
  13. data/ext/http-parser/contrib/parsertrace.c +156 -0
  14. data/ext/http-parser/contrib/url_parser.c +44 -0
  15. data/ext/http-parser/http_parser.c +2175 -0
  16. data/ext/http-parser/http_parser.h +304 -0
  17. data/ext/http-parser/test.c +3425 -0
  18. data/ext/http_parser.c +1 -0
  19. data/ext/inc/epoll.h +60 -0
  20. data/ext/inc/kqueue.h +77 -0
  21. data/ext/inc/status_codes.inc +64 -0
  22. data/ext/inc/str_intern.h +66 -0
  23. data/ext/inc/version.inc +1 -0
  24. data/ext/mime.c +107 -0
  25. data/ext/multipart-parser-c/README.md +18 -0
  26. data/ext/multipart-parser-c/multipart_parser.c +309 -0
  27. data/ext/multipart-parser-c/multipart_parser.h +48 -0
  28. data/ext/multipart_parser.c +1 -0
  29. data/ext/nyara.c +56 -0
  30. data/ext/nyara.h +59 -0
  31. data/ext/request.c +474 -0
  32. data/ext/route.cc +325 -0
  33. data/ext/url_encoded.c +304 -0
  34. data/hello.rb +5 -0
  35. data/lib/nyara/config.rb +64 -0
  36. data/lib/nyara/config_hash.rb +51 -0
  37. data/lib/nyara/controller.rb +336 -0
  38. data/lib/nyara/cookie.rb +31 -0
  39. data/lib/nyara/cpu_counter.rb +65 -0
  40. data/lib/nyara/header_hash.rb +18 -0
  41. data/lib/nyara/mime_types.rb +612 -0
  42. data/lib/nyara/nyara.rb +82 -0
  43. data/lib/nyara/param_hash.rb +5 -0
  44. data/lib/nyara/request.rb +144 -0
  45. data/lib/nyara/route.rb +138 -0
  46. data/lib/nyara/route_entry.rb +43 -0
  47. data/lib/nyara/session.rb +104 -0
  48. data/lib/nyara/view.rb +317 -0
  49. data/lib/nyara.rb +25 -0
  50. data/nyara.gemspec +20 -0
  51. data/rakefile +91 -0
  52. data/readme.md +35 -0
  53. data/spec/ext_mime_match_spec.rb +27 -0
  54. data/spec/ext_parse_accept_value_spec.rb +29 -0
  55. data/spec/ext_parse_spec.rb +138 -0
  56. data/spec/ext_route_spec.rb +70 -0
  57. data/spec/hashes_spec.rb +71 -0
  58. data/spec/path_helper_spec.rb +77 -0
  59. data/spec/request_delegate_spec.rb +67 -0
  60. data/spec/request_spec.rb +56 -0
  61. data/spec/route_entry_spec.rb +12 -0
  62. data/spec/route_spec.rb +84 -0
  63. data/spec/session_spec.rb +66 -0
  64. data/spec/spec_helper.rb +52 -0
  65. data/spec/view_spec.rb +87 -0
  66. data/tools/bench-cookie.rb +22 -0
  67. metadata +111 -0
@@ -0,0 +1,48 @@
1
+ /* Based on node-formidable by Felix Geisendörfer
2
+ * Igor Afonov - afonov@gmail.com - 2012
3
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
4
+ */
5
+ #ifndef _multipart_parser_h
6
+ #define _multipart_parser_h
7
+
8
+ #ifdef __cplusplus
9
+ extern "C"
10
+ {
11
+ #endif
12
+
13
+ #include <stdlib.h>
14
+ #include <ctype.h>
15
+
16
+ typedef struct multipart_parser multipart_parser;
17
+ typedef struct multipart_parser_settings multipart_parser_settings;
18
+ typedef struct multipart_parser_state multipart_parser_state;
19
+
20
+ typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
21
+ typedef int (*multipart_notify_cb) (multipart_parser*);
22
+
23
+ struct multipart_parser_settings {
24
+ multipart_data_cb on_header_field;
25
+ multipart_data_cb on_header_value;
26
+ multipart_data_cb on_part_data;
27
+
28
+ multipart_notify_cb on_part_data_begin;
29
+ multipart_notify_cb on_headers_complete;
30
+ multipart_notify_cb on_part_data_end;
31
+ multipart_notify_cb on_body_end;
32
+ };
33
+
34
+ multipart_parser* multipart_parser_init
35
+ (const char *boundary, const multipart_parser_settings* settings);
36
+
37
+ void multipart_parser_free(multipart_parser* p);
38
+
39
+ size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len);
40
+
41
+ void multipart_parser_set_data(multipart_parser* p, void* data);
42
+ void * multipart_parser_get_data(multipart_parser* p);
43
+
44
+ #ifdef __cplusplus
45
+ } /* extern "C" */
46
+ #endif
47
+
48
+ #endif
@@ -0,0 +1 @@
1
+ #include "multipart-parser-c/multipart_parser.c"
data/ext/nyara.c ADDED
@@ -0,0 +1,56 @@
1
+ #include "nyara.h"
2
+ #include <ruby/io.h>
3
+ #include <sys/socket.h>
4
+ #include <sys/resource.h>
5
+
6
+ static void set_fd_limit(int nofiles) {
7
+ struct rlimit rlim;
8
+ getrlimit (RLIMIT_NOFILE, &rlim);
9
+ if (nofiles >= 0) {
10
+ rlim.rlim_cur = nofiles;
11
+ if ((unsigned int)nofiles > rlim.rlim_max)
12
+ rlim.rlim_max = nofiles;
13
+ setrlimit (RLIMIT_NOFILE, &rlim);
14
+ }
15
+ }
16
+
17
+ void Init_nyara() {
18
+ set_fd_limit(20000);
19
+
20
+ VALUE nyara = rb_define_module("Nyara");
21
+
22
+ // utils: hashes
23
+ Init_hashes(nyara);
24
+
25
+ // utils: method map
26
+ volatile VALUE method_map = rb_class_new_instance(0, NULL, nyara_param_hash_class);
27
+ rb_const_set(nyara, rb_intern("HTTP_METHODS"), method_map);
28
+ VALUE tmp_key = Qnil;
29
+ # define METHOD_STR2NUM(n, name, string) \
30
+ tmp_key = rb_str_new2(#string);\
31
+ OBJ_FREEZE(tmp_key);\
32
+ rb_hash_aset(method_map, tmp_key, INT2FIX(n));
33
+ HTTP_METHOD_MAP(METHOD_STR2NUM);
34
+ # undef METHOD_STR2NUM
35
+ OBJ_FREEZE(method_map);
36
+
37
+ // utils: status codes
38
+ volatile VALUE status_map = rb_hash_new();
39
+ rb_const_set(nyara, rb_intern("HTTP_STATUS_CODES"), status_map);
40
+ VALUE tmp_value = Qnil;
41
+ # define STATUS_DESC(status, desc) \
42
+ tmp_value = rb_str_new2(desc);\
43
+ OBJ_FREEZE(tmp_value);\
44
+ rb_hash_aset(status_map, INT2FIX(status), tmp_value);
45
+ HTTP_STATUS_CODES(STATUS_DESC);
46
+ # undef STATUS_DESC
47
+ OBJ_FREEZE(status_map);
48
+
49
+ VALUE ext = rb_define_module_under(nyara, "Ext");
50
+ Init_accept(ext);
51
+ Init_mime(ext);
52
+ Init_request(nyara, ext);
53
+ Init_event(ext);
54
+ Init_route(nyara, ext);
55
+ Init_url_encoded(ext);
56
+ }
data/ext/nyara.h ADDED
@@ -0,0 +1,59 @@
1
+ #pragma once
2
+ #include <ruby.h>
3
+ #include <stdbool.h>
4
+ #include <http_parser.h>
5
+ #include "inc/status_codes.inc"
6
+
7
+ #ifdef DEBUG
8
+ #undef NDEBUG
9
+ #endif
10
+
11
+
12
+ /* -- event -- */
13
+ void Init_event(VALUE ext);
14
+
15
+
16
+ /* -- request & response class -- */
17
+ void Init_request(VALUE nyara, VALUE ext);
18
+ void nyara_handle_request(int fd);
19
+
20
+
21
+ /* -- url encoded parse -- */
22
+ void Init_url_encoded(VALUE ext);
23
+ size_t nyara_parse_path(VALUE path, const char*s, size_t len);
24
+ void nyara_parse_param(VALUE output, const char* s, size_t len);
25
+
26
+
27
+ /* -- accept parse -- */
28
+ void Init_accept(VALUE ext);
29
+ VALUE ext_parse_accept_value(VALUE _, VALUE str);
30
+
31
+
32
+ /* -- mime parse and match -- */
33
+ void Init_mime(VALUE ext);
34
+ VALUE ext_mime_match(VALUE _, VALUE request_accept, VALUE accept_mimes);
35
+
36
+
37
+ /* -- hashes -- */
38
+ void Init_hashes(VALUE nyara);
39
+
40
+ // ab-cd => Ab-Cd
41
+ // note str must be string created by nyara code
42
+ void nyara_headerlize(VALUE str);
43
+ int nyara_rb_hash_has_key(VALUE hash, VALUE key);
44
+
45
+ extern VALUE nyara_param_hash_class;
46
+ extern VALUE nyara_header_hash_class;
47
+ extern VALUE nyara_config_hash_class;
48
+
49
+
50
+ /* -- route -- */
51
+ typedef struct {
52
+ VALUE controller;
53
+ VALUE args;
54
+ VALUE scope;
55
+ VALUE ext; // maybe string or map
56
+ } RouteResult;
57
+
58
+ extern void Init_route(VALUE nyara, VALUE ext);
59
+ extern RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath);
data/ext/request.c ADDED
@@ -0,0 +1,474 @@
1
+ #include "nyara.h"
2
+ #include <ruby/encoding.h>
3
+ #include <multipart_parser.h>
4
+ #include <errno.h>
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 fiber;
15
+ VALUE scope; // mapped prefix
16
+ VALUE path;
17
+ VALUE param;
18
+ VALUE last_field;
19
+ VALUE last_value;
20
+ // string if determined by url, or hash if need further check with header[Accept]
21
+ VALUE ext;
22
+ VALUE self;
23
+ int fd;
24
+
25
+ // response
26
+ int status;
27
+ VALUE response_content_type;
28
+ VALUE response_header;
29
+ VALUE response_header_extra_lines;
30
+ } Request;
31
+
32
+ // typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
33
+ // typedef int (*http_cb) (http_parser*);
34
+
35
+ static ID id_not_found;
36
+ static VALUE str_html;
37
+ static rb_encoding* u8_encoding;
38
+ static VALUE request_class;
39
+ static VALUE method_override_key;
40
+ static VALUE str_accept;
41
+ static VALUE nyara_http_methods;
42
+
43
+ static VALUE fd_request_map;
44
+ #define MAX_RECEIVE_DATA 65536
45
+ static char received_data[MAX_RECEIVE_DATA];
46
+
47
+ static VALUE fiber_func(VALUE _, VALUE args) {
48
+ VALUE instance = rb_ary_pop(args);
49
+ VALUE meth = rb_ary_pop(args);
50
+ rb_apply(instance, SYM2ID(meth), args);
51
+ return Qnil;
52
+ }
53
+
54
+ static void _upcase_method(VALUE str) {
55
+ char* s = RSTRING_PTR(str);
56
+ long len = RSTRING_LEN(str);
57
+ for (long i = 0; i < len; i++) {
58
+ if (s[i] >= 'a' && s[i] <= 'z') {
59
+ s[i] = 'A' + (s[i] - 'a');
60
+ }
61
+ }
62
+ }
63
+
64
+ static inline void _close_fd(int fd) {
65
+ rb_hash_delete(fd_request_map, INT2FIX(fd));
66
+ close(fd);
67
+ }
68
+
69
+ // fixme assume url is always sent as whole (tcp window is large!)
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
+ // matching raw path is bad idea, for example: %a0 and %A0 are different strings but same route
75
+ p->path = rb_enc_str_new("", 0, u8_encoding);
76
+ size_t query_i = nyara_parse_path(p->path, s, len);
77
+ p->param = rb_class_new_instance(0, NULL, nyara_param_hash_class);
78
+ if (query_i < len) {
79
+ nyara_parse_param(p->param, s + query_i, len - query_i);
80
+
81
+ // do method override with _method=xxx in query
82
+ if (p->method == HTTP_POST) {
83
+ VALUE meth = rb_hash_aref(p->param, method_override_key);
84
+ if (TYPE(meth) == T_STRING) {
85
+ _upcase_method(meth);
86
+ VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
87
+ if (meth_num != Qnil) {
88
+ p->method = FIX2INT(meth_num);
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ volatile RouteResult result = nyara_lookup_route(p->method, p->path);
95
+ if (RTEST(result.controller)) {
96
+ {
97
+ VALUE instance = rb_class_new_instance(1, &(p->self), result.controller);
98
+ rb_ary_push(result.args, instance);
99
+ }
100
+ // result.args is on stack, no need to worry gc
101
+ p->fiber = rb_fiber_new(fiber_func, result.args);
102
+ p->scope = result.scope;
103
+ p->header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
104
+ p->ext = result.ext;
105
+ p->response_header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
106
+ p->response_header_extra_lines = rb_ary_new();
107
+ return 0;
108
+ } else {
109
+ rb_funcall(p->self, id_not_found, 0);
110
+ return 1;
111
+ }
112
+ }
113
+
114
+ static int on_message_complete(http_parser* parser) {
115
+ Request* p = (Request*)parser;
116
+ if (p->fiber == Qnil) {
117
+ return 1;
118
+ } else {
119
+ VALUE state = rb_fiber_resume(p->fiber, 0, NULL);
120
+ if (state == Qnil) { // terminated (todo check raise error)
121
+ if (p->status == 200) {
122
+ write(p->fd, "0\r\n\r\n", 5);
123
+ }
124
+ _close_fd(p->fd);
125
+ p->fd = 0;
126
+ } else if (SYM2ID(state) == rb_intern("term_close")) {
127
+ write(p->fd, "0\r\n\r\n", 5);
128
+ _close_fd(p->fd);
129
+ p->fd = 0;
130
+ }
131
+ return 0;
132
+ }
133
+ }
134
+
135
+ static int on_header_field(http_parser* parser, const char* s, size_t len) {
136
+ Request* p = (Request*)parser;
137
+ if (p->last_field == Qnil) {
138
+ p->last_field = rb_str_new(s, len);
139
+ p->last_value = Qnil;
140
+ } else {
141
+ rb_str_cat(p->last_field, s, len);
142
+ }
143
+ return 0;
144
+ }
145
+
146
+ static int on_header_value(http_parser* parser, const char* s, size_t len) {
147
+ Request* p = (Request*)parser;
148
+ if (p->last_field == Qnil) {
149
+ if (p->last_value == Qnil) {
150
+ rb_bug("on_header_value called when neither last_field nor last_value exist");
151
+ return 1;
152
+ }
153
+ rb_str_cat(p->last_value, s, len);
154
+ } else {
155
+ nyara_headerlize(p->last_field);
156
+ p->last_value = rb_str_new(s, len);
157
+ rb_hash_aset(p->header, p->last_field, p->last_value);
158
+ p->last_field = Qnil;
159
+ }
160
+ return 0;
161
+ }
162
+
163
+ static int on_headers_complete(http_parser* parser) {
164
+ Request* p = (Request*)parser;
165
+ p->last_field = Qnil;
166
+ p->last_value = Qnil;
167
+
168
+ if (TYPE(p->ext) != T_STRING) {
169
+ VALUE accept = rb_hash_aref(p->header, str_accept);
170
+ if (RTEST(accept)) {
171
+ accept = ext_parse_accept_value(Qnil, accept);
172
+ rb_iv_set(p->self, "@accept", accept);
173
+ p->ext = ext_mime_match(Qnil, accept, p->ext);
174
+ }
175
+ if (p->ext == Qnil) {
176
+ rb_funcall(p->self, id_not_found, 0);
177
+ return 1;
178
+ }
179
+ }
180
+
181
+ return 0;
182
+ }
183
+
184
+ static http_parser_settings request_settings = {
185
+ .on_message_begin = NULL,
186
+ .on_url = on_url,
187
+ .on_status_complete = NULL,
188
+ .on_header_field = on_header_field,
189
+ .on_header_value = on_header_value,
190
+ .on_headers_complete = on_headers_complete,
191
+ .on_body = NULL, // data_cb
192
+ .on_message_complete = on_message_complete
193
+ };
194
+
195
+ static void request_mark(void* pp) {
196
+ Request* p = pp;
197
+ if (p) {
198
+ rb_gc_mark_maybe(p->header);
199
+ rb_gc_mark_maybe(p->fiber);
200
+ rb_gc_mark_maybe(p->scope);
201
+ rb_gc_mark_maybe(p->path);
202
+ rb_gc_mark_maybe(p->param);
203
+ rb_gc_mark_maybe(p->last_field);
204
+ rb_gc_mark_maybe(p->last_value);
205
+ rb_gc_mark_maybe(p->ext);
206
+ rb_gc_mark_maybe(p->response_content_type);
207
+ rb_gc_mark_maybe(p->response_header);
208
+ rb_gc_mark_maybe(p->response_header_extra_lines);
209
+ }
210
+ }
211
+
212
+ static void request_free(void* pp) {
213
+ Request* p = pp;
214
+ if (p) {
215
+ if (p->fd) {
216
+ _close_fd(p->fd);
217
+ }
218
+ xfree(p);
219
+ }
220
+ }
221
+
222
+ static Request* request_alloc() {
223
+ Request* p = ALLOC(Request);
224
+ http_parser_init(&(p->hparser), HTTP_REQUEST);
225
+ p->mparser = NULL;
226
+ p->header = Qnil;
227
+ p->fiber = Qnil;
228
+ p->scope = Qnil;
229
+ p->path = Qnil;
230
+ p->param = Qnil;
231
+ p->last_field = Qnil;
232
+ p->last_value = Qnil;
233
+ p->ext = Qnil;
234
+ p->fd = 0;
235
+ p->status = 200;
236
+ p->response_content_type = Qnil;
237
+ p->response_header = Qnil;
238
+ p->response_header_extra_lines = Qnil;
239
+ p->self = Data_Wrap_Struct(request_class, request_mark, request_free, p);
240
+ return p;
241
+ }
242
+
243
+ static VALUE request_alloc_func(VALUE klass) {
244
+ return request_alloc()->self;
245
+ }
246
+
247
+ /* client entrance
248
+ invoke order:
249
+ - find/create request
250
+ - http_parser_execute
251
+ - on_url
252
+ - on_message_complete
253
+ */
254
+ void nyara_handle_request(int fd) {
255
+ Request* p = NULL;
256
+ bool first_time = false;
257
+
258
+ {
259
+ VALUE v_fd = INT2FIX(fd);
260
+ VALUE request = rb_hash_aref(fd_request_map, v_fd);
261
+ if (request == Qnil) {
262
+ p = request_alloc();
263
+ p->fd = fd;
264
+ rb_hash_aset(fd_request_map, v_fd, p->self);
265
+ first_time = true;
266
+ } else {
267
+ Data_Get_Struct(request, Request, p);
268
+ }
269
+ }
270
+
271
+ long len = read(fd, received_data, MAX_RECEIVE_DATA);
272
+ if (len < 0) {
273
+ if (errno != EAGAIN) {
274
+ // todo log the bug
275
+ if (p->fd) {
276
+ _close_fd(p->fd);
277
+ p->fd = 0;
278
+ }
279
+ }
280
+ } else {
281
+ if (first_time && !len) {
282
+ // todo log this exception
283
+ return;
284
+ }
285
+ // note: when len == 0, means eof reached, that also informs http_parser the eof
286
+ http_parser_execute(&(p->hparser), &request_settings, received_data, len);
287
+ }
288
+ }
289
+
290
+ #define P \
291
+ Request* p;\
292
+ Data_Get_Struct(self, Request, p);
293
+
294
+ static VALUE request_http_method(VALUE self) {
295
+ P;
296
+ return rb_str_new2(http_method_str(p->method));
297
+ }
298
+
299
+ static VALUE request_header(VALUE self) {
300
+ P;
301
+ return p->header;
302
+ }
303
+
304
+ static VALUE request_scope(VALUE self) {
305
+ P;
306
+ return p->scope;
307
+ }
308
+
309
+ static VALUE request_path(VALUE self) {
310
+ P;
311
+ return p->path;
312
+ }
313
+
314
+ static VALUE request_matched_accept(VALUE _, VALUE self) {
315
+ P;
316
+ return p->ext == Qnil ? str_html : p->ext;
317
+ }
318
+
319
+ static VALUE request_status(VALUE self) {
320
+ P;
321
+ return INT2FIX(p->status);
322
+ }
323
+
324
+ static VALUE request_response_content_type(VALUE self) {
325
+ P;
326
+ return p->response_content_type;
327
+ }
328
+
329
+ static VALUE request_response_content_type_eq(VALUE self, VALUE ct) {
330
+ P;
331
+ p->response_content_type = ct;
332
+ return self;
333
+ }
334
+
335
+ static VALUE request_response_header(VALUE self) {
336
+ P;
337
+ return p->response_header;
338
+ }
339
+
340
+ static VALUE request_response_header_extra_lines(VALUE self) {
341
+ P;
342
+ return p->response_header_extra_lines;
343
+ }
344
+
345
+ static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
346
+ P;
347
+ p->status = NUM2INT(n);
348
+ return n;
349
+ }
350
+
351
+ static VALUE ext_request_param(VALUE _, VALUE self) {
352
+ P;
353
+ return p->param;
354
+ }
355
+
356
+ static VALUE ext_send_data(VALUE _, VALUE self, VALUE data) {
357
+ P;
358
+ char* buf = RSTRING_PTR(data);
359
+ long len = RSTRING_LEN(data);
360
+
361
+ while(len) {
362
+ long written = write(p->fd, buf, len);
363
+ if (written == 0)
364
+ return Qnil;
365
+ if (written == -1) {
366
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
367
+ // todo enqueue data and set state
368
+ }
369
+ return Qnil;
370
+ }
371
+ buf += written;
372
+ len -= written;
373
+ }
374
+ return Qnil;
375
+ }
376
+
377
+ static VALUE ext_send_chunk(VALUE _, VALUE self, VALUE str) {
378
+ long len = RSTRING_LEN(str);
379
+ if (!len) {
380
+ return Qnil;
381
+ }
382
+ // todo len overflow?
383
+ P;
384
+ long res = dprintf(p->fd, "%lx\r\n%.*s\r\n", len, (int)len, RSTRING_PTR(str));
385
+ if (res < 0) {
386
+ rb_raise(rb_eRuntimeError, "%s", strerror(errno));
387
+ }
388
+ return Qnil;
389
+ }
390
+
391
+ // for test: find or create a request with a fd
392
+ static VALUE ext_handle_request(VALUE _, VALUE v_fd) {
393
+ int fd = FIX2INT(v_fd);
394
+ nyara_handle_request(fd);
395
+ return rb_hash_aref(fd_request_map, v_fd);
396
+ }
397
+
398
+ // set internal attrs in the request object
399
+ static VALUE ext_set_request_attrs(VALUE _, VALUE self, VALUE attrs) {
400
+ # define ATTR(key) rb_hash_aref(attrs, ID2SYM(rb_intern(key)))
401
+ # define HEADER_HASH_NEW rb_class_new_instance(0, NULL, nyara_header_hash_class)
402
+ P;
403
+
404
+ if (ATTR("method_num") == Qnil) {
405
+ rb_raise(rb_eArgError, "bad method_num");
406
+ }
407
+
408
+ p->method = NUM2INT(ATTR("method_num"));
409
+ p->path = ATTR("path");
410
+ p->param = ATTR("param");
411
+ p->fiber = ATTR("fiber");
412
+ p->scope = ATTR("scope");
413
+ p->header = RTEST(ATTR("header")) ? ATTR("header") : HEADER_HASH_NEW;
414
+ p->ext = ATTR("ext");
415
+ p->response_header = RTEST(ATTR("response_header")) ? ATTR("response_header") : HEADER_HASH_NEW;
416
+ if (RTEST(ATTR("response_header_extra_lines"))) {
417
+ p->response_header_extra_lines = ATTR("response_header_extra_lines");
418
+ } else {
419
+ p->response_header_extra_lines = rb_ary_new();
420
+ }
421
+ return self;
422
+ # undef HEADER_HASH_NEW
423
+ # undef ATTR
424
+ }
425
+
426
+ // skip on_url so we can focus on testing request
427
+ static VALUE ext_set_skip_on_url(VALUE _, VALUE pred) {
428
+ if (RTEST(pred)) {
429
+ request_settings.on_url = NULL;
430
+ } else {
431
+ request_settings.on_url = on_url;
432
+ }
433
+ return Qnil;
434
+ }
435
+
436
+ void Init_request(VALUE nyara, VALUE ext) {
437
+ id_not_found = rb_intern("not_found");
438
+ str_html = rb_str_new2("html");
439
+ OBJ_FREEZE(str_html);
440
+ rb_gc_register_mark_object(str_html);
441
+ u8_encoding = rb_utf8_encoding();
442
+ method_override_key = rb_str_new2("_method");
443
+ rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
444
+ nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
445
+ fd_request_map = rb_hash_new();
446
+ rb_gc_register_mark_object(fd_request_map);
447
+ str_accept = rb_str_new2("Accept");
448
+ rb_gc_register_mark_object(fd_request_map);
449
+
450
+ // request
451
+ request_class = rb_define_class_under(nyara, "Request", rb_cObject);
452
+ rb_define_alloc_func(request_class, request_alloc_func);
453
+ rb_define_method(request_class, "http_method", request_http_method, 0);
454
+ rb_define_method(request_class, "header", request_header, 0);
455
+ rb_define_method(request_class, "scope", request_scope, 0);
456
+ rb_define_method(request_class, "path", request_path, 0);
457
+ rb_define_method(request_class, "matched_accept", request_matched_accept, 0);
458
+
459
+ rb_define_method(request_class, "status", request_status, 0);
460
+ rb_define_method(request_class, "response_content_type", request_response_content_type, 0);
461
+ rb_define_method(request_class, "response_content_type=", request_response_content_type_eq, 1);
462
+ rb_define_method(request_class, "response_header", request_response_header, 0);
463
+ rb_define_method(request_class, "response_header_extra_lines", request_response_header_extra_lines, 0);
464
+
465
+ // hide internal methods in ext
466
+ rb_define_singleton_method(ext, "request_set_status", ext_request_set_status, 2);
467
+ rb_define_singleton_method(ext, "request_param", ext_request_param, 1);
468
+ rb_define_singleton_method(ext, "send_data", ext_send_data, 2);
469
+ rb_define_singleton_method(ext, "send_chunk", ext_send_chunk, 2);
470
+ // for test
471
+ rb_define_singleton_method(ext, "handle_request", ext_handle_request, 1);
472
+ rb_define_singleton_method(ext, "set_request_attrs", ext_set_request_attrs, 2);
473
+ rb_define_singleton_method(ext, "set_skip_on_url", ext_set_skip_on_url, 1);
474
+ }