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.
- checksums.yaml +7 -0
- data/example/design.rb +62 -0
- data/example/fib.rb +15 -0
- data/example/hello.rb +5 -0
- data/example/stream.rb +10 -0
- data/ext/accept.c +133 -0
- data/ext/event.c +89 -0
- data/ext/extconf.rb +34 -0
- data/ext/hashes.c +130 -0
- data/ext/http-parser/AUTHORS +41 -0
- data/ext/http-parser/CONTRIBUTIONS +4 -0
- data/ext/http-parser/LICENSE-MIT +23 -0
- data/ext/http-parser/contrib/parsertrace.c +156 -0
- data/ext/http-parser/contrib/url_parser.c +44 -0
- data/ext/http-parser/http_parser.c +2175 -0
- data/ext/http-parser/http_parser.h +304 -0
- data/ext/http-parser/test.c +3425 -0
- data/ext/http_parser.c +1 -0
- data/ext/inc/epoll.h +60 -0
- data/ext/inc/kqueue.h +77 -0
- data/ext/inc/status_codes.inc +64 -0
- data/ext/inc/str_intern.h +66 -0
- data/ext/inc/version.inc +1 -0
- data/ext/mime.c +107 -0
- data/ext/multipart-parser-c/README.md +18 -0
- data/ext/multipart-parser-c/multipart_parser.c +309 -0
- data/ext/multipart-parser-c/multipart_parser.h +48 -0
- data/ext/multipart_parser.c +1 -0
- data/ext/nyara.c +56 -0
- data/ext/nyara.h +59 -0
- data/ext/request.c +474 -0
- data/ext/route.cc +325 -0
- data/ext/url_encoded.c +304 -0
- data/hello.rb +5 -0
- data/lib/nyara/config.rb +64 -0
- data/lib/nyara/config_hash.rb +51 -0
- data/lib/nyara/controller.rb +336 -0
- data/lib/nyara/cookie.rb +31 -0
- data/lib/nyara/cpu_counter.rb +65 -0
- data/lib/nyara/header_hash.rb +18 -0
- data/lib/nyara/mime_types.rb +612 -0
- data/lib/nyara/nyara.rb +82 -0
- data/lib/nyara/param_hash.rb +5 -0
- data/lib/nyara/request.rb +144 -0
- data/lib/nyara/route.rb +138 -0
- data/lib/nyara/route_entry.rb +43 -0
- data/lib/nyara/session.rb +104 -0
- data/lib/nyara/view.rb +317 -0
- data/lib/nyara.rb +25 -0
- data/nyara.gemspec +20 -0
- data/rakefile +91 -0
- data/readme.md +35 -0
- data/spec/ext_mime_match_spec.rb +27 -0
- data/spec/ext_parse_accept_value_spec.rb +29 -0
- data/spec/ext_parse_spec.rb +138 -0
- data/spec/ext_route_spec.rb +70 -0
- data/spec/hashes_spec.rb +71 -0
- data/spec/path_helper_spec.rb +77 -0
- data/spec/request_delegate_spec.rb +67 -0
- data/spec/request_spec.rb +56 -0
- data/spec/route_entry_spec.rb +12 -0
- data/spec/route_spec.rb +84 -0
- data/spec/session_spec.rb +66 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/view_spec.rb +87 -0
- data/tools/bench-cookie.rb +22 -0
- 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, ®ion, 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(®ion);
         | 
| 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
    
    
    
        data/lib/nyara/config.rb
    ADDED
    
    | @@ -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
         |