nyara 0.0.1.pre.6 → 0.0.1.pre.8

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/example/project.rb +11 -0
  3. data/example/stream.rb +6 -2
  4. data/ext/event.c +83 -32
  5. data/ext/hashes.c +6 -1
  6. data/ext/inc/epoll.h +1 -2
  7. data/ext/inc/kqueue.h +1 -2
  8. data/ext/nyara.h +2 -0
  9. data/ext/request.c +14 -9
  10. data/ext/test_response.c +2 -5
  11. data/ext/url_encoded.c +55 -1
  12. data/lib/nyara/config.rb +68 -17
  13. data/lib/nyara/controller.rb +76 -15
  14. data/lib/nyara/controllers/public_controller.rb +14 -0
  15. data/lib/nyara/cookie.rb +5 -4
  16. data/lib/nyara/flash.rb +2 -0
  17. data/lib/nyara/nyara.rb +153 -20
  18. data/lib/nyara/patches/to_query.rb +1 -2
  19. data/lib/nyara/request.rb +0 -5
  20. data/lib/nyara/route.rb +2 -2
  21. data/lib/nyara/route_entry.rb +5 -4
  22. data/lib/nyara/session.rb +47 -22
  23. data/lib/nyara/test.rb +13 -10
  24. data/lib/nyara/view.rb +27 -49
  25. data/lib/nyara/view_handlers/erb.rb +21 -0
  26. data/lib/nyara/view_handlers/erubis.rb +81 -0
  27. data/lib/nyara/view_handlers/haml.rb +17 -0
  28. data/lib/nyara/view_handlers/slim.rb +16 -0
  29. data/nyara.gemspec +3 -1
  30. data/readme.md +2 -2
  31. data/spec/apps/connect.rb +1 -1
  32. data/spec/config_spec.rb +76 -4
  33. data/spec/{test_spec.rb → integration_spec.rb} +47 -3
  34. data/spec/path_helper_spec.rb +1 -1
  35. data/spec/performance/escape.rb +10 -0
  36. data/spec/performance/layout.slim +14 -0
  37. data/spec/performance/page.slim +16 -0
  38. data/spec/performance_spec.rb +6 -1
  39. data/spec/public/empty file.html +0 -0
  40. data/spec/public/index.html +1 -0
  41. data/spec/request_delegate_spec.rb +1 -1
  42. data/spec/request_spec.rb +20 -0
  43. data/spec/route_entry_spec.rb +7 -0
  44. data/spec/session_spec.rb +8 -4
  45. data/spec/spec_helper.rb +1 -0
  46. data/spec/{ext_parse_spec.rb → url_encoded_spec.rb} +17 -5
  47. data/spec/views/edit.haml +2 -0
  48. data/spec/views/edit.slim +2 -0
  49. data/spec/views/index.liquid +0 -0
  50. data/spec/views/invalid_layout.liquid +0 -0
  51. data/spec/views/layout.erb +1 -0
  52. data/spec/views/show.slim +1 -0
  53. data/tools/foo.rb +9 -0
  54. data/tools/hello.rb +16 -11
  55. metadata +22 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4923242f45af2bfc9d06879f83c200d10379f89b
4
- data.tar.gz: 7a03f851beecfb9e5846bd17adf3b15efe91968b
3
+ metadata.gz: 1464261ce5d4ec90868a68278e81de0124568897
4
+ data.tar.gz: 566342b6927ac0fc3f5607580a0863d18b9be432
5
5
  SHA512:
