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

Sign up to get free protection for your applications and to get access to all the features.
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