nyara 0.0.1.pre → 0.0.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb0049e820e49ff09f09423ecce778a56834fd82
4
- data.tar.gz: 30a2651c518f2f7c3db0581396b52ef1655c4ce8
3
+ metadata.gz: 924fd91c47d5907d59fac0bef09fa645ed552188
4
+ data.tar.gz: f3f74e666325973cc8a1938eb91ce9a91102ed99
5
5
  SHA512:
6
- metadata.gz: b43a204aa71c4a3b1f2a94b21ee87be98d0e78348e93d9ba7439df27cb383cced9d1135e3ac1ce47e6bd7479d8542456360ce1c297399eaa77cfcce41871ea79
7
- data.tar.gz: 955570020bccc660c5d7ae82ecad85c821988f92ebc80b4beffaa84575a1b487ccecf07be51d6041c7a88af62565e6a61260bec11009dacaf0c88814decedca7
6
+ metadata.gz: 88025bd5ad58a09e1c53e239572dbbc78039a5f93de7e52475074f4da6415f6616c39b59235f24fb8e8f30bd66e3530e66f48829369edab085ea139edd837d36
7
+ data.tar.gz: 8e602eb1355aeb7a4abd81f9e7bfe5cb586f3d697eecfaba5546ae40f5aa7d6e119ea93d1937198c2805ca5bb36825cc73cae93592cb5b844a759f244b27734e
data/ext/accept.c CHANGED
@@ -45,7 +45,7 @@ static void qarray_delete(QArray* qa) {
45
45
  xfree(qa->qs);
46
46
  }
47
47
 