6
- metadata.gz: 3ffe022bf0b05d95238fddcf4bf2fdb998c23e8490d31eec5154bd011034eb058fb2fd6ca99dcc37aeee1282102f6cb924e61fe20eeecc81f0ed50d903762186
7
- data.tar.gz: cf2fe6b094478f9ed04c96e940e2075a019908abafb7fbf5eb255e66c91afa80dd5efea3b4c9d943f6d27a6a18f966b17b116e1664552c658264c7abda26fe69
6
+ metadata.gz: c322de1bd85993df20041bd8535800751abdf4b4c8152b7ed4899a0430aed90d4552a97f21839267376f78bac18cc254fd395e9124cc071db8e4b3575ba6537c
7
+ data.tar.gz: e7c16bb717db1c131069d361087a8d97e96b1282d93d002582aa10cd0bcff01741eedefa0d43ab65c18c9ec480e1c214d3f03218c4569a1bb3786194c43c46e8
@@ -0,0 +1,11 @@
1
+ require_relative "../lib/nyara"
2
+
3
+ configure do
4
+ set :root, __dir__
5
+ set :public, 'public'
6
+ set :views, 'views'
7
+ end
8
+
9
+ get '/' do
10
+ send_string 'hello'
11
+ end
data/example/stream.rb CHANGED
@@ -1,5 +1,9 @@
1
- require "nyara"
2
- require "slim"
1
+ require_relative "../lib/nyara"
2
+ require "pry"
3
+
4
+ configure do
5
+ set :root, __dir__
6
+ end
3
7
 
4
8
  get '/' do
5
9
  view = stream 'index'
data/ext/event.c CHANGED
@@ -18,6 +18,7 @@ extern VALUE rb_obj_reveal(VALUE obj, VALUE klass);
18
18
  #define ETYPE_CONNECT 2
19
19
  #define MAX_E 1024
20
20
  static void loop_body(int fd, int etype);
21
+ static void loop_check();
21
22
  static int qfd = 0;
22
23
 
23
24
  #define MAX_RECEIVE_DATA 65536 * 2
@@ -38,6 +39,8 @@ static VALUE sym_writing;
38
39
  static VALUE sym_reading;
39
40
  static VALUE sym_sleep;
40
41
  static Request* curr_request;
42
+ static VALUE to_resume_actions;
43
+ static bool graceful_quit = false;
41
44
 
42
45
  static VALUE _fiber_func(VALUE _, VALUE args) {
43
46
  VALUE instance = rb_ary_pop(args);
@@ -46,6 +49,22 @@ static VALUE _fiber_func(VALUE _, VALUE args) {
46
49
  return Qnil;
47
50
  }
48
51
 
52
+ static void _resume_action(Request* p) {
53
+ VALUE state = rb_fiber_resume(p->fiber, 0, NULL);
54
+ if (state == Qnil) { // _fiber_func always returns Qnil
55
+ // terminated (todo log raised error ?)
56
+ nyara_request_term_close(p->self);
57
+ } else if (state == sym_term_close) {
58
+ nyara_request_term_close(p->self);
59
+ } else if (state == sym_writing) {
60
+ // do nothing
61
+ } else if (state == sym_reading) {
62
+ // do nothing
63
+ } else if (state == sym_sleep) {
64
+ // do nothing
65
+ }
66
+ }
67
+
49
68
  static void _handle_request(VALUE request) {
50
69
  Request* p;
51
70
  Data_Get_Struct(request, Request, p);
@@ -92,27 +111,19 @@ static void _handle_request(VALUE request) {
92
111
  p->response_header_extra_lines = rb_ary_new();
93
112
  nyara_request_init_env(request);
94
113
  } else {
95
- rb_funcall(p->self, id_not_found, 0);
114
+ static const char* not_found = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
115
+ static long not_found_len = 0;
116
+ if (!not_found_len) {
117
+ not_found_len = strlen(not_found);
118
+ }
119
+ nyara_send_data(p->fd, not_found, not_found_len);
96
120
  nyara_detach_fd(p->fd);
97
121
  p->fd = 0;
98
122
  return;
99
123
  }
100
124
  }
101
125
 
102
- // resume action
103
- VALUE state = rb_fiber_resume(p->fiber, 0, NULL);
104
- if (state == Qnil) { // _fiber_func always returns Qnil
105
- // terminated (todo log raised error ?)
106
- nyara_request_term_close(request);
107
- } else if (state == sym_term_close) {
108
- nyara_request_term_close(request);
109
- } else if (state == sym_writing) {
110
- // do nothing
111
- } else if (state == sym_reading) {
112
- // do nothing
113
- } else if (state == sym_sleep) {
114
- // do nothing
115
- }
126
+ _resume_action(p);
116
127
  }
117
128
 
118
129
  // platform independent, invoked by LOOP_E()
