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
data/ext/route.cc ADDED
@@ -0,0 +1,325 @@
1
+ extern "C" {
2
+ #include "nyara.h"
3
+ }
4
+ #include <ruby/re.h>
5
+ #include <ruby/encoding.h>
6
+ #include <vector>
7
+ #include <map>
8
+ #include "inc/str_intern.h"
9
+
10
+ struct RouteEntry {
11
+ // note on order: scope is supposed to be the last, but when searching, is_sub is checked first
12
+ bool is_sub; // = last_prefix.start_with? prefix
13
+ char* prefix;
14
+ long prefix_len;
15
+ regex_t *suffix_re;
16
+ VALUE controller;
17
+ VALUE id; // symbol
18
+ VALUE accept_exts; // {ext => true}
19
+ VALUE accept_mimes; // [[m1, m2, ext]]
20
+ std::vector<ID> conv;
21
+ VALUE scope;
22
+ char* suffix; // only for inspect
23
+ long suffix_len;
24
+
25
+ // don't make it destructor, or it could be called twice if on stack
26
+ void dealloc() {
27
+ if (prefix) {
28
+ xfree(prefix);
29
+ prefix = NULL;
30
+ }
31
+ if (suffix_re) {
32
+ onig_free(suffix_re);
33
+ suffix_re = NULL;
34
+ }
35
+ if (suffix) {
36
+ xfree(suffix);
37
+ suffix = NULL;
38
+ }
39
+ }
40
+ };
41
+
42
+ typedef std::vector<RouteEntry> RouteEntries;
43
+ static std::map<enum http_method, RouteEntries*> route_map;
44
+ static OnigRegion region; // we can reuse the region without worrying thread safety
45
+ static ID id_to_s;
46
+ static rb_encoding* u8_enc;
47
+ static VALUE str_html;
48
+ static VALUE nyara_http_methods;
49
+
50
+ static bool start_with(const char* a, long a_len, const char* b, long b_len) {
51
+ if (b_len > a_len) {
52
+ return false;
53
+ }
54
+ for (size_t i = 0; i < b_len; i++) {
55
+ if (a[i] != b[i]) {
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ }
61
+
62
+ static enum http_method canonicalize_http_method(VALUE m) {
63
+ VALUE method_num;
64
+ if (TYPE(m) == T_STRING) {
65
+ method_num = rb_hash_aref(nyara_http_methods, m);
66
+ } else {
67
+ method_num = m;
68
+ }
69
+ Check_Type(method_num, T_FIXNUM);
70
+ return (enum http_method)FIX2INT(method_num);
71
+ }
72
+
73
+ static VALUE ext_clear_route(VALUE req) {
74
+ for (auto i = route_map.begin(); i != route_map.end(); ++i) {
75
+ RouteEntries* entries = i->second;
76
+ for (auto j = entries->begin(); j != entries->end(); ++j) {
77
+ j->dealloc();
78
+ }
79
+ delete entries;
80
+ }
81
+ route_map.clear();
82
+ return Qnil;
83
+ }
84
+
85
+ static VALUE ext_register_route(VALUE self, VALUE v_e) {
86
+ // get route entries
87
+ enum http_method m = canonicalize_http_method(rb_iv_get(v_e, "@http_method"));
88
+ RouteEntries* route_entries;
89
+ auto map_iter = route_map.find(m);
90
+ if (map_iter == route_map.end()) {
91
+ route_entries = new RouteEntries();
92
+ route_map[m] = route_entries;
93
+ } else {
94
+ route_entries = map_iter->second;
95
+ }
96
+
97
+ // prefix
98
+ VALUE v_prefix = rb_iv_get(v_e, "@prefix");
99
+ long prefix_len = RSTRING_LEN(v_prefix);
100
+ char* prefix = ALLOC_N(char, prefix_len);
101
+ memcpy(prefix, RSTRING_PTR(v_prefix), prefix_len);
102
+
103
+ // check if prefix is substring of last entry
104
+ bool is_sub = false;
105
+ if (route_entries->size()) {
106
+ is_sub = start_with(route_entries->rbegin()->prefix, route_entries->rbegin()->prefix_len, prefix, prefix_len);
107
+ }
108
+
109
+ // suffix
110
+ VALUE v_suffix = rb_iv_get(v_e, "@suffix");
111
+ long suffix_len = RSTRING_LEN(v_suffix);
112
+ char* suffix = ALLOC_N(char, suffix_len);
113
+ memcpy(suffix, RSTRING_PTR(v_suffix), suffix_len);
114
+ regex_t* suffix_re;
115
+ OnigErrorInfo err_info;
116
+ onig_new(&suffix_re, (const UChar*)suffix, (const UChar*)(suffix + suffix_len),
117
+ ONIG_OPTION_NONE, ONIG_ENCODING_ASCII, ONIG_SYNTAX_RUBY, &err_info);
118
+
119
+ std::vector<ID> _conv;
120
+ RouteEntry e;
121
+ e.is_sub = is_sub;
122
+ e.prefix = prefix;
123
+ e.prefix_len = prefix_len;
124
+ e.suffix_re = suffix_re;
125
+ e.suffix = suffix;
126
+ e.suffix_len = suffix_len;
127
+ e.controller = rb_iv_get(v_e, "@controller");
128
+ e.id = rb_iv_get(v_e, "@id");
129
+ e.conv = _conv;
130
+ e.scope = rb_iv_get(v_e, "@scope");
131
+
132
+ // conv
133
+ VALUE v_conv = rb_iv_get(v_e, "@conv");
134
+ VALUE* conv_ptr = RARRAY_PTR(v_conv);
135
+ long conv_len = RARRAY_LEN(v_conv);
136
+ if (onig_number_of_captures(suffix_re) != conv_len) {
137
+ e.dealloc();
138
+ rb_raise(rb_eRuntimeError, "number of captures mismatch");
139
+ }
140
+ for (long i = 0; i < conv_len; i++) {
141
+ ID conv_id = SYM2ID(conv_ptr[i]);
142
+ e.conv.push_back(conv_id);
143
+ }
144
+
145
+ // accept
146
+ e.accept_exts = rb_iv_get(v_e, "@accept_exts");
147
+ e.accept_mimes = rb_iv_get(v_e, "@accept_mimes");
148
+
149
+ route_entries->push_back(e);
150
+ return Qnil;
151
+ }
152
+
153
+ static VALUE ext_list_route(VALUE self) {
154
+ // note: prevent leak with init nil
155
+ volatile VALUE arr = Qnil;
156
+ volatile VALUE e = Qnil;
157
+ volatile VALUE prefix = Qnil;
158
+ volatile VALUE conv = Qnil;
159
+
160
+ volatile VALUE route_hash = rb_hash_new();
161
+ for (auto j = route_map.begin(); j != route_map.end(); j++) {
162
+ RouteEntries* route_entries = j->second;
163
+ VALUE arr = rb_ary_new();
164
+ rb_hash_aset(route_hash, rb_str_new2(http_method_str(j->first)), arr);
165
+ for (auto i = route_entries->begin(); i != route_entries->end(); i++) {
166
+ e = rb_ary_new();
167
+ rb_ary_push(e, i->is_sub ? Qtrue : Qfalse);
168
+ rb_ary_push(e, i->scope);
169
+ rb_ary_push(e, rb_str_new(i->prefix, i->prefix_len));
170
+ rb_ary_push(e, rb_str_new(i->suffix, i->suffix_len));
171
+ rb_ary_push(e, i->controller);
172
+ rb_ary_push(e, i->id);
173
+ conv = rb_ary_new();
174
+ for (size_t j = 0; j < i->conv.size(); j++) {
175
+ rb_ary_push(conv, ID2SYM(i->conv[j]));
176
+ }
177
+ rb_ary_push(e, conv);
178
+ rb_ary_push(arr, e);
179
+ }
180
+ }
181
+ return route_hash;
182
+ }
183
+
184
+ static VALUE build_args(const char* suffix, std::vector<ID>& conv) {
185
+ volatile VALUE args = rb_ary_new();
186
+ volatile VALUE str = rb_str_new2("");
187
+ long last_len = 0;
188
+ for (size_t j = 0; j < conv.size(); j++) {
189
+ const char* capture_ptr = suffix + region.beg[j+1];
190
+ long capture_len = region.end[j+1] - region.beg[j+1];
191
+ if (conv[j] == id_to_s) {
192
+ rb_ary_push(args, rb_enc_str_new(capture_ptr, capture_len, u8_enc));
193
+ } else if (capture_len == 0) {
194
+ rb_ary_push(args, Qnil);
195
+ } else {
196
+ if (capture_len > last_len) {
197
+ RESIZE_CAPA(str, capture_len);
198
+ last_len = capture_len;
199
+ }
200
+ memcpy(RSTRING_PTR(str), capture_ptr, capture_len);
201
+ STR_SET_LEN(str, capture_len);
202
+ rb_ary_push(args, rb_funcall(str, conv[j], 0)); // hex, to_i, to_f
203
+ }
204
+ }
205
+ return args;
206
+ }
207
+
208
+ static VALUE extract_ext(const char* s, long len) {
209
+ if (s[0] != '.') {
210
+ return Qnil;
211
+ }
212
+ s++;
213
+ len--;
214
+ for (long i = 0; i < len; i++) {
215
+ if (!isalnum(s[i])) {
216
+ return Qnil;
217
+ }
218
+ }
219
+ return rb_str_new(s, len);
220
+ }
221
+
222
+ extern "C"
223
+ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
224
+ RouteResult r = {Qnil, Qnil, Qnil, Qnil};
225
+ auto map_iter = route_map.find(method_num);
226
+ if (map_iter == route_map.end()) {
227
+ return r;
228
+ }
229
+ RouteEntries* route_entries = map_iter->second;
230
+
231
+ const char* path = RSTRING_PTR(vpath);
232
+ long len = RSTRING_LEN(vpath);
233
+ // must iterate all
234
+ bool last_matched = false;
235
+ auto i = route_entries->begin();
236
+ for (; i != route_entries->end(); ++i) {
237
+ bool matched;
238
+ if (i->is_sub && last_matched) { // save a bit compare
239
+ matched = last_matched;
240
+ } else {
241
+ matched = start_with(path, len, i->prefix, i->prefix_len);
242
+ }
243
+ last_matched = matched;
244
+
245
+ if (/* prefix */ matched) {
246
+ const char* suffix = path + i->prefix_len;
247
+ long suffix_len = len - i->prefix_len;
248
+ if (i->suffix_len == 0) {
249
+ if (suffix_len) {
250
+ r.ext = extract_ext(suffix, suffix_len);
251
+ if (r.ext == Qnil) {
252
+ break;
253
+ }
254
+ }
255
+ r.args = rb_ary_new3(1, i->id);
256
+ r.controller = i->controller;
257
+ break;
258
+ } else {
259
+ long matched_len = onig_match(i->suffix_re, (const UChar*)suffix, (const UChar*)(suffix + suffix_len),
260
+ (const UChar*)suffix, &region, 0);
261
+ if (matched_len > 0) {
262
+ if (matched_len < suffix_len) {
263
+ r.ext = extract_ext(suffix + matched_len, suffix_len);
264
+ if (r.ext == Qnil) {
265
+ break;
266
+ }
267
+ }
268
+ r.args = build_args(suffix, i->conv);
269
+ rb_ary_push(r.args, i->id);
270
+ r.controller = i->controller;
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ if (r.controller != Qnil) {
278
+ r.scope = i->scope;
279
+
280
+ if (r.ext == Qnil) {
281
+ if (i->accept_exts == Qnil) {
282
+ r.ext = str_html;
283
+ } else {
284
+ // NOTE maybe rejected
285
+ r.ext = i->accept_mimes;
286
+ }
287
+ } else {
288
+ if (i->accept_exts != Qnil) {
289
+ if (!RTEST(rb_hash_aref(i->accept_exts, r.ext))) {
290
+ r.controller = Qnil; // reject if ext mismatch
291
+ }
292
+ }
293
+ }
294
+ }
295
+ return r;
296
+ }
297
+
298
+ static VALUE ext_lookup_route(VALUE self, VALUE method, VALUE path) {
299
+ enum http_method method_num = canonicalize_http_method(method);
300
+ volatile RouteResult r = nyara_lookup_route(method_num, path);
301
+ volatile VALUE a = rb_ary_new();
302
+ rb_ary_push(a, r.scope);
303
+ rb_ary_push(a, r.controller);
304
+ rb_ary_push(a, r.args);
305
+ rb_ary_push(a, r.ext);
306
+ return a;
307
+ }
308
+
309
+ extern "C"
310
+ void Init_route(VALUE nyara, VALUE ext) {
311
+ nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
312
+ id_to_s = rb_intern("to_s");
313
+ u8_enc = rb_utf8_encoding();
314
+ str_html = rb_str_new2("html");
315
+ OBJ_FREEZE(str_html);
316
+ rb_gc_register_mark_object(str_html);
317
+ onig_region_init(&region);
318
+
319
+ rb_define_singleton_method(ext, "register_route", RUBY_METHOD_FUNC(ext_register_route), 1);
320
+ rb_define_singleton_method(ext, "clear_route", RUBY_METHOD_FUNC(ext_clear_route), 0);
321
+
322
+ // for test
323
+ rb_define_singleton_method(ext, "list_route", RUBY_METHOD_FUNC(ext_list_route), 0);
324
+ rb_define_singleton_method(ext, "lookup_route", RUBY_METHOD_FUNC(ext_lookup_route), 2);
325
+ }
data/ext/url_encoded.c ADDED
@@ -0,0 +1,304 @@
1
+ // parse path / query / url-encoded body
2
+ #include "nyara.h"
3
+ #include <ruby/encoding.h>
4
+
5
+ static rb_encoding* u8_encoding;
6
+
7
+ static char _half_octet(char c) {
8
+ // there's a faster way but not validating the range:
9
+ // #define hex2c(c) ((c | 32) % 39 - 9)
10
+ if (c >= '0' && c <= '9') {
11
+ return c - '0';
12
+ } else if (c >= 'A' && c <= 'F') {
13
+ return c - 'A' + 10;
14
+ } else if (c >= 'a' && c <= 'f') {
15
+ return c - 'a' + 10;
16
+ } else {
17
+ return -1;
18
+ }
19
+ }
20
+
21
+ static size_t _decode_url_seg(VALUE path, const char*s, size_t len, char stop_char) {
22
+ const char* last_s = s;
23
+ long last_len = 0;
24
+
25
+ # define FLUSH_UNESCAPED\
26
+ if (last_len) {\
27
+ rb_str_cat(path, last_s, last_len);\
28
+ last_s += last_len;\
29
+ last_len = 0;\
30
+ }
31
+
32
+ size_t i;
33
+ for (i = 0; i < len; i++) {
34
+ if (s[i] == '%') {
35
+ if (i + 2 >= len) {
36
+ last_len++;
37
+ continue;
38
+ }
39
+ char r1 = _half_octet(s[i + 1]);
40
+ if (r1 < 0) {
41
+ last_len++;
42
+ continue;
43
+ }
44
+ char r2 = _half_octet(s[i + 2]);
45
+ if (r2 < 0) {
46
+ last_len++;
47
+ continue;
48
+ }
49
+ i += 2;
50
+ unsigned char r = ((unsigned char)r1 << 4) | (unsigned char)r2;
51
+ FLUSH_UNESCAPED;
52
+ last_s += 3;
53
+ rb_str_cat(path, (char*)&r, 1);
54
+
55
+ } else if (s[i] == stop_char) {
56
+ i++;
57
+ break;
58
+
59
+ } else if (s[i] == '+') {
60
+ FLUSH_UNESCAPED;
61
+ rb_str_cat(path, " ", 1);
62
+
63
+ } else {
64
+ last_len++;
65
+ }
66
+ }
67
+ FLUSH_UNESCAPED;
68
+ # undef FLUSH_UNESCAPED
69
+
70
+ return i;
71
+ }
72
+
73
+ // return parsed len, s + return == start of query
74
+ size_t nyara_parse_path(VALUE output, const char* s, size_t len) {
75
+ return _decode_url_seg(output, s, len, '?');
76
+ }
77
+
78
+ static VALUE ext_parse_path(VALUE self, VALUE output, VALUE input) {
79
+ size_t parsed = nyara_parse_path(output, RSTRING_PTR(input), RSTRING_LEN(input));
80
+ return ULONG2NUM(parsed);
81
+ }
82
+
83
+ static void _error(const char* msg, const char* s, long len, long segment_i) {
84
+ rb_raise(rb_eRuntimeError,
85
+ "error parsing \"%.*s\": segments[%ld] is %s",
86
+ (int)len, s, segment_i, msg);
87
+ }
88
+
89
+ static VALUE _new_child(long hash) {
90
+ return hash ? rb_class_new_instance(0, NULL, nyara_param_hash_class) : rb_ary_new();
91
+ }
92
+
93
+ // a, b, c = keys; h[a][b][c] = value
94
+ // the last 2 args are for error report
95
+ static void _aset_keys(VALUE output, VALUE keys, VALUE value, const char* kv_s, long kv_len) {
96
+ VALUE* arr = RARRAY_PTR(keys);
97
+ long len = RARRAY_LEN(keys);
98
+ if (!len) {
99
+ rb_bug("bug: aset 0 length key");
100
+ return;
101
+ }
102
+
103
+ // first key seg
104
+ volatile VALUE key = arr[0];
105
+ long is_hash_key = 1;
106
+
107
+ // middle key segs
108
+ for (long i = 0; i < len - 1; i++) {
109
+ key = arr[i];
110
+ long next_is_hash_key = RSTRING_LEN(arr[i + 1]);
111
+ if (is_hash_key) {
112
+ if (nyara_rb_hash_has_key(output, key)) {
113
+ output = rb_hash_aref(output, key);
114
+ if (next_is_hash_key) {
115
+ if (TYPE(output) != T_HASH) {
116
+ // note: StringValueCStr requires VALUE* as param, and can raise another error if there's nul in the string
117
+ _error("not array index (expect to be empty)", kv_s, kv_len, i);
118
+ }
119
+ } else {
120
+ if (TYPE(output) != T_ARRAY) {
121
+ _error("not hash key (expect to be non-empty)", kv_s, kv_len, i);
122
+ }
123
+ }
124
+ } else {
125
+ volatile VALUE child = _new_child(next_is_hash_key);
126
+ rb_hash_aset(output, key, child);
127
+ output = child;
128
+ }
129
+ } else {
130
+ volatile VALUE child = _new_child(next_is_hash_key);
131
+ rb_ary_push(output, child);
132
+ output = child;
133
+ }
134
+ is_hash_key = next_is_hash_key;
135
+ }
136
+
137
+ // terminate key seg: add value
138
+ key = arr[len - 1];
139
+ if (is_hash_key) {
140
+ rb_hash_aset(output, key, value);
141
+ } else {
142
+ rb_ary_push(output, value);
143
+ }
144
+ }
145
+
146
+ static const char* _strnchr(const char* s, long len, char c) {
147
+ for (long i = 0; i < len; i++) {
148
+ if (s[i] == c) {
149
+ return s + i;
150
+ }
151
+ }
152
+ return NULL;
153
+ }
154
+
155
+ static inline VALUE _new_blank_str() {
156
+ return rb_enc_str_new("", 0, u8_encoding);
157
+ }
158
+
159
+ static void _url_encoded_seg(VALUE output, const char* kv_s, long kv_len, int nested_mode) {
160
+ // (note if we _decode_url_seg with '&' first, then there may be multiple '='s in one kv)
161
+ const char* s = kv_s;
162
+ long len = kv_len;
163
+ if (!len) {
164
+ return;
165
+ }
166
+
167
+ volatile VALUE value = _new_blank_str();
168
+
169
+ // rule out the value part
170
+ {
171
+ // strnstr is not available on linux :(
172
+ const char* value_s = _strnchr(s, len, '=');
173
+ if (value_s) {
174
+ value_s++;
175
+ long value_len = s + len - value_s;
176
+ long parsed = _decode_url_seg(value, value_s, value_len, '&');
177
+ if (parsed != value_len) {
178
+ rb_raise(rb_eArgError, "separator & in param segment");
179
+ }
180
+ len = value_s - s - 1;
181
+ }
182
+ // starts with '='
183
+ if (value_s == s) {
184
+ rb_hash_aset(output, _new_blank_str(), value);
185
+ return;
186
+ }
187
+ }
188
+
189
+ volatile VALUE key = _new_blank_str();
190
+ if (nested_mode) {
191
+ // todo fault-tolerant?
192
+ long parsed = _decode_url_seg(key, s, len, '[');
193
+ if (parsed == len) {
194
+ rb_hash_aset(output, key, value);
195
+ return;
196
+ }
197
+ s += parsed;
198
+ len -= parsed;
199
+ volatile VALUE keys = rb_ary_new3(1, key);
200
+ while (len) {
201
+ key = _new_blank_str();
202
+ parsed = _decode_url_seg(key, s, len, ']');
203
+ rb_ary_push(keys, key);
204
+ s += parsed;
205
+ len -= parsed;
206
+ if (len) {
207
+ if (s[0] == '[') {
208
+ s++;
209
+ len--;
210
+ } else {
211
+ rb_raise(rb_eRuntimeError, "malformed params: remaining chars in key but not starting with '['");
212
+ return;
213
+ }
214
+ }
215
+ }
216
+ _aset_keys(output, keys, value, kv_s, kv_len);
217
+ } else {
218
+ _decode_url_seg(key, s, len, '=');
219
+ rb_hash_aset(output, key, value);
220
+ }
221
+
222
+ return;
223
+ }
224
+
225
+ static VALUE ext_parse_url_encoded_seg(VALUE self, VALUE output, VALUE kv, VALUE v_nested_mode) {
226
+ _url_encoded_seg(output, RSTRING_PTR(kv), RSTRING_LEN(kv), RTEST(v_nested_mode));
227
+ return output;
228
+ }
229
+
230
+ void nyara_parse_param(VALUE output, const char* s, size_t len) {
231
+ // split with /[&;] */
232
+ size_t last_i = 0;
233
+ size_t i = 0;
234
+ for (; i < len; i++) {
235
+ if (s[i] == '&' || s[i] == ';') {
236
+ if (i > last_i) {
237
+ _url_encoded_seg(output, s + last_i, i - last_i, 1);
238
+ }
239
+ while(i + 1 < len && s[i + 1] == ' ') {
240
+ i++;
241
+ }
242
+ last_i = i + 1;
243
+ }
244
+ }
245
+ if (i > last_i) {
246
+ _url_encoded_seg(output, s + last_i, i - last_i, 1);
247
+ }
248
+ }
249
+
250
+ static VALUE ext_parse_param(VALUE self, VALUE output, VALUE s) {
251
+ nyara_parse_param(output, RSTRING_PTR(s), RSTRING_LEN(s));
252
+ return output;
253
+ }
254
+
255
+ static VALUE _cookie_seg_str_new(const char* s, long len) {
256
+ // trim tailing space
257
+ for (; len > 0; len--) {
258
+ if (s[len - 1] != ' ') {
259
+ break;
260
+ }
261
+ }
262
+ return rb_enc_str_new(s, len, u8_encoding);
263
+ }
264
+
265
+ static VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
266
+ volatile VALUE arr = rb_ary_new();
267
+ const char* s = RSTRING_PTR(str);
268
+ size_t len = RSTRING_LEN(str);
269
+
270
+ // split with / *[,;] */
271
+ size_t last_i = 0;
272
+ size_t i = 0;
273
+ for (; i < len; i++) {
274
+ if (s[i] == ',' || s[i] == ';') {
275
+ // char* and len parse_seg
276
+ if (i > last_i) {
277
+ rb_ary_push(arr, _cookie_seg_str_new(s + last_i, i - last_i));
278
+ }
279
+ while(i + 1 < len && s[i + 1] == ' ') {
280
+ i++;
281
+ }
282
+ last_i = i + 1;
283
+ }
284
+ }
285
+ if (i > last_i) {
286
+ rb_ary_push(arr, _cookie_seg_str_new(s + last_i, i - last_i));
287
+ }
288
+
289
+ VALUE* arr_p = RARRAY_PTR(arr);
290
+ for (long j = RARRAY_LEN(arr) - 1; j >= 0; j--) {
291
+ _url_encoded_seg(output, RSTRING_PTR(arr_p[j]), RSTRING_LEN(arr_p[j]), 0);
292
+ }
293
+ return output;
294
+ }
295
+
296
+ void Init_url_encoded(VALUE ext) {
297
+ u8_encoding = rb_utf8_encoding();
298
+
299
+ rb_define_singleton_method(ext, "parse_param", ext_parse_param, 2);
300
+ rb_define_singleton_method(ext, "parse_cookie", ext_parse_cookie, 2);
301
+ // for test
302
+ rb_define_singleton_method(ext, "parse_url_encoded_seg", ext_parse_url_encoded_seg, 3);
303
+ rb_define_singleton_method(ext, "parse_path", ext_parse_path, 2);
304
+ }
data/hello.rb ADDED
@@ -0,0 +1,5 @@
1
+ require_relative "lib/nyara"
2
+
3
+ get '/' do
4
+ send_string 'hello world'
5
+ end
@@ -0,0 +1,64 @@
1
+ module Nyara
2
+ # other options:
3
+ # - session (see also Session)
4
+ # - host
5
+ # - views
6
+ # - public
7
+ Config = ConfigHash.new
8
+ class << Config
9
+ def map prefix, controller
10
+ Route.register_controller prefix, controller
11
+ end
12
+
13
+ def port n
14
+ n = n.to_i
15
+ assert n >= 0 && n <= 65535
16
+ Config['port'] = n
17
+ end
18
+
19
+ def workers n
20
+ n = n.to_i
21
+ assert n > 0 && n < 1000
22
+ Config['workers'] = n
23
+ end
24
+
25
+ def env
26
+ self['env'].to_s
27
+ end
28
+
29
+ def development?
30
+ e = env
31
+ e.empty? or e == 'development'
32
+ end
33
+
34
+ def production?
35
+ env == 'production'
36
+ end
37
+
38
+ def test?
39
+ env == 'test'
40
+ end
41
+
42
+ alias set []=
43
+ alias get []
44
+
45
+ def assert expr
46
+ raise ArgumentError unless expr
47
+ end
48
+
49
+ # todo env aware configure
50
+ def configure &blk
51
+ instance_eval &blk
52
+ end
53
+ end
54
+ end
55
+
56
+ def configure *xs, &blk
57
+ Nyara::Config.configure *xs, &blk
58
+ end
59
+
60
+ configure do
61
+ set 'env', 'development'
62
+ set 'views', 'views'
63
+ set 'public', 'public'
64
+ end