nyara 0.0.1.pre.5 → 0.0.1.pre.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/example/factorial.rb +19 -0
  3. data/ext/accept.c +2 -2
  4. data/ext/event.c +48 -23
  5. data/ext/extconf.rb +2 -0
  6. data/ext/hashes.c +28 -3
  7. data/ext/http-parser/http_parser.h +1 -0
  8. data/ext/nyara.c +20 -3
  9. data/ext/nyara.h +13 -2
  10. data/ext/request.c +90 -13
  11. data/ext/request.h +8 -2
  12. data/ext/request_parse.c +135 -6
  13. data/ext/route.cc +7 -10
  14. data/ext/test_response.c +155 -0
  15. data/ext/url_encoded.c +0 -5
  16. data/lib/nyara/config.rb +5 -0
  17. data/lib/nyara/controller.rb +91 -28
  18. data/lib/nyara/cookie.rb +7 -0
  19. data/lib/nyara/flash.rb +23 -0
  20. data/lib/nyara/hashes/header_hash.rb +2 -0
  21. data/lib/nyara/nyara.rb +14 -2
  22. data/lib/nyara/part.rb +156 -0
  23. data/lib/nyara/patches/array.rb +5 -0
  24. data/lib/nyara/patches/blank.rb +128 -0
  25. data/lib/nyara/patches/json.rb +15 -0
  26. data/lib/nyara/patches/mini_support.rb +6 -0
  27. data/lib/nyara/patches/string.rb +21 -0
  28. data/lib/nyara/patches/to_query.rb +113 -0
  29. data/lib/nyara/request.rb +13 -15
  30. data/lib/nyara/route.rb +15 -80
  31. data/lib/nyara/route_entry.rb +69 -2
  32. data/lib/nyara/session.rb +66 -21
  33. data/lib/nyara/test.rb +170 -0
  34. data/lib/nyara/view.rb +5 -6
  35. data/lib/nyara.rb +7 -6
  36. data/nyara.gemspec +2 -2
  37. data/rakefile +34 -4
  38. data/readme.md +8 -1
  39. data/spec/config_spec.rb +28 -0
  40. data/spec/cpu_counter_spec.rb +9 -0
  41. data/spec/evented_io_spec.rb +1 -0
  42. data/spec/flash_spec.rb +29 -0
  43. data/spec/hashes_spec.rb +8 -0
  44. data/spec/mini_support_spec.rb +54 -0
  45. data/spec/part_spec.rb +52 -0
  46. data/spec/path_helper_spec.rb +22 -14
  47. data/spec/request_delegate_spec.rb +19 -11
  48. data/spec/route_entry_spec.rb +55 -0
  49. data/spec/session_spec.rb +69 -7
  50. data/spec/spec_helper.rb +3 -0
  51. data/spec/test_spec.rb +58 -0
  52. data/tools/hello.rb +11 -3
  53. data/tools/memcheck.rb +33 -0
  54. data/tools/s.rb +11 -0
  55. metadata +23 -7
  56. data/example/design.rb +0 -62
  57. data/example/fib.rb +0 -15
  58. data/spec/route_spec.rb +0 -84
  59. /data/ext/inc/{status_codes.inc → status_codes.h} +0 -0
data/ext/request_parse.c CHANGED
@@ -2,17 +2,92 @@
2
2
 
3
3
  #include "nyara.h"
4
4
  #include "request.h"
5
+ #include <ruby/re.h>
5
6
 
7
+ static ID id_update;
8
+ static ID id_final;
6
9
  static VALUE str_accept;
10
+ static VALUE str_content_type;
7
11
  static VALUE method_override_key;
8
12
  static VALUE nyara_http_methods;
9
13
 