@@ -145,6 +156,45 @@ static void loop_body(int fd, int etype) {
145
156
  }
146
157
  }
147
158
 
159
+ static void loop_check() {
160
+ // execute other thread / interrupts
161
+ rb_thread_schedule();
162
+
163
+ // wakeup actions which finished sleeping
164
+ long len = RARRAY_LEN(to_resume_actions);
165
+ if (len) {
166
+ VALUE* ptr = RARRAY_PTR(to_resume_actions);
167
+ for (long i = 0; i < len; i++) {
168
+ VALUE request = ptr[i];
169
+ Request* p;
170
+ Data_Get_Struct(request, Request, p);
171
+
172
+ p->sleeping = false;
173
+ if (!rb_fiber_alive_p(p->fiber)) {
174
+ continue;
175
+ }
176
+
177
+ _resume_action(p);
178
+ if (qfd) {
179
+ VALUE* v_fds = RARRAY_PTR(p->watched_fds);
180
+ long v_fds_len = RARRAY_LEN(p->watched_fds);
181
+ for (long i = 0; i < v_fds_len; i++) {
182
+ ADD_E(FIX2INT(v_fds[i]), ETYPE_CONNECT);
183
+ }
184
+ ADD_E(p->fd, ETYPE_HANDLE_REQUEST);
185
+ } else {
186
+ // we are in a test, no queue
187
+ }
188
+ }
189
+ }
190
+
191
+ if (graceful_quit) {
192
+ if (RTEST(rb_funcall(fd_request_map, rb_intern("empty?"), 0))) {
193
+ _Exit(0);
194
+ }
195
+ }
196
+ }
197
+
148
198
  void nyara_detach_fd(int fd) {
149
199
  VALUE request = rb_hash_delete(fd_request_map, INT2FIX(fd));
150
200
  if (request != Qnil) {
@@ -164,8 +214,9 @@ static VALUE ext_init_queue(VALUE _) {
164
214
  return Qnil;
165
215
  }
166
216
 
167
- static VALUE ext_run_queue(VALUE _, VALUE v_fd) {
168
- int fd = FIX2INT(v_fd);
217
+ // run queue loop with server_fd
218
+ static VALUE ext_run_queue(VALUE _, VALUE v_server_fd) {
219
+ int fd = FIX2INT(v_server_fd);
169
220
  nyara_set_nonblock(fd);
170
221
  ADD_E(fd, ETYPE_CAN_ACCEPT);
171
222
 
@@ -173,6 +224,15 @@ static VALUE ext_run_queue(VALUE _, VALUE v_fd) {
173
224
  return Qnil;
174
225
  }
175
226
 
227
+ // set graceful quit flag and do not accept server_fd anymore
228
+ static VALUE ext_graceful_quit(VALUE _, VALUE v_server_fd) {
229
+ graceful_quit = true;
230
+ int fd = FIX2INT(v_server_fd);
231
+ DEL_E(fd);
232
+ return Qnil;
233
+ }
234
+
235
+ // put request into sleep
176
236
  static VALUE ext_request_sleep(VALUE _, VALUE request) {
177
237
  Request* p;
178
238
  Data_Get_Struct(request, Request, p);
@@ -192,23 +252,10 @@ static VALUE ext_request_sleep(VALUE _, VALUE request) {
192
252
  return Qnil;
193
253
  }
194
254
 
255
+ // NOTE this will be executed in another thread, resuming fiber in a non-main thread will stuck
195
256
  static VALUE ext_request_wakeup(VALUE _, VALUE request) {
196
257
  // NOTE should not use curr_request
197
- Request* p;
198
- Data_Get_Struct(request, Request, p);
199
-
200
- p->sleeping = false;
201
- if (!qfd) {
202
- // we are in a test
203
- return Qnil;
204
- }
205
-
206
- VALUE* v_fds = RARRAY_PTR(p->watched_fds);
207
- long v_fds_len = RARRAY_LEN(p->watched_fds);
208
- for (long i = 0; i < v_fds_len; i++) {
209
- ADD_E(FIX2INT(v_fds[i]), ETYPE_CONNECT);
210
- }
211
- ADD_E(p->fd, ETYPE_HANDLE_REQUEST);
258
+ rb_ary_push(to_resume_actions, request);
212
259
  return Qnil;
213
260
  }
214
261
 
@@ -330,8 +377,12 @@ void Init_event(VALUE ext) {
330
377
  sym_reading = ID2SYM(rb_intern("reading"));
331
378
  sym_sleep = ID2SYM(rb_intern("sleep"));
332
379
 
380
+ to_resume_actions = rb_ary_new();
381
+ rb_gc_register_mark_object(to_resume_actions);
382
+
333
383
  rb_define_singleton_method(ext, "init_queue", ext_init_queue, 0);
334
384
  rb_define_singleton_method(ext, "run_queue", ext_run_queue, 1);
385
+ rb_define_singleton_method(ext, "graceful_quit", ext_graceful_quit, 1);
335
386
 
336
387
  rb_define_singleton_method(ext, "request_sleep", ext_request_sleep, 1);
337
388
  rb_define_singleton_method(ext, "request_wakeup", ext_request_wakeup, 1);
data/ext/hashes.c CHANGED
@@ -112,8 +112,13 @@ static VALUE header_hash_reverse_merge_bang(VALUE self, VALUE other) {
112
112
  }
113
113
 
114
114
  static int header_hash_serialize_func(VALUE k, VALUE v, VALUE arr) {
115
- long klen = RSTRING_LEN(k);
116
115
  long vlen = RSTRING_LEN(v);
116
+ // deleted field
117
+ if (vlen == 0) {
118
+ return ST_CONTINUE;
119
+ }
120
+
121
+ long klen = RSTRING_LEN(k);
117
122
  long capa = klen + vlen + 4;
118
123
  volatile VALUE s = rb_str_buf_new(capa);
119
124
  sprintf(RSTRING_PTR(s), "%.*s: %.*s\r\n", (int)klen, RSTRING_PTR(k), (int)vlen, RSTRING_PTR(v));
data/ext/inc/epoll.h CHANGED
@@ -57,7 +57,6 @@ static void LOOP_E() {
57
57
  break;
58
58
  }
59
59
  }
60
- // execute other thread / interrupts
61
- rb_thread_schedule();
60
+ loop_check();
62
61
  }
63
62
  }
data/ext/inc/kqueue.h CHANGED
@@ -72,7 +72,6 @@ static void LOOP_E() {
72
72
  break;
73
73
  }
74
74
  }
75
- // execute other thread / interrupts
76
- rb_thread_schedule();
75
+ loop_check();
77
76
  }
78
77
  }
data/ext/nyara.h CHANGED
@@ -24,6 +24,7 @@ void Init_request(VALUE nyara, VALUE ext);
24
24
  VALUE nyara_request_new(int fd);
25
25
  void nyara_request_init_env(VALUE request);
26
26
  void nyara_request_term_close(VALUE request);
27
+ bool nyara_send_data(int fd, const char* s, long len);
27
28
 
28
29
 
29
30
  /* test_response.c */
@@ -34,6 +35,7 @@ void Init_test_response(VALUE nyara);
34
35
  void Init_url_encoded(VALUE ext);
35
36
  long nyara_parse_path(VALUE path, const char*s, long len);
36
37
  void nyara_parse_param(VALUE output, const char* s, long len);
38
+ VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str);
37
39
 
