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

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