14
+ static int mp_header_field(multipart_parser* parser, const char* s, size_t len) {
15
+ Request* p = multipart_parser_get_data(parser);
16
+ if (p->last_part == Qnil) {
17
+ p->last_part = rb_hash_new();
18
+ }
19
+
20
+ if (p->last_field == Qnil) {
21
+ p->last_field = rb_enc_str_new(s, len, u8_encoding);
22
+ p->last_value = Qnil;
23
+ } else {
24
+ rb_str_cat(p->last_field, s, len);
25
+ }
26
+ return 0;
27
+ }
28
+
29
+ static int mp_header_value(multipart_parser* parser, const char* s, size_t len) {
30
+ Request* p = multipart_parser_get_data(parser);
31
+ if (p->last_field == Qnil) {
32
+ if (p->last_value == Qnil) {
33
+ p->parse_state = PS_ERROR;
34
+ return 1;
35
+ }
36
+ rb_str_cat(p->last_value, s, len);
37
+ } else {
38
+ nyara_headerlize(p->last_field);
39
+ p->last_value = rb_enc_str_new(s, len, u8_encoding);
40
+ rb_hash_aset(p->last_part, p->last_field, p->last_value);
41
+ p->last_field = Qnil;
42
+ }
43
+ return 0;
44
+ }
45
+
46
+ static int mp_headers_complete(multipart_parser* parser) {
47
+ static VALUE part_class = Qnil;
48
+ if (part_class == Qnil) {
49
+ VALUE nyara = rb_const_get(rb_cModule, rb_intern("Nyara"));
50
+ part_class = rb_const_get(nyara, rb_intern("Part"));
51
+ }
52
+
53
+ Request* p = multipart_parser_get_data(parser);
54
+ p->last_field = Qnil;
55
+ p->last_value = Qnil;
56
+ p->last_part = rb_class_new_instance(1, &p->last_part, part_class);
57
+ return 0;
58
+ }
59
+
60
+ static int mp_part_data(multipart_parser* parser, const char* s, size_t len) {
61
+ Request* p = multipart_parser_get_data(parser);
62
+ rb_funcall(p->last_part, id_update, 1, rb_str_new(s, len)); // no need encoding
63
+ return 0;
64
+ }
65
+
66
+ static int mp_part_data_end(multipart_parser* parser) {
67
+ Request* p = multipart_parser_get_data(parser);
68
+ rb_ary_push(p->body, rb_funcall(p->last_part, id_final, 0));
69
+ p->last_part = Qnil;
70
+ return 0;
71
+ }
72
+
73
+ static multipart_parser_settings multipart_settings = {
74
+ .on_header_field = mp_header_field,
75
+ .on_header_value = mp_header_value,
76
+ .on_headers_complete = mp_headers_complete,
77
+
78
+ .on_part_data_begin = NULL,
79
+ .on_part_data = mp_part_data,
80
+ .on_part_data_end = mp_part_data_end,
81
+
82
+ .on_body_end = NULL
83
+ };
84
+
10
85
  static int on_url(http_parser* parser, const char* s, size_t len) {
11
86
  Request* p = (Request*)parser;
12
87
  p->method = parser->method;
13
88
 
14
89
  if (p->path_with_query == Qnil) {
15
- p->path_with_query = rb_str_new(s, len);
90
+ p->path_with_query = rb_enc_str_new(s, len, u8_encoding);
16
91
  } else {
17
92
  rb_str_cat(p->path_with_query, s, len);
18
93
  }
@@ -22,7 +97,7 @@ static int on_url(http_parser* parser, const char* s, size_t len) {
22
97
  static int on_header_field(http_parser* parser, const char* s, size_t len) {
23
98
  Request* p = (Request*)parser;
24
99
  if (p->last_field == Qnil) {
25
- p->last_field = rb_str_new(s, len);
100
+ p->last_field = rb_enc_str_new(s, len, u8_encoding);
26
101
  p->last_value = Qnil;
27
102
  } else {
28
103
  rb_str_cat(p->last_field, s, len);
@@ -40,7 +115,7 @@ static int on_header_value(http_parser* parser, const char* s, size_t len) {
40
115
  rb_str_cat(p->last_value, s, len);
41
116
  } else {
42
117
  nyara_headerlize(p->last_field);
43
- p->last_value = rb_str_new(s, len);
118
+ p->last_value = rb_enc_str_new(s, len, u8_encoding);
44
119
  rb_hash_aset(p->header, p->last_field, p->last_value);
45
120
  p->last_field = Qnil;
46
121
  }
@@ -79,6 +154,39 @@ static void _parse_path_and_query(Request* p) {
79
154
  }
80
155
  }
81
156
 
157
+ static char* _parse_multipart_boundary(VALUE header) {
158
+ static regex_t* re = NULL;
159
+ static OnigRegion region;
160
+ if (!re) {
161
+ // rfc2046
162
+ // regexp copied from rack
163
+ const char* pattern = "\\Amultipart/.*boundary=\\\"?([^\\\";,]+)\\\"?";
164
+ onig_new(&re, (const UChar*)pattern, (const UChar*)(pattern + strlen(pattern)),
165
+ ONIG_OPTION_NONE, ONIG_ENCODING_ASCII, ONIG_SYNTAX_RUBY, NULL);
166
+ onig_region_init(&region);
167
+ }
168
+
169
+ VALUE content_type = rb_hash_aref(header, str_content_type);
170
+ if (content_type == Qnil) {
171
+ return NULL;
172
+ }
173
+
174
+ long len = RSTRING_LEN(content_type);
175
+ char* s = RSTRING_PTR(content_type);
176
+
177
+ long matched_len = onig_match(re, (const UChar*)s, (const UChar*)(s + len), (const UChar*)s, &region, 0);
178
+ if (matched_len > 0) {
179
+ // multipart-parser needs a buffer to end with '\0'
180
+ long boundary_len = region.end[0] - region.beg[0];
181
+ char* boundary_bytes = ALLOC_N(char, boundary_len + 1);
182
+ memcpy(boundary_bytes, s + region.beg[0], boundary_len);
183
+ boundary_bytes[boundary_len] = '\0';
184
+ return boundary_bytes;
185
+ } else {
186
+ return NULL;
187
+ }
188
+ }
189
+
82
190
  static int on_headers_complete(http_parser* parser) {
83
191
  Request* p = (Request*)parser;
84
192
  p->last_field = Qnil;
@@ -87,11 +195,28 @@ static int on_headers_complete(http_parser* parser) {
87
195
  _parse_path_and_query(p);
88
196
  p->accept = ext_parse_accept_value(Qnil, rb_hash_aref(p->header, str_accept));
89
197
  p->parse_state = PS_HEADERS_COMPLETE;
198
+
199
+ char* boundary = _parse_multipart_boundary(p->header);
200
+ if (boundary) {
201
+ p->mparser = multipart_parser_init(boundary, &multipart_settings);
202
+ xfree(boundary);
203
+ multipart_parser_set_data(p->mparser, p);
204
+ p->body = rb_ary_new();
205
+ } else {
206
+ p->body = rb_enc_str_new("", 0, u8_encoding);
207
+ }
208
+
90
209
  return 0;
91
210
  }
92
211
 
93
212
  static int on_body(http_parser* parser, const char* s, size_t len) {
94
- // todo
213
+ Request* p = (Request*)parser;
214
+ if (p->mparser) {
215
+ multipart_parser_execute(p->mparser, s, len);
216
+ // todo sum total length, if too big, trigger save to tmpfile
217
+ } else {
218
+ rb_str_cat(p->body, s, len);
219
+ }
95
220
  return 0;
96
221
  }
97
222
 
@@ -114,9 +239,13 @@ http_parser_settings nyara_request_parse_settings = {
114
239
  };
115
240
 
116
241
  void Init_request_parse(VALUE nyara) {
117
- str_accept = rb_str_new2("Accept");
242
+ id_update = rb_intern("update");
243
+ id_final = rb_intern("final");
244
+ str_accept = rb_enc_str_new("Accept", strlen("Accept"), u8_encoding);
118
245
  rb_gc_register_mark_object(str_accept);
119
- method_override_key = rb_str_new2("_method");
246
+ str_content_type = rb_enc_str_new("Content-Type", strlen("Content-Type"), u8_encoding);
247
+ rb_gc_register_mark_object(str_content_type);
248
+ method_override_key = rb_enc_str_new("_method", strlen("_method"), u8_encoding);
120
249
  OBJ_FREEZE(method_override_key);
121
250
  rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
122
251
  nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
data/ext/route.cc CHANGED
@@ -4,7 +4,6 @@ extern "C" {
4
4
  #include "nyara.h"
5
5
  }
6
6
  #include <ruby/re.h>
7
- #include <ruby/encoding.h>
8
7
  #include <vector>
9
8
  #include <map>
10
9
  #include "inc/str_intern.h"
@@ -52,7 +51,6 @@ typedef RouteMap::iterator MapIter;
52
51
  static RouteMap route_map;
53
52
  static OnigRegion region; // we can reuse the region without worrying thread safety
54
53
  static ID id_to_s;
55
- static rb_encoding* u8_enc;
56
54
  static VALUE str_html;
57
55
  static VALUE nyara_http_methods;
58
56
 
@@ -169,13 +167,13 @@ static VALUE ext_list_route(VALUE self) {
169
167
  for (MapIter j = route_map.begin(); j != route_map.end(); j++) {
170
168
  RouteEntries* route_entries = j->second;
171
169
  arr = rb_ary_new();
172
- rb_hash_aset(route_hash, rb_str_new2(http_method_str(j->first)), arr);
170
+ rb_hash_aset(route_hash, rb_enc_str_new(http_method_str(j->first), strlen(http_method_str(j->first)), u8_encoding), arr);
173
171
  for (EntriesIter i = route_entries->begin(); i != route_entries->end(); i++) {
174
172
  e = rb_ary_new();
175
173
  rb_ary_push(e, i->is_sub ? Qtrue : Qfalse);
176
174
  rb_ary_push(e, i->scope);
177
- rb_ary_push(e, rb_str_new(i->prefix, i->prefix_len));
178
- rb_ary_push(e, rb_str_new(i->suffix, i->suffix_len));
175
+ rb_ary_push(e, rb_enc_str_new(i->prefix, i->prefix_len, u8_encoding));
176
+ rb_ary_push(e, rb_enc_str_new(i->suffix, i->suffix_len, u8_encoding));
179
177
  rb_ary_push(e, i->controller);
180
178
  rb_ary_push(e, i->id);
181
179
  conv = rb_ary_new();
@@ -191,13 +189,13 @@ static VALUE ext_list_route(VALUE self) {
191
189
 
192
190
  static VALUE build_args(const char* suffix, std::vector<ID>& conv) {
193
191
  volatile VALUE args = rb_ary_new();
194
- volatile VALUE str = rb_str_new2("");
192
+ volatile VALUE str = rb_str_new2(""); // tmp for conversion, no need encoding
195
193
  long last_len = 0;
196
194
  for (size_t j = 0; j < conv.size(); j++) {
197
195
  const char* capture_ptr = suffix + region.beg[j+1];
198
196
  long capture_len = region.end[j+1] - region.beg[j+1];
199
197
  if (conv[j] == id_to_s) {
200
- rb_ary_push(args, rb_enc_str_new(capture_ptr, capture_len, u8_enc));
198
+ rb_ary_push(args, rb_enc_str_new(capture_ptr, capture_len, u8_encoding));
201
199
  } else if (capture_len == 0) {
202
200
  rb_ary_push(args, Qnil);
203
201
  } else {
@@ -224,7 +222,7 @@ static VALUE extract_ext(const char* s, long len) {
224
222
  return Qnil;
225
223
  }
226
224
  }
227
- return rb_str_new(s, len);
225
+ return rb_enc_str_new(s, len, u8_encoding);
228
226
  }
229
227
 
230
228
  extern "C"
@@ -320,8 +318,7 @@ extern "C"
320
318
  void Init_route(VALUE nyara, VALUE ext) {
321
319
  nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
322
320
  id_to_s = rb_intern("to_s");
323
- u8_enc = rb_utf8_encoding();
324
- str_html = rb_str_new2("html");
321
+ str_html = rb_enc_str_new("html", strlen("html"), u8_encoding);
325
322
  OBJ_FREEZE(str_html);
326
323
  rb_gc_register_mark_object(str_html);
327
324
  onig_region_init(&region);
@@ -0,0 +1,155 @@
1
+ /* response parse callbacks, for test helper */
2
+
3
+ #include "nyara.h"
4
+
5
+ typedef struct {
6
+ http_parser hparser;
7
+ VALUE header;
8
+ VALUE body;
9
+ VALUE last_field;
10
+ VALUE last_value;
11
+ VALUE set_cookies;
12
+ } Response;
13
+
14
+ static VALUE str_set_cookie;
15
+ static VALUE nyara_http_methods;
16
+
17
+ static int on_header_field(http_parser* parser, const char* s, size_t len) {
18
+ Response* p = (Response*)parser;
19
+ if (p->last_field == Qnil) {
20
+ p->last_field = rb_enc_str_new(s, len, u8_encoding);
21
+ p->last_value = Qnil;
22
+ } else {
23
+ rb_str_cat(p->last_field, s, len);
24
+ }
25
+ return 0;
26
+ }
27
+
28
+ static int on_header_value(http_parser* parser, const char* s, size_t len) {
29
+ Response* p = (Response*)parser;
30
+ if (p->last_field == Qnil) {
31
+ if (p->last_value == Qnil) {
32
+ // todo show where
33
+ rb_raise(rb_eRuntimeError, "parse error");
34
+ return 1;
35
+ }
36
+ rb_str_cat(p->last_value, s, len);
37
+ } else {
38
+ nyara_headerlize(p->last_field);
39
+ p->last_value = rb_enc_str_new(s, len, u8_encoding);
40
+ if (RTEST(rb_funcall(p->last_field, rb_intern("=="), 1, str_set_cookie))) {
41
+ rb_ary_push(p->set_cookies, p->last_value);
42
+ } else {
43
+ rb_hash_aset(p->header, p->last_field, p->last_value);
44
+ }
45
+ p->last_field = Qnil;
46
+ }
47
+ return 0;
48
+ }
49
+
50
+ static int on_headers_complete(http_parser* parser) {
51
+ Response* p = (Response*)parser;
52
+ p->last_field = Qnil;
53
+ p->last_value = Qnil;
54
+ return 0;
55
+ }
56
+
57
+ static int on_body(http_parser* parser, const char* s, size_t len) {
58
+ Response* p = (Response*)parser;
59
+ if (p->body == Qnil) {
60
+ p->body = rb_enc_str_new(s, len, u8_encoding);
61
+ } else {
62
+ rb_str_cat(p->body, s, len);
63
+ }
64
+ return 0;
65
+ }
66
+
67
+ static int on_message_complete(http_parser* parser) {
68
+ Response* p = (Response*)parser;
69
+ p->last_field = Qnil;
70
+ p->last_value = Qnil;
71
+ return 0;
72
+ }
73
+
74
+ static http_parser_settings response_parse_settings = {
75
+ .on_message_begin = NULL,
76
+ .on_url = NULL,
77
+ .on_status_complete = NULL,
78
+ .on_header_field = on_header_field,
79
+ .on_header_value = on_header_value,
80
+ .on_headers_complete = on_headers_complete,
81
+ .on_body = on_body,
82
+ .on_message_complete = on_message_complete
83
+ };
84
+
85
+ static void response_mark(void* pp) {
86
+ Response* p = pp;
87
+ if (p) {
88
+ rb_gc_mark_maybe(p->header);
89
+ rb_gc_mark_maybe(p->body);
90
+ rb_gc_mark_maybe(p->last_field);
91
+ rb_gc_mark_maybe(p->last_value);
92
+ rb_gc_mark_maybe(p->set_cookies);
93
+ }
94
+ }
95
+
96
+ static VALUE response_alloc(VALUE klass) {
97
+ Response* p = ALLOC(Response);
98
+ http_parser_init(&(p->hparser), HTTP_RESPONSE);
99
+ p->header = Qnil;
100
+ p->body = Qnil;
101
+ p->last_field = Qnil;
102
+ p->last_value = Qnil;
103
+ p->set_cookies = Qnil;
104
+ // NOTE new in alloc func will crash GCC-4.2/4.6 in GC.stress mode
105
+ return Data_Wrap_Struct(klass, response_mark, xfree, p);
106
+ }
107
+
108
+ static VALUE response_initialize(VALUE self, VALUE data) {
109
+ Check_Type(data, T_STRING);
110
+ Response* p;
111
+ Data_Get_Struct(self, Response, p);
112
+ p->header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
113
+ p->set_cookies = rb_ary_new();
114
+ http_parser_execute(&(p->hparser), &response_parse_settings, RSTRING_PTR(data), RSTRING_LEN(data));
115
+ return self;
116
+ }
117
+
118
+ static VALUE response_header(VALUE self) {
119
+ Response* p;
120
+ Data_Get_Struct(self, Response, p);
121
+ return p->header;
122
+ }
123
+
124
+ static VALUE response_body(VALUE self) {
125
+ Response* p;
126
+ Data_Get_Struct(self, Response, p);
127
+ return p->body;
128
+ }
129
+
130
+ static VALUE response_status(VALUE self) {
131
+ Response* p;
132
+ Data_Get_Struct(self, Response, p);
133
+ return INT2FIX(p->hparser.status_code);
134
+ }
135
+
136
+ static VALUE response_set_cookies(VALUE self) {
137
+ Response* p;
138
+ Data_Get_Struct(self, Response, p);
139
+ return p->set_cookies;
140
+ }
141
+
142
+ void Init_test_response(VALUE nyara) {
143
+ str_set_cookie = rb_enc_str_new("Set-Cookie", strlen("Set-Cookie"), u8_encoding);
144
+ rb_gc_register_mark_object(str_set_cookie);
145
+
146
+ nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
147
+ VALUE test = rb_define_module_under(nyara, "Test");
148
+ VALUE response = rb_define_class_under(test, "Response", rb_cObject);
149
+ rb_define_alloc_func(response, response_alloc);
150
+ rb_define_method(response, "initialize", response_initialize, 1);
151
+ rb_define_method(response, "header", response_header, 0);
152
+ rb_define_method(response, "body", response_body, 0);
153
+ rb_define_method(response, "status", response_status, 0);
154
+ rb_define_method(response, "set_cookies", response_set_cookies, 0);
155
+ }
data/ext/url_encoded.c CHANGED
@@ -1,9 +1,6 @@
1
1
  /* url-encoded parsing */
2
2
 
3
3
  #include "nyara.h"
4
- #include <ruby/encoding.h>
5
-
6
- static rb_encoding* u8_encoding;
7
4
 
8
5
  static char _half_octet(char c) {
9
6
  // there's a faster way but not validating the range:
@@ -354,8 +351,6 @@ static VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
354
351
  }
355
352
 
356
353
  void Init_url_encoded(VALUE ext) {
357
- u8_encoding = rb_utf8_encoding();
358
-
359
354
  rb_define_singleton_method(ext, "parse_param", ext_parse_param, 2);
360
355
  rb_define_singleton_method(ext, "parse_cookie", ext_parse_cookie, 2);
361
356
  // for test
data/lib/nyara/config.rb CHANGED
@@ -6,6 +6,11 @@ module Nyara
6
6
  # - public
7
7
  Config = ConfigHash.new
8
8
  class << Config
9
+ def reset
10
+ clear
11
+ Route.clear
12
+ end
13
+
9
14
  def map prefix, controller
10
15
  Route.register_controller prefix, controller
11
16
  end
@@ -15,7 +15,7 @@ module Nyara
15
15
  action.http_method = HTTP_METHODS[method]
16
16
  action.path = path
17
17
  action.set_accept_exts @formats
18
- action.id = @curr_id.to_sym if @curr_id
18
+ action.id = @curr_id if @curr_id
19
19
  action.blk = blk
20
20
  @route_entries << action
21
21
 
@@ -45,7 +45,7 @@ module Nyara
45
45
  if tag
46
46
  # todo scan class
47
47
  id = tag[/\#\w++(\-\w++)*/]
48
- @curr_id = id
48
+ @curr_id = id.to_sym
49
49
  end
50
50
 
51
51
  if opts
@@ -81,8 +81,8 @@ module Nyara
81
81
  http 'PATCH', path, &blk
82
82
  end
83
83
 
84
- # HTTP OPTIONS
85
- # todo generate options response for a url
84
+ # HTTP OPTIONS<br>
85
+ # todo generate options response for a url<br>
86
86
  # see http://tools.ietf.org/html/rfc5789
87
87
  def options path, &blk
88
88
  http 'OPTIONS', path, &blk
@@ -104,8 +104,7 @@ module Nyara
104
104
  end
105
105
  attr_reader :controller_name
106
106
 
107
- # :nodoc:
108
- def preprocess_actions
107
+ def compile_route_entries scope # :nodoc:
109
108
  raise "#{self}: no action defined" unless @route_entries
110
109
 
111
110
  curr_id = :'#0'
@@ -118,68 +117,127 @@ module Nyara
118
117
  }
119
118
  next_id[]
120
119
 
120
+ @path_templates = {}
121
121
  @route_entries.each do |e|
122
- e.id ||= next_id[]
122
+ e.id = next_id[] if e.id.empty?
123
123
  define_method e.id, &e.blk
124
+ e.compile self, scope
125
+ e.validate
126
+ @path_templates[e.id] = e.path_template
124
127
  end
125
128
  @route_entries
126
129
  end
130
+
131
+ attr_accessor :path_templates
127
132
  end
128
133
 
129
134
  include Renderable
130
135
 
131
- # :nodoc:
132
136
  def self.inherited klass
133
137
  # klass will also have this inherited method
134
138
  # todo check class name
135
139
  klass.extend ClassMethods
136
- [:@route_entries, :@usred_ids, :@default_layout].each do |iv|
140
+ [:@used_ids, :@default_layout].each do |iv|
137
141
  klass.instance_variable_set iv, klass.superclass.instance_variable_get(iv)
138
142
  end
143
+
144
+ route_entries = klass.superclass.instance_variable_get :@route_entries
145
+ if route_entries
146
+ route_entries.map! {|e| e.dup }
147
+ klass.instance_variable_set :@route_entries, route_entries
148
+ end
139
149
  end
140
150
 
141
151
  # Path helper
142
- def path_for id, *args
152
+ def path_to id, *args
143
153
  if args.last.is_a?(Hash)
144
154
  opts = args.pop
145
155
  end
146
156
 
147
- r = Route.path_template(self.class, id) % args
157
+ r = self.class.path_templates[id.to_s] % args
148
158
 
149
159
  if opts
150
- r << ".#{opts[:format]}" if opts[:format]
151
- query = opts.map do |k, v|
152
- next if k == :format
153
- "#{CGI.escape k.to_s}=#{CGI.escape v}"
154
- end
155
- query.compact!
156
- r << '?' << query.join('&') unless query.empty?
160
+ format = opts.delete :format
161
+ r << ".#{format}" if format
162
+ r << '?' << opts.to_query unless opts.empty?
157
163
  end
158
164
  r
159
165
  end
160
166
 
161
- # Url helper
162
- # NOTE: host can include port
163
- def url_for id, *args, scheme: nil, host: Config['host'], **opts
167
+ # Url helper<br>
168
+ # NOTE: host string can include port number<br>
169
+ # TODO: user and password?
170
+ def url_to id, *args, scheme: nil, host: nil, **opts
164
171
  scheme = scheme ? scheme.sub(/\:?$/, '://') : '//'
165
- host ||= 'localhost'
166
- path = path_for id, *args, opts
172
+ host ||= request.host_with_port
173
+ path = path_to id, *args, opts
167
174
  scheme << host << path
168
175
  end
169
176
 
177
+ # Redirect to a url or path, terminates action<br>
178
+ # +status+ can be one of:
179
+ #
180
+ # - 300 multiple choices (e.g. offer different languages)
181
+ # - 301 moved permanently
182
+ # - 302 found (default)
183
+ # - 303 see other (e.g. for results of cgi-scripts)
184
+ # - 307 temporary redirect
185
+ #
186
+ # Caveats: there's no content in a redirect response yet, if you want one, you can configure nginx to add it
187
+ def redirect url_or_path, status=302
188
+ status = status.to_i
189
+ raise "unsupported redirect status: #{status}" unless HTTP_REDIRECT_STATUS.include?(status)
190
+
191
+ r = request
192
+ header = r.header
193
+ self.status status
194
+
195
+ uri = URI.parse url_or_path
196
+ if uri.host.nil?
197
+ uri.host = request.domain
198
+ uri.port = request.port
199
+ end
200
+ uri.scheme = r.ssl? ? 'https' : 'http'
201
+ r.header['Location'] = uri.to_s
202
+
203
+ # similar to send_header, but without content-type
204
+ Ext.request_send_data r, HTTP_STATUS_FIRST_LINES[r.status]
205
+ data = header.serialize
206
+ data.concat r.response_header_extra_lines
207
+ data << "\r\n"
208
+ Ext.request_send_data r, data.join
209
+
210
+ Fiber.yield :term_close
211
+ end
212
+
213
+ # Shortcut for +redirect url_to *xs+
214
+ def redirect_to *xs
215
+ redirect url_to(*xs)
216
+ end
217
+
218
+ # Request extension or generated by `Accept`
170
219
  def format
171
220
  request.format
172
221
  end
173
222
 
223
+ # Request header<br>
224
+ # NOTE to change response header, use +set_header+
174
225
  def header
175
226
  request.header
176
227
  end
177
228
  alias headers header
178
229
 
179
- def set_header k, v
180
- request.response_header[k] = v
230
+ # Set response header
231
+ def set_header field, value
232
+ request.response_header[field] = value
181
233
  end
182
234
 
235
+ # Append an extra line in reponse header
236
+ #
237
+ # :call-seq:
238
+ #
239
+ # add_header_line "X-Myheader: here we are"
240
+ #
183
241
  def add_header_line h
184
242
  raise 'can not modify sent header' if request.response_header.frozen?
185
243
  h = h.sub /(?<![\r\n])\z/, "\r\n"
@@ -239,6 +297,10 @@ module Nyara
239
297
  request.session
240
298
  end
241
299
 
300
+ def flash
301
+ request.flash
302
+ end
303
+
242
304
  # Set response status
243
305
  def status n
244
306
  raise ArgumentError, "unsupported status: #{n}" unless HTTP_STATUS_FIRST_LINES[n]
@@ -268,15 +330,16 @@ module Nyara
268
330
 
269
331
  header.reverse_merge! OK_RESP_HEADER
270
332
 
271
- data = header.map do |k, v|
272
- "#{k}: #{v}\r\n"
273
- end
333
+ data = header.serialize
274
334
  data.concat r.response_header_extra_lines
335
+ data << Session.encode_set_cookie(r.session, r.ssl?)
275
336
  data << "\r\n"
276
337
  Ext.request_send_data r, data.join
277
338
 
278
339
  # forbid further modification
279
340
  header.freeze
341
+ session.freeze
342
+ flash.next.freeze
280
343
  end
281
344
 
282
345
  # Send raw data, that is, not wrapped in chunked encoding<br>
data/lib/nyara/cookie.rb CHANGED
@@ -3,6 +3,13 @@ module Nyara
3
3
  module Cookie
4
4
  extend self
5
5
 
6
+ # encode to string value
7
+ def encode h
8
+ h.map do |k, v|
9
+ "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}"
10
+ end.join '; '
11
+ end
12
+
6
13
  def decode header
7
14
  res = ParamHash.new
8
15
  if data = header['Cookie']