38
40
 
39
41
  /* accept.c */
data/ext/request.c CHANGED
@@ -106,20 +106,25 @@ VALUE nyara_request_new(int fd) {
106
106
  }
107
107
 
108
108
  void nyara_request_init_env(VALUE self) {
109
- static VALUE cookie_mod = Qnil;
110
109
  static VALUE session_mod = Qnil;
111
110
  static VALUE flash_class = Qnil;
111
+ static VALUE str_cookie = Qnil;
112
112
  static ID id_decode = 0;
113
- if (cookie_mod == Qnil) {
113
+ if (session_mod == Qnil) {
114
114
  VALUE nyara = rb_const_get(rb_cModule, rb_intern("Nyara"));
115
- cookie_mod = rb_const_get(nyara, rb_intern("Cookie"));
116
115
  session_mod = rb_const_get(nyara, rb_intern("Session"));
117
116
  flash_class = rb_const_get(nyara, rb_intern("Flash"));
117
+ str_cookie = rb_enc_str_new("Cookie", strlen("Cookie"), u8_encoding);
118
+ rb_gc_register_mark_object(str_cookie);
118
119
  id_decode = rb_intern("decode");
119
120
  }
120
121
 
121
122
  P;
122
- p->cookie = rb_funcall(cookie_mod, id_decode, 1, p->header);
123
+ p->cookie = rb_class_new_instance(0, NULL, nyara_param_hash_class);
124
+ VALUE cookie = rb_hash_aref(p->header, str_cookie);
125
+ if (cookie != Qnil) {
126
+ ext_parse_cookie(Qnil, p->cookie, cookie);
127
+ }
123
128
  p->session = rb_funcall(session_mod, id_decode, 1, p->cookie);
124
129
  p->flash = rb_class_new_instance(1, &p->session, flash_class);
125
130
  }
@@ -232,7 +237,7 @@ static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
232
237
  }