48
- static VALUE trim_space(VALUE str) {
48
+ static VALUE trim_space(volatile VALUE str) {
49
49
  long olen = RSTRING_LEN(str);
50
50
  str = rb_str_new(RSTRING_PTR(str), olen);
51
51
  char* s = RSTRING_PTR(str);
@@ -105,7 +105,7 @@ static void parse_seg(const char* s, long len, VALUE out, QArray* qa) {
105
105
  rb_ary_store(out, pos, rb_str_new(s, len));
106
106
  }
107
107
 
108
- VALUE ext_parse_accept_value(VALUE _, VALUE str) {
108
+ VALUE ext_parse_accept_value(VALUE _, volatile VALUE str) {
109
109
  if (str == Qnil) {
110
110
  return rb_ary_new();
111
111
  }
data/ext/hashes.c CHANGED
@@ -66,9 +66,10 @@ void nyara_headerlize(VALUE str) {
66
66
  static VALUE header_hash_tidy_key(VALUE key) {
67
67
  if (TYPE(key) == T_SYMBOL) {
68
68
  key = rb_sym_to_s(key);
69
+ } else {
70
+ Check_Type(key, T_STRING);
71
+ key = rb_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
69
72
  }
70
- Check_Type(key, T_STRING);
71
- key = rb_str_new(RSTRING_PTR(key), RSTRING_LEN(key));
72
73
  nyara_headerlize(key);
73
74
  return key;
74
75
  }
data/ext/mime.c CHANGED
@@ -12,7 +12,9 @@ static const char* _strnchr(const char* s, long len, char c) {
12
12
  return NULL;
13
13
  }
14
14
 
15
- static bool mime_match_seg(const char* m1_ptr, long m1_len, VALUE v1, VALUE v2) {
15
+ // m1, m2: request
16
+ // v1, v2: action
17
+ static bool _mime_match_seg(const char* m1_ptr, long m1_len, VALUE v1, VALUE v2) {
16
18
  const char* m2_ptr = _strnchr(m1_ptr, m1_len, '/');
17
19
  long m2_len;
18
20
  if (m2_ptr) {
@@ -31,21 +33,6 @@ static bool mime_match_seg(const char* m1_ptr, long m1_len, VALUE v1, VALUE v2)
31
33
  # define EQL_STAR(s, len) (len == 1 && s[0] == '*')
32
34
  # define EQL(s1, len1, s2, len2) (len1 == len2 && strncmp(s1, s2, len1) == 0)
33
35
 
34
- /*
35
- if m1 == '*'
36
- if m2.nil? || m2 == '*'
37
- return true
38
- elsif m2 == v2
39
- return true
40
- else
41
- return false
42
- end
43
- end
44
- return false if v1 != m1
45
- return true if m2.nil? || m2 == '*'
46
- m2 == v2
47
- */
48
-
49
36
  if (EQL_STAR(m1_ptr, m1_len)) {
50
37
  if (m2_len == 0 || EQL_STAR(m2_ptr, m2_len)) {
51
38
  return true;
@@ -69,30 +56,32 @@ static bool mime_match_seg(const char* m1_ptr, long m1_len, VALUE v1, VALUE v2)
69
56
 
70
57
  // for test
71
58
  static VALUE ext_mime_match_seg(VALUE self, VALUE m, VALUE v1, VALUE v2) {
72
- if (mime_match_seg(RSTRING_PTR(m), RSTRING_LEN(m), v1, v2)) {
59
+ if (_mime_match_seg(RSTRING_PTR(m), RSTRING_LEN(m), v1, v2)) {
73
60
  return Qtrue;
74
61
  } else {
75
62
  return Qfalse;
76
63
  }
77
64
  }
78
65
 
79
- // returns matched ext or nil
80
- VALUE ext_mime_match(VALUE self, VALUE request_accept, VALUE accept_mimes) {
66
+ // +request_accept+ is an array of mime types
67
+ // +action_accept+ is an array of split mime type and format, e.g. +[['application', 'javascript', 'js']]+
68
+ // returns matched format or nil
69
+ VALUE ext_mime_match(VALUE self, VALUE request_accept, VALUE action_accept) {
81
70
  Check_Type(request_accept, T_ARRAY);
82
- Check_Type(accept_mimes, T_ARRAY);
71
+ Check_Type(action_accept, T_ARRAY);
83
72
 
84
- VALUE* accepts = RARRAY_PTR(request_accept);
85
- long accepts_len = RARRAY_LEN(request_accept);
86
- VALUE* values = RARRAY_PTR(accept_mimes);
87
- long values_len = RARRAY_LEN(accept_mimes);
73
+ VALUE* requests = RARRAY_PTR(request_accept);
74
+ long requests_len = RARRAY_LEN(request_accept);
75
+ VALUE* values = RARRAY_PTR(action_accept);
76
+ long values_len = RARRAY_LEN(action_accept);
88
77
 
89
- for (long j = 0; j < accepts_len; j++) {
90
- char* s = RSTRING_PTR(accepts[j]);
91
- long len = RSTRING_LEN(accepts[j]);
78
+ for (long j = 0; j < requests_len; j++) {
79
+ char* s = RSTRING_PTR(requests[j]);
80
+ long len = RSTRING_LEN(requests[j]);
92
81
  for (long i = 0; i < values_len; i++) {
93
82
  Check_Type(values[i], T_ARRAY);
94
83
  VALUE* arr = RARRAY_PTR(values[i]);
95
- if (mime_match_seg(s, len, arr[0], arr[1])) {
84
+ if (_mime_match_seg(s, len, arr[0], arr[1])) {
96
85
  return arr[2];
97
86
  }
98
87
  }
@@ -23,8 +23,8 @@ static void multipart_log(const char * format, ...)
23
23
 
24
24
  #define NOTIFY_CB(FOR) \
25
25
  do { \
26
- if (p->settings->on_##FOR) { \
27
- if (p->settings->on_##FOR(p) != 0) { \
26
+ if (p->settings->on_##FOR) { \
27
+ if (p->settings->on_##FOR(p) != 0) { \
28
28
  return i; \
29
29
  } \
30
30
  } \
@@ -114,7 +114,7 @@ size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len
114
114
  char c, cl;
115
115
  int is_last = 0;
116
116
 
117
- while(!is_last) {
117
+ while(i < len) {
118
118
  c = buf[i];
119
119
  is_last = (i == (len - 1));
120
120
  switch (p->state) {
data/ext/nyara.h CHANGED
@@ -20,8 +20,8 @@ void nyara_handle_request(int fd);
20
20
 
21
21
  /* -- url encoded parse -- */
22
22
  void Init_url_encoded(VALUE ext);
23
- size_t nyara_parse_path(VALUE path, const char*s, size_t len);
24
- void nyara_parse_param(VALUE output, const char* s, size_t len);
23
+ long nyara_parse_path(VALUE path, const char*s, long len);
24
+ void nyara_parse_param(VALUE output, const char* s, long len);
25
25
 
26
26
 
27
27
  /* -- accept parse -- */
@@ -52,8 +52,8 @@ typedef struct {
52
52
  VALUE controller;
53
53
  VALUE args;
54
54
  VALUE scope;
55
- VALUE ext; // maybe string or map
55
+ VALUE format; // string, path extension or matched ext in config
56
56
  } RouteResult;
57
57
 
58
58
  extern void Init_route(VALUE nyara, VALUE ext);
59
- extern RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath);
59
+ extern RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath, VALUE accept_arr);
data/ext/request.c CHANGED
@@ -11,14 +11,15 @@ typedef struct {
11
11
  multipart_parser* mparser;
12
12
  enum http_method method;
13
13
  VALUE header;
14
+ VALUE accept; // mime array sorted with q
15
+ VALUE format; // string ext without dot
14
16
  VALUE fiber;
15
17
  VALUE scope; // mapped prefix
18
+ VALUE path_with_query;
16
19
  VALUE path;
17
- VALUE param;
20
+ VALUE query;
18
21
  VALUE last_field;
19
22
  VALUE last_value;
20
- // string if determined by url, or hash if need further check with header[Accept]
21
- VALUE ext;
22
23
  VALUE self;
23
24
  int fd;
24
25
 
@@ -44,7 +45,7 @@ static VALUE fd_request_map;
44
45
  #define MAX_RECEIVE_DATA 65536
45
46
  static char received_data[MAX_RECEIVE_DATA];
46
47
 
47
- static VALUE fiber_func(VALUE _, VALUE args) {
48
+ static VALUE _fiber_func(VALUE _, VALUE args) {
48
49
  VALUE instance = rb_ary_pop(args);
49
50
  VALUE meth = rb_ary_pop(args);
50
51
  rb_apply(instance, SYM2ID(meth), args);
@@ -66,49 +67,16 @@ static inline void _close_fd(int fd) {
66
67
  close(fd);
67
68
  }
68
69
 
69
- // fixme assume url is always sent as whole (tcp window is large!)
70
70
  static int on_url(http_parser* parser, const char* s, size_t len) {
71
71
  Request* p = (Request*)parser;
72
72
  p->method = parser->method;
73
73
 
74
- // matching raw path is bad idea, for example: %a0 and %A0 are different strings but same route
75
- p->path = rb_enc_str_new("", 0, u8_encoding);
76
- size_t query_i = nyara_parse_path(p->path, s, len);
77
- p->param = rb_class_new_instance(0, NULL, nyara_param_hash_class);
78
- if (query_i < len) {
79
- nyara_parse_param(p->param, s + query_i, len - query_i);
80
-
81
- // do method override with _method=xxx in query
82
- if (p->method == HTTP_POST) {
83
- VALUE meth = rb_hash_aref(p->param, method_override_key);
84
- if (TYPE(meth) == T_STRING) {
85
- _upcase_method(meth);
86
- VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
87
- if (meth_num != Qnil) {
88
- p->method = FIX2INT(meth_num);
89
- }
90
- }
91
- }
92
- }
93
-
94
- volatile RouteResult result = nyara_lookup_route(p->method, p->path);
95
- if (RTEST(result.controller)) {
96
- {
97
- VALUE instance = rb_class_new_instance(1, &(p->self), result.controller);
98
- rb_ary_push(result.args, instance);
99
- }
100
- // result.args is on stack, no need to worry gc
101
- p->fiber = rb_fiber_new(fiber_func, result.args);
102
- p->scope = result.scope;
103
- p->header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
104
- p->ext = result.ext;
105
- p->response_header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
106
- p->response_header_extra_lines = rb_ary_new();
107
- return 0;
74
+ if (p->path_with_query == Qnil) {
75
+ p->path_with_query = rb_str_new(s, len);
108
76
  } else {
109
- rb_funcall(p->self, id_not_found, 0);
110
- return 1;
77
+ rb_str_cat(p->path_with_query, s, len);
111
78
  }
79
+ return 0;
112
80
  }
113
81
 
114
82
  static int on_message_complete(http_parser* parser) {
@@ -160,25 +128,48 @@ static int on_header_value(http_parser* parser, const char* s, size_t len) {
160
128
  return 0;
161
129
  }
162
130
 
131
+ // may override POST by _method in query
132
+ static void _parse_path_and_query(Request* p) {
133
+ char* s = RSTRING_PTR(p->path_with_query);
134
+ long len = RSTRING_LEN(p->path_with_query);
135
+ long query_i = nyara_parse_path(p->path, s, len);
136
+ if (query_i < len) {
137
+ nyara_parse_param(p->query, s + query_i, len - query_i);
138
+
139
+ // do method override with _method=xxx in query
140
+ if (p->method == HTTP_POST) {
141
+ VALUE meth = rb_hash_aref(p->query, method_override_key);
142
+ if (TYPE(meth) == T_STRING) {
143
+ _upcase_method(meth);
144
+ VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
145
+ if (meth_num != Qnil) {
146
+ p->method = FIX2INT(meth_num);
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+
163
153
  static int on_headers_complete(http_parser* parser) {
164
154
  Request* p = (Request*)parser;
165
155
  p->last_field = Qnil;
166
156
  p->last_value = Qnil;
167
157
 
168
- if (TYPE(p->ext) != T_STRING) {
169
- VALUE accept = rb_hash_aref(p->header, str_accept);
170
- if (RTEST(accept)) {
171
- accept = ext_parse_accept_value(Qnil, accept);
172
- rb_iv_set(p->self, "@accept", accept);
173
- p->ext = ext_mime_match(Qnil, accept, p->ext);
174
- }
175
- if (p->ext == Qnil) {
176
- rb_funcall(p->self, id_not_found, 0);
177
- return 1;
178
- }
158
+ _parse_path_and_query(p);
159
+ p->accept = ext_parse_accept_value(Qnil, rb_hash_aref(p->header, str_accept));
160
+ volatile RouteResult result = nyara_lookup_route(p->method, p->path, p->accept);
161
+ if (RTEST(result.controller)) {
162
+ rb_ary_push(result.args, rb_class_new_instance(1, &(p->self), result.controller));
163
+ // result.args is on stack, no need to worry gc
164
+ p->fiber = rb_fiber_new(_fiber_func, result.args);
165
+ p->scope = result.scope;
166
+ p->format = result.format;
167
+ p->response_header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
168
+ p->response_header_extra_lines = rb_ary_new();
169
+ return 0;
179
170
  }
180
-
181
- return 0;
171
+ rb_funcall(p->self, id_not_found, 0);
172
+ return 1;
182
173
  }
183
174
 
184
175
  static http_parser_settings request_settings = {
@@ -196,13 +187,15 @@ static void request_mark(void* pp) {
196
187
  Request* p = pp;
197
188
  if (p) {
198
189
  rb_gc_mark_maybe(p->header);
190
+ rb_gc_mark_maybe(p->accept);
191
+ rb_gc_mark_maybe(p->format);
199
192
  rb_gc_mark_maybe(p->fiber);
200
193
  rb_gc_mark_maybe(p->scope);
194
+ rb_gc_mark_maybe(p->path_with_query);
201
195
  rb_gc_mark_maybe(p->path);
202
- rb_gc_mark_maybe(p->param);
196
+ rb_gc_mark_maybe(p->query);
203
197
  rb_gc_mark_maybe(p->last_field);
204
198
  rb_gc_mark_maybe(p->last_value);
205
- rb_gc_mark_maybe(p->ext);
206
199
  rb_gc_mark_maybe(p->response_content_type);
207
200
  rb_gc_mark_maybe(p->response_header);
208
201
  rb_gc_mark_maybe(p->response_header_extra_lines);
@@ -222,15 +215,20 @@ static void request_free(void* pp) {
222
215
  static Request* request_alloc() {
223
216
  Request* p = ALLOC(Request);
224
217
  http_parser_init(&(p->hparser), HTTP_REQUEST);
218
+ volatile VALUE header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
219
+ volatile VALUE path = rb_enc_str_new("", 0, u8_encoding);
220
+ volatile VALUE query = rb_class_new_instance(0, NULL, nyara_param_hash_class);
225
221
  p->mparser = NULL;
226
- p->header = Qnil;
222
+ p->header = header;
223
+ p->accept = Qnil;
224
+ p->format = Qnil;
227
225
  p->fiber = Qnil;
228
226
  p->scope = Qnil;
229
- p->path = Qnil;
230
- p->param = Qnil;
227
+ p->path_with_query = Qnil;
228
+ p->path = path;
229
+ p->query = query;
231
230
  p->last_field = Qnil;
232
231
  p->last_value = Qnil;
233
- p->ext = Qnil;
234
232
  p->fd = 0;
235
233
  p->status = 200;
236
234
  p->response_content_type = Qnil;
@@ -248,8 +246,8 @@ static VALUE request_alloc_func(VALUE klass) {
248
246
  invoke order:
249
247
  - find/create request
250
248
  - http_parser_execute
251
- - on_url
252
- - on_message_complete
249
+ - on_headers_complete => 404 or create request
250
+ - on_message_complete => run action
253
251
  */
254
252
  void nyara_handle_request(int fd) {
255
253
  Request* p = NULL;
@@ -311,9 +309,24 @@ static VALUE request_path(VALUE self) {
311
309
  return p->path;
312
310
  }
313
311
 
314
- static VALUE request_matched_accept(VALUE _, VALUE self) {
312
+ static VALUE request_query(VALUE self) {
315
313
  P;
316
- return p->ext == Qnil ? str_html : p->ext;
314
+ return p->query;
315
+ }
316
+
317
+ static VALUE request_path_with_query(VALUE self) {
318
+ P;
319
+ return p->path_with_query;
320
+ }
321
+
322
+ static VALUE request_accept(VALUE self) {
323
+ P;
324
+ return p->accept;
325
+ }
326
+
327
+ static VALUE request_format(VALUE self) {
328
+ P;
329
+ return p->format == Qnil ? str_html : p->format;
317
330
  }
318
331
 
319
332
  static VALUE request_status(VALUE self) {
@@ -348,11 +361,6 @@ static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
348
361
  return n;
349
362
  }
350
363
 
351
- static VALUE ext_request_param(VALUE _, VALUE self) {
352
- P;
353
- return p->param;
354
- }
355
-
356
364
  static VALUE ext_send_data(VALUE _, VALUE self, VALUE data) {
357
365
  P;
358
366
  char* buf = RSTRING_PTR(data);
@@ -397,42 +405,37 @@ static VALUE ext_handle_request(VALUE _, VALUE v_fd) {
397
405
 
398
406
  // set internal attrs in the request object
399
407
  static VALUE ext_set_request_attrs(VALUE _, VALUE self, VALUE attrs) {
400
- # define ATTR(key) rb_hash_aref(attrs, ID2SYM(rb_intern(key)))
408
+ # define ATTR(key) rb_hash_delete(attrs, ID2SYM(rb_intern(key)))
401
409
  # define HEADER_HASH_NEW rb_class_new_instance(0, NULL, nyara_header_hash_class)
402
410
  P;
403
411
 
404
- if (ATTR("method_num") == Qnil) {
412
+ VALUE method_num = ATTR("method_num");
413
+ if (method_num == Qnil) {
405
414
  rb_raise(rb_eArgError, "bad method_num");
406
415
  }
407
-
408
- p->method = NUM2INT(ATTR("method_num"));
409
- p->path = ATTR("path");
410
- p->param = ATTR("param");
411
- p->fiber = ATTR("fiber");
412
- p->scope = ATTR("scope");
413
- p->header = RTEST(ATTR("header")) ? ATTR("header") : HEADER_HASH_NEW;
414
- p->ext = ATTR("ext");
415
- p->response_header = RTEST(ATTR("response_header")) ? ATTR("response_header") : HEADER_HASH_NEW;
416
- if (RTEST(ATTR("response_header_extra_lines"))) {
417
- p->response_header_extra_lines = ATTR("response_header_extra_lines");
418
- } else {
419
- p->response_header_extra_lines = rb_ary_new();
416
+ p->method = NUM2INT(method_num);
417
+ p->path = ATTR("path");
418
+ p->query = ATTR("query");
419
+ p->fiber = ATTR("fiber");
420
+ p->scope = ATTR("scope");
421
+ p->header = ATTR("header");
422
+ p->format = ATTR("format");
423
+ p->response_header = ATTR("response_header");
424
+ p->response_header_extra_lines = ATTR("response_header_extra_lines");
425
+
426
+ if (!RTEST(p->header)) p->header = HEADER_HASH_NEW;
427
+ if (!RTEST(p->response_header)) p->response_header = HEADER_HASH_NEW;
428
+ if (!RTEST(p->response_header_extra_lines)) p->response_header_extra_lines = rb_ary_new();
429
+
430
+ if (!RTEST(rb_funcall(attrs, rb_intern("empty?"), 0))) {
431
+ VALUE attrs_inspect = rb_funcall(attrs, rb_intern("inspect"), 0);
432
+ rb_raise(rb_eArgError, "unkown attrs: %.*s", (int)RSTRING_LEN(attrs_inspect), RSTRING_PTR(attrs_inspect));
420
433
  }
421
434
  return self;
422
435
  # undef HEADER_HASH_NEW
423
436
  # undef ATTR
424
437
  }
425
438
 
426
- // skip on_url so we can focus on testing request
427
- static VALUE ext_set_skip_on_url(VALUE _, VALUE pred) {
428
- if (RTEST(pred)) {
429
- request_settings.on_url = NULL;
430
- } else {
431
- request_settings.on_url = on_url;
432
- }
433
- return Qnil;
434
- }
435
-
436
439
  void Init_request(VALUE nyara, VALUE ext) {
437
440
  id_not_found = rb_intern("not_found");
438
441
  str_html = rb_str_new2("html");
@@ -445,7 +448,7 @@ void Init_request(VALUE nyara, VALUE ext) {
445
448
  fd_request_map = rb_hash_new();
446
449
  rb_gc_register_mark_object(fd_request_map);
447
450
  str_accept = rb_str_new2("Accept");
448
- rb_gc_register_mark_object(fd_request_map);
451
+ rb_gc_register_mark_object(str_accept);
449
452
 
450
453
  // request
451
454
  request_class = rb_define_class_under(nyara, "Request", rb_cObject);
@@ -454,7 +457,10 @@ void Init_request(VALUE nyara, VALUE ext) {
454
457
  rb_define_method(request_class, "header", request_header, 0);
455
458
  rb_define_method(request_class, "scope", request_scope, 0);
456
459
  rb_define_method(request_class, "path", request_path, 0);
457
- rb_define_method(request_class, "matched_accept", request_matched_accept, 0);
460
+ rb_define_method(request_class, "query", request_query, 0);
461
+ rb_define_method(request_class, "path_with_query", request_path_with_query, 0);
462
+ rb_define_method(request_class, "accept", request_accept, 0);
463
+ rb_define_method(request_class, "format", request_format, 0);
458
464
 
459
465
  rb_define_method(request_class, "status", request_status, 0);
460
466
  rb_define_method(request_class, "response_content_type", request_response_content_type, 0);
@@ -464,11 +470,9 @@ void Init_request(VALUE nyara, VALUE ext) {
464
470
 
465
471
  // hide internal methods in ext
466
472
  rb_define_singleton_method(ext, "request_set_status", ext_request_set_status, 2);
467
- rb_define_singleton_method(ext, "request_param", ext_request_param, 1);
468
473
  rb_define_singleton_method(ext, "send_data", ext_send_data, 2);
469
474
  rb_define_singleton_method(ext, "send_chunk", ext_send_chunk, 2);
470
475
  // for test
471
476
  rb_define_singleton_method(ext, "handle_request", ext_handle_request, 1);
472
477
  rb_define_singleton_method(ext, "set_request_attrs", ext_set_request_attrs, 2);
473
- rb_define_singleton_method(ext, "set_skip_on_url", ext_set_skip_on_url, 1);
474
478
  }
data/ext/route.cc CHANGED
@@ -6,6 +6,9 @@ extern "C" {
6
6
  #include <vector>
7
7
  #include <map>
8
8
  #include "inc/str_intern.h"
9
+ #ifndef isalnum
10
+ #include <cctype>
11
+ #endif
9
12
 
10
13
  struct RouteEntry {
11
14
  // note on order: scope is supposed to be the last, but when searching, is_sub is checked first
@@ -220,7 +223,7 @@ static VALUE extract_ext(const char* s, long len) {
220
223
  }
221
224
 
222
225
  extern "C"
223
- RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
226
+ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath, VALUE accept_arr) {
224
227
  RouteResult r = {Qnil, Qnil, Qnil, Qnil};
225
228
  auto map_iter = route_map.find(method_num);
226
229
  if (map_iter == route_map.end()) {
@@ -247,8 +250,8 @@ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
247
250
  long suffix_len = len - i->prefix_len;
248
251
  if (i->suffix_len == 0) {
249
252
  if (suffix_len) {
250
- r.ext = extract_ext(suffix, suffix_len);
251
- if (r.ext == Qnil) {
253
+ r.format = extract_ext(suffix, suffix_len);
254
+ if (r.format == Qnil) {
252
255
  break;
253
256
  }
254
257
  }
@@ -260,8 +263,8 @@ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
260
263
  (const UChar*)suffix, &region, 0);
261
264
  if (matched_len > 0) {
262
265
  if (matched_len < suffix_len) {
263
- r.ext = extract_ext(suffix + matched_len, suffix_len);
264
- if (r.ext == Qnil) {
266
+ r.format = extract_ext(suffix + matched_len, suffix_len);
267
+ if (r.format == Qnil) {
265
268
  break;
266
269
  }
267
270
  }
@@ -277,17 +280,19 @@ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
277
280
  if (r.controller != Qnil) {
278
281
  r.scope = i->scope;
279
282
 
280
- if (r.ext == Qnil) {
283
+ if (r.format == Qnil) {
281
284
  if (i->accept_exts == Qnil) {
282
- r.ext = str_html;
285
+ r.format = str_html; // not configured, just plain html
283
286
  } else {
284
- // NOTE maybe rejected
285
- r.ext = i->accept_mimes;
287
+ r.format = ext_mime_match(Qnil, accept_arr, i->accept_mimes);
288
+ if (r.format == Qnil) {
289
+ r.controller = Qnil; // reject if mime mismatch
290
+ }
286
291
  }
287
292
  } else {
288
293
  if (i->accept_exts != Qnil) {
289
- if (!RTEST(rb_hash_aref(i->accept_exts, r.ext))) {
290
- r.controller = Qnil; // reject if ext mismatch
294
+ if (!RTEST(rb_hash_aref(i->accept_exts, r.format))) {
295
+ r.controller = Qnil; // reject if path ext mismatch
291
296
  }
292
297
  }
293
298
  }
@@ -295,14 +300,14 @@ RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
295
300
  return r;
296
301
  }
297
302
 
298
- static VALUE ext_lookup_route(VALUE self, VALUE method, VALUE path) {
303
+ static VALUE ext_lookup_route(VALUE self, VALUE method, VALUE path, VALUE accept_arr) {
299
304
  enum http_method method_num = canonicalize_http_method(method);
300
- volatile RouteResult r = nyara_lookup_route(method_num, path);
305
+ volatile RouteResult r = nyara_lookup_route(method_num, path, accept_arr);
301
306
  volatile VALUE a = rb_ary_new();
302
307
  rb_ary_push(a, r.scope);
303
308
  rb_ary_push(a, r.controller);
304
309
  rb_ary_push(a, r.args);
305
- rb_ary_push(a, r.ext);
310
+ rb_ary_push(a, r.format);
306
311
  return a;
307
312
  }
308
313
 
@@ -321,5 +326,5 @@ void Init_route(VALUE nyara, VALUE ext) {
321
326
 
322
327
  // for test
323
328
  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);
329
+ rb_define_singleton_method(ext, "lookup_route", RUBY_METHOD_FUNC(ext_lookup_route), 3);
325
330
  }
data/ext/url_encoded.c CHANGED
@@ -18,7 +18,7 @@ static char _half_octet(char c) {
18
18
  }
19
19
  }
20
20
 
21
- static size_t _decode_url_seg(VALUE path, const char*s, size_t len, char stop_char) {
21
+ static long _decode_url_seg(VALUE path, const char*s, long len, char stop_char) {
22
22
  const char* last_s = s;
23
23
  long last_len = 0;
24
24
 
@@ -29,7 +29,7 @@ static size_t _decode_url_seg(VALUE path, const char*s, size_t len, char stop_ch
29
29
  last_len = 0;\
30
30
  }
31
31
 
32
- size_t i;
32
+ long i;
33
33
  for (i = 0; i < len; i++) {
34
34
  if (s[i] == '%') {
35
35
  if (i + 2 >= len) {
@@ -71,12 +71,12 @@ static size_t _decode_url_seg(VALUE path, const char*s, size_t len, char stop_ch
71
71
  }
72
72
 
73
73
  // return parsed len, s + return == start of query
74
- size_t nyara_parse_path(VALUE output, const char* s, size_t len) {
74
+ long nyara_parse_path(VALUE output, const char* s, long len) {
75
75
  return _decode_url_seg(output, s, len, '?');
76
76
  }
77
77
 
78
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));
79
+ long parsed = nyara_parse_path(output, RSTRING_PTR(input), RSTRING_LEN(input));
80
80
  return ULONG2NUM(parsed);
81
81
  }
82
82
 
@@ -227,10 +227,10 @@ static VALUE ext_parse_url_encoded_seg(VALUE self, VALUE output, VALUE kv, VALUE
227
227
  return output;
228
228
  }
229
229
 
230
- void nyara_parse_param(VALUE output, const char* s, size_t len) {
230
+ void nyara_parse_param(VALUE output, const char* s, long len) {
231
231
  // split with /[&;] */
232
- size_t last_i = 0;
233
- size_t i = 0;
232
+ long last_i = 0;
233
+ long i = 0;
234
234
  for (; i < len; i++) {
235
235
  if (s[i] == '&' || s[i] == ';') {
236
236
  if (i > last_i) {
@@ -265,11 +265,11 @@ static VALUE _cookie_seg_str_new(const char* s, long len) {
265
265
  static VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
266
266
  volatile VALUE arr = rb_ary_new();
267
267
  const char* s = RSTRING_PTR(str);
268
- size_t len = RSTRING_LEN(str);
268
+ long len = RSTRING_LEN(str);
269
269
 
270
270
  // split with / *[,;] */
271
- size_t last_i = 0;
272
- size_t i = 0;
271
+ long last_i = 0;
272
+ long i = 0;
273
273
  for (; i < len; i++) {
274
274
  if (s[i] == ',' || s[i] == ';') {
275
275
  // char* and len parse_seg
data/hello.rb CHANGED
@@ -3,3 +3,5 @@ require_relative "lib/nyara"
3
3
  get '/' do
4
4
  send_string 'hello world'
5
5
  end
6
+
7
+ # GC.stress = true
@@ -14,7 +14,7 @@ module Nyara
14
14
  action = RouteEntry.new
15
15
  action.http_method = HTTP_METHODS[method]
16
16
  action.path = path
17
- action.set_accept_exts @accept
17
+ action.set_accept_exts @formats
18
18
  action.id = @curr_id.to_sym if @curr_id
19
19
  action.blk = blk
20
20
  @route_entries << action
@@ -25,7 +25,7 @@ module Nyara
25
25
  @curr_id = nil
26
26
  @meta_exist = nil
27
27
  end
28
- @accept = nil
28
+ @formats = nil
29
29
  end
30
30
 
31
31
  # Set meta data for next action
@@ -50,7 +50,7 @@ module Nyara
50
50
 
51
51
  if opts
52
52
  # todo add opts: strong param, etag, cache-control
53
- @accept = opts[:accept]
53
+ @formats = opts[:formats]
54
54
  end
55
55
 
56
56
  @meta_exist = true
@@ -167,8 +167,8 @@ module Nyara
167
167
  scheme << host << path
168
168
  end
169
169
 
170
- def matched_accept
171
- request.matched_accept
170
+ def format
171
+ request.format
172
172
  end
173
173
 
174
174
  def header
@@ -244,7 +244,8 @@ module Nyara
244
244
  r.response_content_type ||
245
245
  header.aref_content_type ||
246
246
  (r.accept and MIME_TYPES[r.accept]) ||
247
- template_deduced_content_type
247
+ template_deduced_content_type ||
248
+ 'text/html'
248
249
 
249
250
  header.reverse_merge! OK_RESP_HEADER
250
251
 
data/lib/nyara/request.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Nyara
4
4
  # request and handler
5
5
  class Request
6
- # c-ext: http_method, scope, path, matched_accept, header
6
+ # c-ext: http_method, scope, path, query, path_with_query format, accept, header
7
7
  # status, response_content_type, response_header, response_header_extra_lines
8
8
  # todo: body, move all underline methods into Ext
9
9
 
@@ -77,10 +77,6 @@ module Nyara
77
77
  header["Requested-With"] == "XMLHttpRequest"
78
78
  end
79
79
 
80
- def accept
81
- @accept ||= Ext.parse_accept_value header['Accept']
82
- end
83
-
84
80
  def accept_language
85
81
  @accept_language ||= Ext.parse_accept_value header['Accept-Language']
86
82
  end
@@ -116,11 +112,12 @@ module Nyara
116
112
 
117
113
  def param
118
114
  @param ||= begin
119
- query_param = Ext.request_param self
115
+ q = query.dup
120
116
  if form?
121
- Ext.parse_param query_param, body
117
+ # todo read body, change encoding
118
+ Ext.parse_param q, body
122
119
  end
123
- query_param
120
+ q
124
121
  end
125
122
  end
126
123
 
data/nyara.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "nyara"
3
- s.version = "0.0.1.pre"
3
+ s.version = "0.0.1.pre.1"
4
4
  s.author = "Zete Lui"
5
5
  s.email = "nobody@example.com"
6
6
  s.homepage = "https://github.com/luikore/nyara"
data/rakefile CHANGED
@@ -4,6 +4,8 @@ Dir.chdir __dir__
4
4
 
5
5
  status_file = "ext/inc/status_codes.inc"
6
6
  version_file = "ext/inc/version.inc"
7
+ makefile = "ext/Makefile"
8
+ extconf = "ext/extconf.rb"
7
9
 
8
10
  desc "code generate"
9
11
  task :gen => [status_file, version_file]
@@ -42,13 +44,57 @@ file version_file => 'nyara.gemspec' do
42
44
  end
43
45
  end
44
46
 
47
+ desc "generate makefile"
48
+ file makefile => extconf do
49
+ Dir.chdir 'ext' do
50
+ sh 'ruby extconf.rb'
51
+ end
52
+ end
53
+
45
54
  desc "build ext"
46
- task :build do
55
+ task :build => makefile do
47
56
  Dir.chdir 'ext' do
48
57
  sh 'make'
49
58
  end
50
59
  end
51
60
 
61
+ def term_color n
62
+ print "\e[38;5;#{n}m"
63
+ end
64
+
65
+ def reset_color
66
+ print "\e[00m"
67
+ end
68
+
69
+ desc "check arity of rb_define_method/rb_define_singleton_method"
70
+ task :check_arity do
71
+ Dir.glob 'ext/*.{c,cc}' do |f|
72
+ puts "validatign #{f}"
73
+ arities = {}
74
+ data = File.read f
75
+ data.scan /^(?:static )?VALUE (\w+)\((.+)\)/ do |func, params|
76
+ arities[func] = params.count(',')
77
+ puts " scan: #{func}/#{arities[func]}"
78
+ end
79
+ data.scan /rb_define(?:_singleton)?_method\(.*?(\w+)\s*\,\s*(\d+)\)/ do |func, arity|
80
+ print " check: #{func}/#{arity} "
81
+ if arities[func].nil?
82
+ term_color 5
83
+ print "UNSCANNED"
84
+ reset_color
85
+ puts
86
+ elsif arities[func] != arity.to_i
87
+ term_color 9
88
+ print "MISMATCH #{arities[func]}"
89
+ reset_color
90
+ puts
91
+ else
92
+ puts "OK"
93
+ end
94
+ end
95
+ end
96
+ end
97
+
52
98
  desc "test"
53
99
  task :test => :build do
54
100
  sh 'rspec', '-c'
@@ -58,8 +104,10 @@ desc "build and test"
58
104
  task :default => :test
59
105
 
60
106
  desc "build and install gem"
61
- task :gem do
62
- sh 'rm', '-f', '*.gem'
107
+ task :gem => 'gen' do
108
+ Dir.glob('*.gem') do |f|
109
+ sh 'rm', f
110
+ end
63
111
  sh 'gem', 'build', 'nyara.gemspec'
64
112
  gem_package = Dir.glob('*.gem').first
65
113
  sh 'gem', 'install', '--no-rdoc', '--no-ri', gem_package
data/readme.md CHANGED
@@ -1,35 +1,96 @@
1
- git submodule update --recursive
1
+ Not Yet Another Ruby Async web framework and server. Not on rack nor rack-compatible neither eventmachine.
2
+
3
+ - Evented IO while API remains synchrony
4
+ - Prefork production server
5
+ - Sinatra-like http method and scanf-like http path and path helper
6
+ - Request format matcher with just `case ... when`
7
+ - Easy to stream the view with `Fiber.yield`
8
+
9
+ # Getting started
10
+
11
+ Requires Ruby 2.0+, BSD/Linux/Mac OS X.
12
+
13
+ Install
14
+
15
+ ```bash
16
+ gem ins --pre nyara
17
+ ```
18
+
19
+ Edit a file, name it `nyahaha.rb` for example
20
+
21
+ ```ruby
22
+ require 'nyara'
23
+ get '/' do
24
+ send_string 'hello world'
25
+ end
26
+ ```
27
+
28
+ And start server
29
+
30
+ ```bash
31
+ ruby nyahaha.rb
32
+ ```
33
+
34
+ # Build from source
35
+
36
+ After cloning
37
+
38
+ ```bash
39
+ git submodule update --init
40
+ bundle
2
41
  rake gen
3
42
  rake gem
43
+ ```
4
44
 
5
45
  # Why fast
6
46
 
7
- ## Solid http parsers written in C
47
+ ### Solid http parsers written in C
48
+
49
+ Nyara uses two evented parsers:
50
+
51
+ - [http_parser](https://github.com/joyent/http-parser) with chunked encoding support
52
+ - [multipart-parser-c](https://github.com/iafonov/multipart-parser-c) (todo)
53
+
54
+ And implemented the following in addition:
55
+
56
+ - RFC2616 compliant `Accept*` parser
57
+ - MIME type matcher
58
+ - Url-encoded parser for path / query / request body
59
+
60
+ ### Decisive routing on header complete
61
+
62
+ To support HTTP methods like PUT, DELETE for archaic browsers, a technique called **method override** is used, and the HTTP method can be overriden by a request param (usually named `_method`). In Rack the param may rest in request body, so it needs to parse the whole body before routing to a desired action. In Nyara the param is always in request path query, and routing starts when header completes. So server can do a lot of things before a huge file completely uploaded and provide better user experience.
63
+
64
+ ### Thin evented IO layer built for BSD or Linux
65
+
66
+ Nyara is only for systems with kqueue or epoll (maybe iocp in the future). Manufactural event queue is a waste of time.
67
+
68
+ ### Solve sequential problems with Fiber
69
+
70
+ The common caveats of an evented framework is: mutual callbacks must be used to ensure the order of operations. In eventmachine, sent data buffers are copied and chained in a deque to flush, and `close_connection_after_writing` must be called to ensure that all data are written before close.
71
+
72
+ While in Nyara, the data sending is much simpler: we send them directly, if there are remaining bytes, the action fiber is paused. When the socket is ready again, we wake up the fiber to continue the send action. So a lot of duplications, memory copy and schedule are avoided and `close` is the `close`.
73
+
74
+ ### More stable memory management
8
75
 
9
- These evented parsers are used:
76
+ To make better user experience, you may tune the server to stop GC while doing request, and start GC again after every serveral requests. But by doing so you are increasing the probability of OOM: there are cases when `malloc` or `new` fails to get memory while the GC stopped by you can release some. With C-ext this can be partly fixed with Ruby's `ALLOC` and `ALLOC_N`, which can make GC release some memory when `malloc` fails. But with C++ this becomes a bit messy: you need to redefine `new` operators.
10
77
 
11
- - http_parser with chunked encoding support
12
- - multipart request parser
78
+ In Nyara, the use of C++ memory allocation is limited to boot time (the use of C++ may possibly removed in the future) so your server has less chance to be quit by a silent `OutOfMemoryException`.
13
79
 
14
- And these are implemented in addition:
80
+ ### Shared buffer in layout rendering
15
81
 
16
- - `Accept-*` parser
17
- - mime type parser and content type matcher
18
- - url-encoded parser for parsing path / query / request body
82
+ Consider you have a page with nested layout: `layout1` encloses `layout2`, and `layout2` contains `page`.
19
83
 
20
- ## Fast first-line routing and request class
84
+ When rendering `layout2`, the output string of `page` becomes an element inside the array buffer of `layout2`, then the output of `page` is duplicated in the output of `layout2`. When rendering `layout1`, the output of `layout2` is duplicated so a string containing the output of `page` is duplicated, again.
21
85
 
22
- ## Thin evented IO layer built for *nix and Fiber
86
+ In Nyara, nested templates of Slim, ERB or Haml share the same output buffer, so the duplication is greatly reduced.
23
87
 
24
88
  # How fast
25
89
 
26
- Speed is feature, there are specs on:
90
+ Performance is feature, there are specs on (TODO):
27
91
 
28
92
  - Accept-* parse vs rack
29
- - MIME parse vs rack
30
- - query parse vs rack
31
- - query parse vs houdini
32
- - layout engine vs tilt
93
+ - MIME matching vs rack
94
+ - param parse vs ruby
95
+ - layout rendering vs tilt
33
96
  - evented IO vs eventmachine
34
- - helloworld vs sinatra
35
- - pseudo real app speed vs jruby sinatra threaded mode
@@ -50,20 +50,20 @@ module Nyara
50
50
  end
51
51
 
52
52
  it '#lookup_route' do
53
- scope, cont, args = Ext.lookup_route 'GET', '/hello'
53
+ scope, cont, args = Ext.lookup_route 'GET', '/hello', nil
54
54
  assert_equal @e2.scope, scope
55
55
  assert_equal @e2.controller, cont
56
56
  assert_equal [:'#second'], args
57
57
 
58
- scope, cont, args = Ext.lookup_route 'GET', '/hello/3world'
58
+ scope, cont, args = Ext.lookup_route 'GET', '/hello/3world', nil
59
59
  assert_equal @e1.scope, scope
60
60
  assert_equal @e1.controller, cont
61
61
  assert_equal [3, :'#1'], args
62
62
 
63
- scope, _ = Ext.lookup_route 'GET', '/world'
63
+ scope, _ = Ext.lookup_route 'GET', '/world', nil
64
64
  assert_equal nil, scope
65
65
 
66
- scope, _, args = Ext.lookup_route 'GET', '/a目录/2013-6-1'
66
+ scope, _, args = Ext.lookup_route 'GET', '/a目录/2013-6-1', nil
67
67
  assert_equal [2013, 6, 1, :'#dir'], args
68
68
  end
69
69
  end
@@ -5,14 +5,6 @@ module Nyara
5
5
  class DelegateController < Controller
6
6
  end
7
7
 
8
- before :all do
9
- Ext.set_skip_on_url true
10
- end
11
-
12
- after :all do
13
- Ext.set_skip_on_url false
14
- end
15
-
16
8
  before :each do
17
9
  @client, @server = Socket.pair :UNIX, :STREAM
18
10
  Ext.set_nonblock @server.fileno
@@ -20,7 +12,7 @@ module Nyara
20
12
  Ext.set_request_attrs @request, {
21
13
  method_num: HTTP_METHODS['GET'],
22
14
  path: '/search',
23
- param: ParamHash.new.tap{|h| h['q'] = 'nyara' },
15
+ query: ParamHash.new.tap{|h| h['q'] = 'nyara' },
24
16
  fiber: Fiber.new{},
25
17
  scope: '/scope',
26
18
  header: HeaderHash.new.tap{|h| h['Accept'] = 'en-US' }
data/spec/request_spec.rb CHANGED
@@ -2,14 +2,6 @@ require_relative "spec_helper"
2
2
 
3
3
  module Nyara
4
4
  describe Request do
5
- before :all do
6
- Ext.set_skip_on_url true
7
- end
8
-
9
- after :all do
10
- Ext.set_skip_on_url false
11
- end
12
-
13
5
  before :each do
14
6
  @server, @client = Socket.pair :UNIX, :STREAM, 0
15
7
  Ext.set_nonblock @server.fileno
@@ -18,10 +10,10 @@ module Nyara
18
10
  @request_attrs = {
19
11
  method_num: HTTP_METHODS['GET'],
20
12
  path: '/',
21
- param: HeaderHash.new.tap{|h| h['id'] = 1 },
13
+ query: HeaderHash.new.tap{|h| h['id'] = 1 },
22
14
  fiber: nil,
23
15
  scope: '/',
24
- ext: nil
16
+ format: 'html'
25
17
  }
26
18
  set_request_attrs
27
19
  end
data/spec/spec_helper.rb CHANGED
@@ -43,6 +43,14 @@ RSpec.configure do |config|
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ config.before :each do
48
+ GC.stress = true
49
+ end
50
+
51
+ config.after :each do
52
+ GC.stress = false
53
+ end
46
54
  end
47
55
 
48
56
  configure do
data/spec/view_spec.rb CHANGED
@@ -20,6 +20,10 @@ module Nyara
20
20
  View.init
21
21
  end
22
22
 
23
+ before :each do
24
+ GC.stress = false
25
+ end
26
+
23
27
  def render *xs
24
28
  @instance = RenderableMock.new
25
29
  view = View.new @instance, *xs
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nyara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.pre
4
+ version: 0.0.1.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zete Lui
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-19 00:00:00.000000000 Z
11
+ date: 2013-06-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Fast, slim and fuzzy ruby web framework + server, based on preforked
14
14
  event queue and Fiber. NO rack NOR eventmachine are used.