nyara 0.0.1.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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