233
238
 
234
239
  // return true if success
235
- static bool _send_data(int fd, const char* buf, long len) {
240
+ bool nyara_send_data(int fd, const char* buf, long len) {
236
241
  while(len) {
237
242
  long written = write(fd, buf, len);
238
243
  if (written <= 0) {
@@ -256,7 +261,7 @@ static VALUE ext_request_send_data(VALUE _, VALUE self, VALUE data) {
256
261
  P;
257
262
  char* buf = RSTRING_PTR(data);
258
263
  long len = RSTRING_LEN(data);
259
- _send_data(p->fd, buf, len);
264
+ nyara_send_data(p->fd, buf, len);
260
265
  return Qnil;
261
266
  }
262
267
 
@@ -273,9 +278,9 @@ static VALUE ext_request_send_chunk(VALUE _, VALUE self, VALUE str) {
273
278
  rb_raise(rb_eRuntimeError, "fail to format chunk length for len: %ld", len);
274
279
  }
275
280
  bool success = \
276
- _send_data(p->fd, pre_buf, pre_len) &&
277
- _send_data(p->fd, RSTRING_PTR(str), len) &&
278
- _send_data(p->fd, "\r\n", 2);
281
+ nyara_send_data(p->fd, pre_buf, pre_len) &&
282
+ nyara_send_data(p->fd, RSTRING_PTR(str), len) &&
283
+ nyara_send_data(p->fd, "\r\n", 2);
279
284
 
280
285
  if (!success) {
281
286
  rb_sys_fail("write(2)");
data/ext/test_response.c CHANGED
@@ -56,11 +56,7 @@ static int on_headers_complete(http_parser* parser) {
56
56
 
57
57
  static int on_body(http_parser* parser, const char* s, size_t len) {
58
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
- }
59
+ rb_str_cat(p->body, s, len);
64
60
  return 0;
65
61
  }
66
62
 
@@ -111,6 +107,7 @@ static VALUE response_initialize(VALUE self, VALUE data) {
111
107
  Data_Get_Struct(self, Response, p);
112
108
  p->header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
113
109
  p->set_cookies = rb_ary_new();
110
+ p->body = rb_enc_str_new("", 0, u8_encoding);
114
111
  http_parser_execute(&(p->hparser), &response_parse_settings, RSTRING_PTR(data), RSTRING_LEN(data));
115
112
  return self;
116
113
  }
data/ext/url_encoded.c CHANGED
@@ -1,6 +1,7 @@
1
1
  /* url-encoded parsing */
2
2
 
3
3
  #include "nyara.h"
4
+ #include <ctype.h>
4
5
 
5
6
  static char _half_octet(char c) {
6
7
  // there's a faster way but not validating the range:
@@ -319,7 +320,7 @@ static VALUE _cookie_seg_str_new(const char* s, long len) {
319
320
  return rb_enc_str_new(s, len, u8_encoding);
320
321
  }
321
322
 
322
- static VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
323
+ VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
323
324
  volatile VALUE arr = rb_ary_new();
324
325
  const char* s = RSTRING_PTR(str);
325
326
  long len = RSTRING_LEN(str);
@@ -350,9 +351,62 @@ static VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
350
351
  return output;
351
352
  }
352
353
 
354
+ static bool _should_escape(char c) {
355
+ return !isalnum(c) && c != '_' && c != '.' && c != '-';
356
+ }
357
+
358
+ // prereq: n always < 16
359
+ static char _hex_char(unsigned char n) {
360
+ if (n < 10) {
361
+ return '0' + n;
362
+ } else {
363
+ return 'A' + (n - 10);
364
+ }
365
+ }
366
+
367
+ static void _concat_char(VALUE s, char c, bool ispath) {
368
+ static char buf[3] = {'%', 0, 0};
369
+ static char plus[1] = {'+'};
370
+
371
+ if (ispath) {
372
+ if (_should_escape(c) && c != '+' && c != '/') {
373
+ buf[1] = _hex_char((unsigned char)c / 16);
374
+ buf[2] = _hex_char((unsigned char)c % 16);
375
+ rb_str_cat(s, buf, 3);
376
+ } else {
377
+ rb_str_cat(s, &c, 1);
378
+ }
379
+ } else {
380
+ if (c == ' ') {
381
+ rb_str_cat(s, plus, 1);
382
+ } else if (_should_escape(c)) {
383
+ buf[1] = _hex_char((unsigned char)c / 16);
384
+ buf[2] = _hex_char((unsigned char)c % 16);
385
+ rb_str_cat(s, buf, 3);
386
+ } else {
387
+ rb_str_cat(s, &c, 1);
388
+ }
389
+ }
390
+ }
391
+
392
+ // escape for uri path ('/', '+' are not changed) or component ('/', '+' are changed)
393
+ static VALUE ext_escape(VALUE _, VALUE s, VALUE v_ispath) {
394
+ Check_Type(s, T_STRING);
395
+ long len = RSTRING_LEN(s);
396
+ const char* ptr = RSTRING_PTR(s);
397
+ volatile VALUE res = rb_str_buf_new(len);
398
+ bool ispath = RTEST(v_ispath);
399
+ for (long i = 0; i < len; i++) {
400
+ _concat_char(res, ptr[i], ispath);
401
+ }
402
+ rb_enc_associate(res, u8_encoding);
403
+ return res;
404
+ }
405
+
353
406
  void Init_url_encoded(VALUE ext) {
354
407
  rb_define_singleton_method(ext, "parse_param", ext_parse_param, 2);
355
408
  rb_define_singleton_method(ext, "parse_cookie", ext_parse_cookie, 2);
409
+ rb_define_singleton_method(ext, "escape", ext_escape, 2);
356
410
  // for test
357
411
  rb_define_singleton_method(ext, "parse_url_encoded_seg", ext_parse_url_encoded_seg, 3);
358
412
  rb_define_singleton_method(ext, "parse_path", ext_parse_path, 2);
data/lib/nyara/config.rb CHANGED
@@ -1,32 +1,83 @@
1
1
  module Nyara
2
- # other options:
3
- # - session (see also Session)
4
- # - host
5
- # - views
6
- # - public
2
+ # options:
3
+ #
4
+ # [env] environment, default is +'development'+
5
+ # [port] listen port number
6
+ # [workers] number of workers
7
+ # [host] host name used in `url_to` helper
8
+ # [root] root path, default is +Dir.pwd+
9
+ # [views] views (templates) directory, relative to root, default is +"views"+
10
+ # [public] static files directory, relative to root, default is +"public"+
11
+ # [x_send_file] header field name for X-Sendfile or X-Accel-Redirect, see Nyara::Controller#send_file for details
12
+ # [session] see Nyara::Session for sub options
13
+ # [prefer_erb] use ERB instead of ERubis for +.erb+ templates
7
14
  Config = ConfigHash.new
8
15
  class << Config
16
+ # clear all settings
9
17
  def reset
10
18
  clear
11
19
  Route.clear
12
20
  end
13
21
 
14
- def map prefix, controller
15
- Route.register_controller prefix, controller
16
- end
22
+ # init and check configures
23
+ def init
24
+ self['env'] ||= 'development'
17
25
 
18
- def port n
19
- n = n.to_i
26
+ n = (self['port'] || 3000).to_i
20
27
  assert n >= 0 && n <= 65535
21
- Config['port'] = n
22
- end
28
+ self['port'] = n
23
29
 
24
- def workers n
25
- n = n.to_i
30
+ n = (self['workers'] || self['worker'] || ((CpuCounter.count + 1)/ 2)).to_i
26
31
  assert n > 0 && n < 1000
27
- Config['workers'] = n
32
+ self['workers'] = n
33
+
34
+ unless self['root']
35
+ set :root, Dir.pwd
36
+ end
37
+ self['root'] = File.expand_path self['root']
38
+
39
+ self['views'] = project_path(self['views'] || 'views')
40
+ self['public'] = project_path(self['public'] || 'public')
41
+
42
+ if self['public']
43
+ map '/', PublicController
44
+ end
45
+ end
46
+
47
+ # get absoute path under project path <br>
48
+ # if +strict+, return nil if path is not under the dir
49
+ def project_path path, strict=true
50
+ path_under 'root', path, strict
51
+ end
52
+
53
+ # get absoute path under public path <br>
54
+ # if +strict+, return nil if path is not under the dir
55
+ def public_path path, strict=true
56
+ path_under 'public', path, strict
57
+ end
58
+
59
+ # get absoute path under views path <br>
60
+ # if +strict+, return nil if path is not under the dir
61
+ def views_path path, strict=true
62
+ path_under 'views', path, strict
63
+ end
64
+
65
+ # get path under the dir configured +Nyara.config[key]+ <br>
66
+ # if +strict+, return nil if path is not under the dir
67
+ def path_under key, path, strict=true
68
+ dir = self[key]
69
+ path = File.expand_path File.join(dir, path)
70
+ if !strict or path.start_with?(dir)
71
+ path
72
+ end
73
+ end
74
+
75
+ # pass requests under a prefix to a controller
76
+ def map prefix, controller
77
+ Route.register_controller prefix, controller
28
78
  end
29
79
 
80
+ # get environment
30
81
  def env
31
82
  self['env'].to_s
32
83
  end
@@ -47,8 +98,8 @@ module Nyara
47
98
  alias set []=
48
99
  alias get []
49
100
 
50
- def assert expr
51
- raise ArgumentError unless expr
101
+ def assert expr # :nodoc:
102
+ raise ArgumentError, "expect #{expr.inspect} to be true", caller[1..-1] unless expr
52
103
  end
53
104
 
54
105
  # todo env aware configure
@@ -93,13 +93,13 @@ module Nyara
93
93
  # +++
94
94
 
95
95
  # Set default layout
96
- def layout l
96
+ def set_default_layout l
97
97
  @default_layout = l
98
98
  end
99
99
  attr_reader :default_layout
100
100
 
101
101
  # Set controller name, so you can use a shorter name to reference the controller in path helper
102
- def set_name n
102
+ def set_controller_name n
103
103
  @controller_name = n
104
104
  end
105
105
  attr_reader :controller_name
@@ -204,6 +204,7 @@ module Nyara
204
204
  Ext.request_send_data r, HTTP_STATUS_FIRST_LINES[r.status]
205
205
  data = header.serialize
206
206
  data.concat r.response_header_extra_lines
207
+ data << Session.encode_set_cookie(r.session, r.ssl?)
207
208
  data << "\r\n"
208
209
  Ext.request_send_data r, data.join
209
210
 
@@ -338,8 +339,8 @@ module Nyara
338
339
 
339
340
  # forbid further modification
340
341
  header.freeze
341
- session.freeze
342
- flash.next.freeze
342
+ r.session.freeze
343
+ r.flash.next.freeze
343
344
  end
344
345
 
345
346
  # Send raw data, that is, not wrapped in chunked encoding<br>
@@ -359,20 +360,77 @@ module Nyara
359
360
  end
360
361
  alias send_string send_chunk
361
362
 
362
- # Send file
363
- def send_file file
364
- if behind_proxy? # todo
365
- header['X-Sendfile'] = file # todo escape name?
366
- # todo content type and disposition
367
- header['Content-Type'] = determine_ct_by_file_name
363
+ # Set aproppriate headers and send the file<br>
364
+ # :call-seq:
365
+ #
366
+ # send_file '/home/www/no-virus-inside.exe', disposition: 'attachment'
367
+ #
368
+ # options are:
369
+ #
370
+ # [disposition] 'inline' by default, if set to 'attachment', the file is presented as a download item in browser.
371
+ # [x_send_file] if not false/nil, it is considered to be behind a web server.
372
+ # Then the app sends file with only header configures,
373
+ # which proxies the actual action to the web server,
374
+ # which can take the advantage of system calls and reduce transfered data,
375
+ # thus faster.
376
+ # [filename] name for the downloaded file, will use basename of +file+ if not set.
377
+ # [content_type] defaults to the MIME type matching +file+ or +filename+.
378
+ #
379
+ # To configure for lighttpd and apache2 mod_xsendfile (https://tn123.org/mod_xsendfile/):
380
+ #
381
+ # configure do
382
+ # set :x_send_file, 'X-Sendfile'
383
+ # end
384
+ #
385
+ # To configure for nginx (http://wiki.nginx.org/XSendfile):
386
+ #
387
+ # configure do
388
+ # set :x_send_file, 'X-Accel-Redirect'
389
+ # end
390
+ #
391
+ # To disable x_send_file while configured:
392
+ #
393
+ # send_file '/some/file', x_send_file: false
394
+ #
395
+ # To enable x_send_file while not configured:
396
+ #
397
+ # send_file '/some/file', x_send_file: 'X-Sendfile'
398
+ #
399
+ def send_file file, disposition: 'inline', x_send_file: Config['x_send_file'], filename: nil, content_type: nil
400
+ header = request.response_header
401
+
402
+ unless header['Content-Type']
403
+ unless content_type
404
+ extname = File.extname(file)
405
+ extname = File.extname(filename) if extname.blank? and filename
406
+ content_type = MIME_TYPES[extname] || 'application/octet-stream'
407
+ end
408
+ header['Content-Type'] = content_type
409
+ end
410
+
411
+ disposition = disposition.to_s
412
+ if disposition != 'inline'
413
+ if disposition != 'attachment'
414
+ raise ArgumentError, "disposition should be inline or attachment, but got #{disposition.inspect}"
415
+ end
416
+ end
417
+
418
+ filename ||= File.basename file
419
+ header['Content-Disposition'] = "#{disposition}; filename=#{Ext.escape filename, true}"
420
+
421
+ header['Transfer-Encoding'] = '' # delete it
422
+
423
+ if x_send_file
424
+ header[x_send_file] = file # todo escape name?
368
425
  send_header unless request.response_header.frozen?
369
426
  else
427
+ # todo nonblock read file?
370
428
  data = File.binread file
371
- header['Content-Type'] = determine_ct_by_file_name
429
+ header['Content-Length'] = data.bytesize
372
430
  send_header unless request.response_header.frozen?
373
431
  send_data data
374
432
  end
375
- Fiber.yield :term_close # is it right? content type changed
433
+ Fiber.yield :term_close
376
434
  end
377
435
 
378
436
  # Resume action after +seconds+
@@ -382,10 +440,10 @@ module Nyara
382
440
 
383
441
  # NOTE request_wake requires request as param, so this method can not be generalized to Fiber.sleep
384
442
 
385
- Ext.request_sleep self # place sleep actions before wake
443
+ Ext.request_sleep request # place sleep actions before wake
386
444
  Thread.new do
387
- sleep seconds
388
- Ext.request_wakeup self
445
+ Kernel.sleep seconds
446
+ Ext.request_wakeup request
389
447
  end
390
448
  Fiber.yield :sleep # see event.c for the handler
391
449
  end
@@ -400,6 +458,9 @@ module Nyara
400
458
  # # with template source, set content type to +text/html+ if not given
401
459
  # render erb: "<%= 1 + 1 %>"
402
460
  #
461
+ # # layout can be string or array
462
+ # render 'index', ['inner_layout', 'outer_layout']
463
+ #
403
464
  # For steam rendering, see #stream
404
465
  def render view_path=nil, layout: self.class.default_layout, locals: nil, **opts
405
466
  view = View.new self, view_path, layout, locals, opts