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.
- checksums.yaml +4 -4
- data/example/project.rb +11 -0
- data/example/stream.rb +6 -2
- data/ext/event.c +83 -32
- data/ext/hashes.c +6 -1
- data/ext/inc/epoll.h +1 -2
- data/ext/inc/kqueue.h +1 -2
- data/ext/nyara.h +2 -0
- data/ext/request.c +14 -9
- data/ext/test_response.c +2 -5
- data/ext/url_encoded.c +55 -1
- data/lib/nyara/config.rb +68 -17
- data/lib/nyara/controller.rb +76 -15
- data/lib/nyara/controllers/public_controller.rb +14 -0
- data/lib/nyara/cookie.rb +5 -4
- data/lib/nyara/flash.rb +2 -0
- data/lib/nyara/nyara.rb +153 -20
- data/lib/nyara/patches/to_query.rb +1 -2
- data/lib/nyara/request.rb +0 -5
- data/lib/nyara/route.rb +2 -2
- data/lib/nyara/route_entry.rb +5 -4
- data/lib/nyara/session.rb +47 -22
- data/lib/nyara/test.rb +13 -10
- data/lib/nyara/view.rb +27 -49
- data/lib/nyara/view_handlers/erb.rb +21 -0
- data/lib/nyara/view_handlers/erubis.rb +81 -0
- data/lib/nyara/view_handlers/haml.rb +17 -0
- data/lib/nyara/view_handlers/slim.rb +16 -0
- data/nyara.gemspec +3 -1
- data/readme.md +2 -2
- data/spec/apps/connect.rb +1 -1
- data/spec/config_spec.rb +76 -4
- data/spec/{test_spec.rb → integration_spec.rb} +47 -3
- data/spec/path_helper_spec.rb +1 -1
- data/spec/performance/escape.rb +10 -0
- data/spec/performance/layout.slim +14 -0
- data/spec/performance/page.slim +16 -0
- data/spec/performance_spec.rb +6 -1
- data/spec/public/empty file.html +0 -0
- data/spec/public/index.html +1 -0
- data/spec/request_delegate_spec.rb +1 -1
- data/spec/request_spec.rb +20 -0
- data/spec/route_entry_spec.rb +7 -0
- data/spec/session_spec.rb +8 -4
- data/spec/spec_helper.rb +1 -0
- data/spec/{ext_parse_spec.rb → url_encoded_spec.rb} +17 -5
- data/spec/views/edit.haml +2 -0
- data/spec/views/edit.slim +2 -0
- data/spec/views/index.liquid +0 -0
- data/spec/views/invalid_layout.liquid +0 -0
- data/spec/views/layout.erb +1 -0
- data/spec/views/show.slim +1 -0
- data/tools/foo.rb +9 -0
- data/tools/hello.rb +16 -11
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1464261ce5d4ec90868a68278e81de0124568897
|
4
|
+
data.tar.gz: 566342b6927ac0fc3f5607580a0863d18b9be432
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c322de1bd85993df20041bd8535800751abdf4b4c8152b7ed4899a0430aed90d4552a97f21839267376f78bac18cc254fd395e9124cc071db8e4b3575ba6537c
|
7
|
+
data.tar.gz: e7c16bb717db1c131069d361087a8d97e96b1282d93d002582aa10cd0bcff01741eedefa0d43ab65c18c9ec480e1c214d3f03218c4569a1bb3786194c43c46e8
|
data/example/project.rb
ADDED
data/example/stream.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
168
|
-
|
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
|
-
|
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
data/ext/inc/kqueue.h
CHANGED
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 (
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
|
-
|
15
|
-
|
16
|
-
|
22
|
+
# init and check configures
|
23
|
+
def init
|
24
|
+
self['env'] ||= 'development'
|
17
25
|
|
18
|
-
|
19
|
-
n = n.to_i
|
26
|
+
n = (self['port'] || 3000).to_i
|
20
27
|
assert n >= 0 && n <= 65535
|
21
|
-
|
22
|
-
end
|
28
|
+
self['port'] = n
|
23
29
|
|
24
|
-
|
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
|
-
|
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
|
data/lib/nyara/controller.rb
CHANGED
@@ -93,13 +93,13 @@ module Nyara
|
|
93
93
|
# +++
|
94
94
|
|
95
95
|
# Set default layout
|
96
|
-
def
|
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
|
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
|
-
#
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
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-
|
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
|
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
|
443
|
+
Ext.request_sleep request # place sleep actions before wake
|
386
444
|
Thread.new do
|
387
|
-
sleep seconds
|
388
|
-
Ext.request_wakeup
|
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
|