nyara 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/example/design.rb +62 -0
- data/example/fib.rb +15 -0
- data/example/hello.rb +5 -0
- data/example/stream.rb +10 -0
- data/ext/accept.c +133 -0
- data/ext/event.c +89 -0
- data/ext/extconf.rb +34 -0
- data/ext/hashes.c +130 -0
- data/ext/http-parser/AUTHORS +41 -0
- data/ext/http-parser/CONTRIBUTIONS +4 -0
- data/ext/http-parser/LICENSE-MIT +23 -0
- data/ext/http-parser/contrib/parsertrace.c +156 -0
- data/ext/http-parser/contrib/url_parser.c +44 -0
- data/ext/http-parser/http_parser.c +2175 -0
- data/ext/http-parser/http_parser.h +304 -0
- data/ext/http-parser/test.c +3425 -0
- data/ext/http_parser.c +1 -0
- data/ext/inc/epoll.h +60 -0
- data/ext/inc/kqueue.h +77 -0
- data/ext/inc/status_codes.inc +64 -0
- data/ext/inc/str_intern.h +66 -0
- data/ext/inc/version.inc +1 -0
- data/ext/mime.c +107 -0
- data/ext/multipart-parser-c/README.md +18 -0
- data/ext/multipart-parser-c/multipart_parser.c +309 -0
- data/ext/multipart-parser-c/multipart_parser.h +48 -0
- data/ext/multipart_parser.c +1 -0
- data/ext/nyara.c +56 -0
- data/ext/nyara.h +59 -0
- data/ext/request.c +474 -0
- data/ext/route.cc +325 -0
- data/ext/url_encoded.c +304 -0
- data/hello.rb +5 -0
- data/lib/nyara/config.rb +64 -0
- data/lib/nyara/config_hash.rb +51 -0
- data/lib/nyara/controller.rb +336 -0
- data/lib/nyara/cookie.rb +31 -0
- data/lib/nyara/cpu_counter.rb +65 -0
- data/lib/nyara/header_hash.rb +18 -0
- data/lib/nyara/mime_types.rb +612 -0
- data/lib/nyara/nyara.rb +82 -0
- data/lib/nyara/param_hash.rb +5 -0
- data/lib/nyara/request.rb +144 -0
- data/lib/nyara/route.rb +138 -0
- data/lib/nyara/route_entry.rb +43 -0
- data/lib/nyara/session.rb +104 -0
- data/lib/nyara/view.rb +317 -0
- data/lib/nyara.rb +25 -0
- data/nyara.gemspec +20 -0
- data/rakefile +91 -0
- data/readme.md +35 -0
- data/spec/ext_mime_match_spec.rb +27 -0
- data/spec/ext_parse_accept_value_spec.rb +29 -0
- data/spec/ext_parse_spec.rb +138 -0
- data/spec/ext_route_spec.rb +70 -0
- data/spec/hashes_spec.rb +71 -0
- data/spec/path_helper_spec.rb +77 -0
- data/spec/request_delegate_spec.rb +67 -0
- data/spec/request_spec.rb +56 -0
- data/spec/route_entry_spec.rb +12 -0
- data/spec/route_spec.rb +84 -0
- data/spec/session_spec.rb +66 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/view_spec.rb +87 -0
- data/tools/bench-cookie.rb +22 -0
- metadata +111 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
/* Based on node-formidable by Felix Geisendörfer
|
2
|
+
* Igor Afonov - afonov@gmail.com - 2012
|
3
|
+
* MIT License - http://www.opensource.org/licenses/mit-license.php
|
4
|
+
*/
|
5
|
+
#ifndef _multipart_parser_h
|
6
|
+
#define _multipart_parser_h
|
7
|
+
|
8
|
+
#ifdef __cplusplus
|
9
|
+
extern "C"
|
10
|
+
{
|
11
|
+
#endif
|
12
|
+
|
13
|
+
#include <stdlib.h>
|
14
|
+
#include <ctype.h>
|
15
|
+
|
16
|
+
typedef struct multipart_parser multipart_parser;
|
17
|
+
typedef struct multipart_parser_settings multipart_parser_settings;
|
18
|
+
typedef struct multipart_parser_state multipart_parser_state;
|
19
|
+
|
20
|
+
typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
|
21
|
+
typedef int (*multipart_notify_cb) (multipart_parser*);
|
22
|
+
|
23
|
+
struct multipart_parser_settings {
|
24
|
+
multipart_data_cb on_header_field;
|
25
|
+
multipart_data_cb on_header_value;
|
26
|
+
multipart_data_cb on_part_data;
|
27
|
+
|
28
|
+
multipart_notify_cb on_part_data_begin;
|
29
|
+
multipart_notify_cb on_headers_complete;
|
30
|
+
multipart_notify_cb on_part_data_end;
|
31
|
+
multipart_notify_cb on_body_end;
|
32
|
+
};
|
33
|
+
|
34
|
+
multipart_parser* multipart_parser_init
|
35
|
+
(const char *boundary, const multipart_parser_settings* settings);
|
36
|
+
|
37
|
+
void multipart_parser_free(multipart_parser* p);
|
38
|
+
|
39
|
+
size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len);
|
40
|
+
|
41
|
+
void multipart_parser_set_data(multipart_parser* p, void* data);
|
42
|
+
void * multipart_parser_get_data(multipart_parser* p);
|
43
|
+
|
44
|
+
#ifdef __cplusplus
|
45
|
+
} /* extern "C" */
|
46
|
+
#endif
|
47
|
+
|
48
|
+
#endif
|
@@ -0,0 +1 @@
|
|
1
|
+
#include "multipart-parser-c/multipart_parser.c"
|
data/ext/nyara.c
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#include "nyara.h"
|
2
|
+
#include <ruby/io.h>
|
3
|
+
#include <sys/socket.h>
|
4
|
+
#include <sys/resource.h>
|
5
|
+
|
6
|
+
static void set_fd_limit(int nofiles) {
|
7
|
+
struct rlimit rlim;
|
8
|
+
getrlimit (RLIMIT_NOFILE, &rlim);
|
9
|
+
if (nofiles >= 0) {
|
10
|
+
rlim.rlim_cur = nofiles;
|
11
|
+
if ((unsigned int)nofiles > rlim.rlim_max)
|
12
|
+
rlim.rlim_max = nofiles;
|
13
|
+
setrlimit (RLIMIT_NOFILE, &rlim);
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
void Init_nyara() {
|
18
|
+
set_fd_limit(20000);
|
19
|
+
|
20
|
+
VALUE nyara = rb_define_module("Nyara");
|
21
|
+
|
22
|
+
// utils: hashes
|
23
|
+
Init_hashes(nyara);
|
24
|
+
|
25
|
+
// utils: method map
|
26
|
+
volatile VALUE method_map = rb_class_new_instance(0, NULL, nyara_param_hash_class);
|
27
|
+
rb_const_set(nyara, rb_intern("HTTP_METHODS"), method_map);
|
28
|
+
VALUE tmp_key = Qnil;
|
29
|
+
# define METHOD_STR2NUM(n, name, string) \
|
30
|
+
tmp_key = rb_str_new2(#string);\
|
31
|
+
OBJ_FREEZE(tmp_key);\
|
32
|
+
rb_hash_aset(method_map, tmp_key, INT2FIX(n));
|
33
|
+
HTTP_METHOD_MAP(METHOD_STR2NUM);
|
34
|
+
# undef METHOD_STR2NUM
|
35
|
+
OBJ_FREEZE(method_map);
|
36
|
+
|
37
|
+
// utils: status codes
|
38
|
+
volatile VALUE status_map = rb_hash_new();
|
39
|
+
rb_const_set(nyara, rb_intern("HTTP_STATUS_CODES"), status_map);
|
40
|
+
VALUE tmp_value = Qnil;
|
41
|
+
# define STATUS_DESC(status, desc) \
|
42
|
+
tmp_value = rb_str_new2(desc);\
|
43
|
+
OBJ_FREEZE(tmp_value);\
|
44
|
+
rb_hash_aset(status_map, INT2FIX(status), tmp_value);
|
45
|
+
HTTP_STATUS_CODES(STATUS_DESC);
|
46
|
+
# undef STATUS_DESC
|
47
|
+
OBJ_FREEZE(status_map);
|
48
|
+
|
49
|
+
VALUE ext = rb_define_module_under(nyara, "Ext");
|
50
|
+
Init_accept(ext);
|
51
|
+
Init_mime(ext);
|
52
|
+
Init_request(nyara, ext);
|
53
|
+
Init_event(ext);
|
54
|
+
Init_route(nyara, ext);
|
55
|
+
Init_url_encoded(ext);
|
56
|
+
}
|
data/ext/nyara.h
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#pragma once
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <stdbool.h>
|
4
|
+
#include <http_parser.h>
|
5
|
+
#include "inc/status_codes.inc"
|
6
|
+
|
7
|
+
#ifdef DEBUG
|
8
|
+
#undef NDEBUG
|
9
|
+
#endif
|
10
|
+
|
11
|
+
|
12
|
+
/* -- event -- */
|
13
|
+
void Init_event(VALUE ext);
|
14
|
+
|
15
|
+
|
16
|
+
/* -- request & response class -- */
|
17
|
+
void Init_request(VALUE nyara, VALUE ext);
|
18
|
+
void nyara_handle_request(int fd);
|
19
|
+
|
20
|
+
|
21
|
+
/* -- url encoded parse -- */
|
22
|
+
void Init_url_encoded(VALUE ext);
|
23
|
+
size_t nyara_parse_path(VALUE path, const char*s, size_t len);
|
24
|
+
void nyara_parse_param(VALUE output, const char* s, size_t len);
|
25
|
+
|
26
|
+
|
27
|
+
/* -- accept parse -- */
|
28
|
+
void Init_accept(VALUE ext);
|
29
|
+
VALUE ext_parse_accept_value(VALUE _, VALUE str);
|
30
|
+
|
31
|
+
|
32
|
+
/* -- mime parse and match -- */
|
33
|
+
void Init_mime(VALUE ext);
|
34
|
+
VALUE ext_mime_match(VALUE _, VALUE request_accept, VALUE accept_mimes);
|
35
|
+
|
36
|
+
|
37
|
+
/* -- hashes -- */
|
38
|
+
void Init_hashes(VALUE nyara);
|
39
|
+
|
40
|
+
// ab-cd => Ab-Cd
|
41
|
+
// note str must be string created by nyara code
|
42
|
+
void nyara_headerlize(VALUE str);
|
43
|
+
int nyara_rb_hash_has_key(VALUE hash, VALUE key);
|
44
|
+
|
45
|
+
extern VALUE nyara_param_hash_class;
|
46
|
+
extern VALUE nyara_header_hash_class;
|
47
|
+
extern VALUE nyara_config_hash_class;
|
48
|
+
|
49
|
+
|
50
|
+
/* -- route -- */
|
51
|
+
typedef struct {
|
52
|
+
VALUE controller;
|
53
|
+
VALUE args;
|
54
|
+
VALUE scope;
|
55
|
+
VALUE ext; // maybe string or map
|
56
|
+
} RouteResult;
|
57
|
+
|
58
|
+
extern void Init_route(VALUE nyara, VALUE ext);
|
59
|
+
extern RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath);
|
data/ext/request.c
ADDED
@@ -0,0 +1,474 @@
|
|
1
|
+
#include "nyara.h"
|
2
|
+
#include <ruby/encoding.h>
|
3
|
+
#include <multipart_parser.h>
|
4
|
+
#include <errno.h>
|
5
|
+
#ifndef write
|
6
|
+
#include <unistd.h>
|
7
|
+
#endif
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
http_parser hparser;
|
11
|
+
multipart_parser* mparser;
|
12
|
+
enum http_method method;
|
13
|
+
VALUE header;
|
14
|
+
VALUE fiber;
|
15
|
+
VALUE scope; // mapped prefix
|
16
|
+
VALUE path;
|
17
|
+
VALUE param;
|
18
|
+
VALUE last_field;
|
19
|
+
VALUE last_value;
|
20
|
+
// string if determined by url, or hash if need further check with header[Accept]
|
21
|
+
VALUE ext;
|
22
|
+
VALUE self;
|
23
|
+
int fd;
|
24
|
+
|
25
|
+
// response
|
26
|
+
int status;
|
27
|
+
VALUE response_content_type;
|
28
|
+
VALUE response_header;
|
29
|
+
VALUE response_header_extra_lines;
|
30
|
+
} Request;
|
31
|
+
|
32
|
+
// typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
33
|
+
// typedef int (*http_cb) (http_parser*);
|
34
|
+
|
35
|
+
static ID id_not_found;
|
36
|
+
static VALUE str_html;
|
37
|
+
static rb_encoding* u8_encoding;
|
38
|
+
static VALUE request_class;
|
39
|
+
static VALUE method_override_key;
|
40
|
+
static VALUE str_accept;
|
41
|
+
static VALUE nyara_http_methods;
|
42
|
+
|
43
|
+
static VALUE fd_request_map;
|
44
|
+
#define MAX_RECEIVE_DATA 65536
|
45
|
+
static char received_data[MAX_RECEIVE_DATA];
|
46
|
+
|
47
|
+
static VALUE fiber_func(VALUE _, VALUE args) {
|
48
|
+
VALUE instance = rb_ary_pop(args);
|
49
|
+
VALUE meth = rb_ary_pop(args);
|
50
|
+
rb_apply(instance, SYM2ID(meth), args);
|
51
|
+
return Qnil;
|
52
|
+
}
|
53
|
+
|
54
|
+
static void _upcase_method(VALUE str) {
|
55
|
+
char* s = RSTRING_PTR(str);
|
56
|
+
long len = RSTRING_LEN(str);
|
57
|
+
for (long i = 0; i < len; i++) {
|
58
|
+
if (s[i] >= 'a' && s[i] <= 'z') {
|
59
|
+
s[i] = 'A' + (s[i] - 'a');
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
static inline void _close_fd(int fd) {
|
65
|
+
rb_hash_delete(fd_request_map, INT2FIX(fd));
|
66
|
+
close(fd);
|
67
|
+
}
|
68
|
+
|
69
|
+
// fixme assume url is always sent as whole (tcp window is large!)
|
70
|
+
static int on_url(http_parser* parser, const char* s, size_t len) {
|
71
|
+
Request* p = (Request*)parser;
|
72
|
+
p->method = parser->method;
|
73
|
+
|
74
|
+
// matching raw path is bad idea, for example: %a0 and %A0 are different strings but same route
|
75
|
+
p->path = rb_enc_str_new("", 0, u8_encoding);
|
76
|
+
size_t query_i = nyara_parse_path(p->path, s, len);
|
77
|
+
p->param = rb_class_new_instance(0, NULL, nyara_param_hash_class);
|
78
|
+
if (query_i < len) {
|
79
|
+
nyara_parse_param(p->param, s + query_i, len - query_i);
|
80
|
+
|
81
|
+
// do method override with _method=xxx in query
|
82
|
+
if (p->method == HTTP_POST) {
|
83
|
+
VALUE meth = rb_hash_aref(p->param, method_override_key);
|
84
|
+
if (TYPE(meth) == T_STRING) {
|
85
|
+
_upcase_method(meth);
|
86
|
+
VALUE meth_num = rb_hash_aref(nyara_http_methods, meth);
|
87
|
+
if (meth_num != Qnil) {
|
88
|
+
p->method = FIX2INT(meth_num);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
volatile RouteResult result = nyara_lookup_route(p->method, p->path);
|
95
|
+
if (RTEST(result.controller)) {
|
96
|
+
{
|
97
|
+
VALUE instance = rb_class_new_instance(1, &(p->self), result.controller);
|
98
|
+
rb_ary_push(result.args, instance);
|
99
|
+
}
|
100
|
+
// result.args is on stack, no need to worry gc
|
101
|
+
p->fiber = rb_fiber_new(fiber_func, result.args);
|
102
|
+
p->scope = result.scope;
|
103
|
+
p->header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
|
104
|
+
p->ext = result.ext;
|
105
|
+
p->response_header = rb_class_new_instance(0, NULL, nyara_header_hash_class);
|
106
|
+
p->response_header_extra_lines = rb_ary_new();
|
107
|
+
return 0;
|
108
|
+
} else {
|
109
|
+
rb_funcall(p->self, id_not_found, 0);
|
110
|
+
return 1;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
static int on_message_complete(http_parser* parser) {
|
115
|
+
Request* p = (Request*)parser;
|
116
|
+
if (p->fiber == Qnil) {
|
117
|
+
return 1;
|
118
|
+
} else {
|
119
|
+
VALUE state = rb_fiber_resume(p->fiber, 0, NULL);
|
120
|
+
if (state == Qnil) { // terminated (todo check raise error)
|
121
|
+
if (p->status == 200) {
|
122
|
+
write(p->fd, "0\r\n\r\n", 5);
|
123
|
+
}
|
124
|
+
_close_fd(p->fd);
|
125
|
+
p->fd = 0;
|
126
|
+
} else if (SYM2ID(state) == rb_intern("term_close")) {
|
127
|
+
write(p->fd, "0\r\n\r\n", 5);
|
128
|
+
_close_fd(p->fd);
|
129
|
+
p->fd = 0;
|
130
|
+
}
|
131
|
+
return 0;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
static int on_header_field(http_parser* parser, const char* s, size_t len) {
|
136
|
+
Request* p = (Request*)parser;
|
137
|
+
if (p->last_field == Qnil) {
|
138
|
+
p->last_field = rb_str_new(s, len);
|
139
|
+
p->last_value = Qnil;
|
140
|
+
} else {
|
141
|
+
rb_str_cat(p->last_field, s, len);
|
142
|
+
}
|
143
|
+
return 0;
|
144
|
+
}
|
145
|
+
|
146
|
+
static int on_header_value(http_parser* parser, const char* s, size_t len) {
|
147
|
+
Request* p = (Request*)parser;
|
148
|
+
if (p->last_field == Qnil) {
|
149
|
+
if (p->last_value == Qnil) {
|
150
|
+
rb_bug("on_header_value called when neither last_field nor last_value exist");
|
151
|
+
return 1;
|
152
|
+
}
|
153
|
+
rb_str_cat(p->last_value, s, len);
|
154
|
+
} else {
|
155
|
+
nyara_headerlize(p->last_field);
|
156
|
+
p->last_value = rb_str_new(s, len);
|
157
|
+
rb_hash_aset(p->header, p->last_field, p->last_value);
|
158
|
+
p->last_field = Qnil;
|
159
|
+
}
|
160
|
+
return 0;
|
161
|
+
}
|
162
|
+
|
163
|
+
static int on_headers_complete(http_parser* parser) {
|
164
|
+
Request* p = (Request*)parser;
|
165
|
+
p->last_field = Qnil;
|
166
|
+
p->last_value = Qnil;
|
167
|
+
|
168
|
+
if (TYPE(p->ext) != T_STRING) {
|
169
|
+
VALUE accept = rb_hash_aref(p->header, str_accept);
|
170
|
+
if (RTEST(accept)) {
|
171
|
+
accept = ext_parse_accept_value(Qnil, accept);
|
172
|
+
rb_iv_set(p->self, "@accept", accept);
|
173
|
+
p->ext = ext_mime_match(Qnil, accept, p->ext);
|
174
|
+
}
|
175
|
+
if (p->ext == Qnil) {
|
176
|
+
rb_funcall(p->self, id_not_found, 0);
|
177
|
+
return 1;
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
return 0;
|
182
|
+
}
|
183
|
+
|
184
|
+
static http_parser_settings request_settings = {
|
185
|
+
.on_message_begin = NULL,
|
186
|
+
.on_url = on_url,
|
187
|
+
.on_status_complete = NULL,
|
188
|
+
.on_header_field = on_header_field,
|
189
|
+
.on_header_value = on_header_value,
|
190
|
+
.on_headers_complete = on_headers_complete,
|
191
|
+
.on_body = NULL, // data_cb
|
192
|
+
.on_message_complete = on_message_complete
|
193
|
+
};
|
194
|
+
|
195
|
+
static void request_mark(void* pp) {
|
196
|
+
Request* p = pp;
|
197
|
+
if (p) {
|
198
|
+
rb_gc_mark_maybe(p->header);
|
199
|
+
rb_gc_mark_maybe(p->fiber);
|
200
|
+
rb_gc_mark_maybe(p->scope);
|
201
|
+
rb_gc_mark_maybe(p->path);
|
202
|
+
rb_gc_mark_maybe(p->param);
|
203
|
+
rb_gc_mark_maybe(p->last_field);
|
204
|
+
rb_gc_mark_maybe(p->last_value);
|
205
|
+
rb_gc_mark_maybe(p->ext);
|
206
|
+
rb_gc_mark_maybe(p->response_content_type);
|
207
|
+
rb_gc_mark_maybe(p->response_header);
|
208
|
+
rb_gc_mark_maybe(p->response_header_extra_lines);
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
static void request_free(void* pp) {
|
213
|
+
Request* p = pp;
|
214
|
+
if (p) {
|
215
|
+
if (p->fd) {
|
216
|
+
_close_fd(p->fd);
|
217
|
+
}
|
218
|
+
xfree(p);
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
222
|
+
static Request* request_alloc() {
|
223
|
+
Request* p = ALLOC(Request);
|
224
|
+
http_parser_init(&(p->hparser), HTTP_REQUEST);
|
225
|
+
p->mparser = NULL;
|
226
|
+
p->header = Qnil;
|
227
|
+
p->fiber = Qnil;
|
228
|
+
p->scope = Qnil;
|
229
|
+
p->path = Qnil;
|
230
|
+
p->param = Qnil;
|
231
|
+
p->last_field = Qnil;
|
232
|
+
p->last_value = Qnil;
|
233
|
+
p->ext = Qnil;
|
234
|
+
p->fd = 0;
|
235
|
+
p->status = 200;
|
236
|
+
p->response_content_type = Qnil;
|
237
|
+
p->response_header = Qnil;
|
238
|
+
p->response_header_extra_lines = Qnil;
|
239
|
+
p->self = Data_Wrap_Struct(request_class, request_mark, request_free, p);
|
240
|
+
return p;
|
241
|
+
}
|
242
|
+
|
243
|
+
static VALUE request_alloc_func(VALUE klass) {
|
244
|
+
return request_alloc()->self;
|
245
|
+
}
|
246
|
+
|
247
|
+
/* client entrance
|
248
|
+
invoke order:
|
249
|
+
- find/create request
|
250
|
+
- http_parser_execute
|
251
|
+
- on_url
|
252
|
+
- on_message_complete
|
253
|
+
*/
|
254
|
+
void nyara_handle_request(int fd) {
|
255
|
+
Request* p = NULL;
|
256
|
+
bool first_time = false;
|
257
|
+
|
258
|
+
{
|
259
|
+
VALUE v_fd = INT2FIX(fd);
|
260
|
+
VALUE request = rb_hash_aref(fd_request_map, v_fd);
|
261
|
+
if (request == Qnil) {
|
262
|
+
p = request_alloc();
|
263
|
+
p->fd = fd;
|
264
|
+
rb_hash_aset(fd_request_map, v_fd, p->self);
|
265
|
+
first_time = true;
|
266
|
+
} else {
|
267
|
+
Data_Get_Struct(request, Request, p);
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
long len = read(fd, received_data, MAX_RECEIVE_DATA);
|
272
|
+
if (len < 0) {
|
273
|
+
if (errno != EAGAIN) {
|
274
|
+
// todo log the bug
|
275
|
+
if (p->fd) {
|
276
|
+
_close_fd(p->fd);
|
277
|
+
p->fd = 0;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
} else {
|
281
|
+
if (first_time && !len) {
|
282
|
+
// todo log this exception
|
283
|
+
return;
|
284
|
+
}
|
285
|
+
// note: when len == 0, means eof reached, that also informs http_parser the eof
|
286
|
+
http_parser_execute(&(p->hparser), &request_settings, received_data, len);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
#define P \
|
291
|
+
Request* p;\
|
292
|
+
Data_Get_Struct(self, Request, p);
|
293
|
+
|
294
|
+
static VALUE request_http_method(VALUE self) {
|
295
|
+
P;
|
296
|
+
return rb_str_new2(http_method_str(p->method));
|
297
|
+
}
|
298
|
+
|
299
|
+
static VALUE request_header(VALUE self) {
|
300
|
+
P;
|
301
|
+
return p->header;
|
302
|
+
}
|
303
|
+
|
304
|
+
static VALUE request_scope(VALUE self) {
|
305
|
+
P;
|
306
|
+
return p->scope;
|
307
|
+
}
|
308
|
+
|
309
|
+
static VALUE request_path(VALUE self) {
|
310
|
+
P;
|
311
|
+
return p->path;
|
312
|
+
}
|
313
|
+
|
314
|
+
static VALUE request_matched_accept(VALUE _, VALUE self) {
|
315
|
+
P;
|
316
|
+
return p->ext == Qnil ? str_html : p->ext;
|
317
|
+
}
|
318
|
+
|
319
|
+
static VALUE request_status(VALUE self) {
|
320
|
+
P;
|
321
|
+
return INT2FIX(p->status);
|
322
|
+
}
|
323
|
+
|
324
|
+
static VALUE request_response_content_type(VALUE self) {
|
325
|
+
P;
|
326
|
+
return p->response_content_type;
|
327
|
+
}
|
328
|
+
|
329
|
+
static VALUE request_response_content_type_eq(VALUE self, VALUE ct) {
|
330
|
+
P;
|
331
|
+
p->response_content_type = ct;
|
332
|
+
return self;
|
333
|
+
}
|
334
|
+
|
335
|
+
static VALUE request_response_header(VALUE self) {
|
336
|
+
P;
|
337
|
+
return p->response_header;
|
338
|
+
}
|
339
|
+
|
340
|
+
static VALUE request_response_header_extra_lines(VALUE self) {
|
341
|
+
P;
|
342
|
+
return p->response_header_extra_lines;
|
343
|
+
}
|
344
|
+
|
345
|
+
static VALUE ext_request_set_status(VALUE _, VALUE self, VALUE n) {
|
346
|
+
P;
|
347
|
+
p->status = NUM2INT(n);
|
348
|
+
return n;
|
349
|
+
}
|
350
|
+
|
351
|
+
static VALUE ext_request_param(VALUE _, VALUE self) {
|
352
|
+
P;
|
353
|
+
return p->param;
|
354
|
+
}
|
355
|
+
|
356
|
+
static VALUE ext_send_data(VALUE _, VALUE self, VALUE data) {
|
357
|
+
P;
|
358
|
+
char* buf = RSTRING_PTR(data);
|
359
|
+
long len = RSTRING_LEN(data);
|
360
|
+
|
361
|
+
while(len) {
|
362
|
+
long written = write(p->fd, buf, len);
|
363
|
+
if (written == 0)
|
364
|
+
return Qnil;
|
365
|
+
if (written == -1) {
|
366
|
+
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
367
|
+
// todo enqueue data and set state
|
368
|
+
}
|
369
|
+
return Qnil;
|
370
|
+
}
|
371
|
+
buf += written;
|
372
|
+
len -= written;
|
373
|
+
}
|
374
|
+
return Qnil;
|
375
|
+
}
|
376
|
+
|
377
|
+
static VALUE ext_send_chunk(VALUE _, VALUE self, VALUE str) {
|
378
|
+
long len = RSTRING_LEN(str);
|
379
|
+
if (!len) {
|
380
|
+
return Qnil;
|
381
|
+
}
|
382
|
+
// todo len overflow?
|
383
|
+
P;
|
384
|
+
long res = dprintf(p->fd, "%lx\r\n%.*s\r\n", len, (int)len, RSTRING_PTR(str));
|
385
|
+
if (res < 0) {
|
386
|
+
rb_raise(rb_eRuntimeError, "%s", strerror(errno));
|
387
|
+
}
|
388
|
+
return Qnil;
|
389
|
+
}
|
390
|
+
|
391
|
+
// for test: find or create a request with a fd
|
392
|
+
static VALUE ext_handle_request(VALUE _, VALUE v_fd) {
|
393
|
+
int fd = FIX2INT(v_fd);
|
394
|
+
nyara_handle_request(fd);
|
395
|
+
return rb_hash_aref(fd_request_map, v_fd);
|
396
|
+
}
|
397
|
+
|
398
|
+
// set internal attrs in the request object
|
399
|
+
static VALUE ext_set_request_attrs(VALUE _, VALUE self, VALUE attrs) {
|
400
|
+
# define ATTR(key) rb_hash_aref(attrs, ID2SYM(rb_intern(key)))
|
401
|
+
# define HEADER_HASH_NEW rb_class_new_instance(0, NULL, nyara_header_hash_class)
|
402
|
+
P;
|
403
|
+
|
404
|
+
if (ATTR("method_num") == Qnil) {
|
405
|
+
rb_raise(rb_eArgError, "bad method_num");
|
406
|
+
}
|
407
|
+
|
408
|
+
p->method = NUM2INT(ATTR("method_num"));
|
409
|
+
p->path = ATTR("path");
|
410
|
+
p->param = ATTR("param");
|
411
|
+
p->fiber = ATTR("fiber");
|
412
|
+
p->scope = ATTR("scope");
|
413
|
+
p->header = RTEST(ATTR("header")) ? ATTR("header") : HEADER_HASH_NEW;
|
414
|
+
p->ext = ATTR("ext");
|
415
|
+
p->response_header = RTEST(ATTR("response_header")) ? ATTR("response_header") : HEADER_HASH_NEW;
|
416
|
+
if (RTEST(ATTR("response_header_extra_lines"))) {
|
417
|
+
p->response_header_extra_lines = ATTR("response_header_extra_lines");
|
418
|
+
} else {
|
419
|
+
p->response_header_extra_lines = rb_ary_new();
|
420
|
+
}
|
421
|
+
return self;
|
422
|
+
# undef HEADER_HASH_NEW
|
423
|
+
# undef ATTR
|
424
|
+
}
|
425
|
+
|
426
|
+
// skip on_url so we can focus on testing request
|
427
|
+
static VALUE ext_set_skip_on_url(VALUE _, VALUE pred) {
|
428
|
+
if (RTEST(pred)) {
|
429
|
+
request_settings.on_url = NULL;
|
430
|
+
} else {
|
431
|
+
request_settings.on_url = on_url;
|
432
|
+
}
|
433
|
+
return Qnil;
|
434
|
+
}
|
435
|
+
|
436
|
+
void Init_request(VALUE nyara, VALUE ext) {
|
437
|
+
id_not_found = rb_intern("not_found");
|
438
|
+
str_html = rb_str_new2("html");
|
439
|
+
OBJ_FREEZE(str_html);
|
440
|
+
rb_gc_register_mark_object(str_html);
|
441
|
+
u8_encoding = rb_utf8_encoding();
|
442
|
+
method_override_key = rb_str_new2("_method");
|
443
|
+
rb_const_set(nyara, rb_intern("METHOD_OVERRIDE_KEY"), method_override_key);
|
444
|
+
nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
|
445
|
+
fd_request_map = rb_hash_new();
|
446
|
+
rb_gc_register_mark_object(fd_request_map);
|
447
|
+
str_accept = rb_str_new2("Accept");
|
448
|
+
rb_gc_register_mark_object(fd_request_map);
|
449
|
+
|
450
|
+
// request
|
451
|
+
request_class = rb_define_class_under(nyara, "Request", rb_cObject);
|
452
|
+
rb_define_alloc_func(request_class, request_alloc_func);
|
453
|
+
rb_define_method(request_class, "http_method", request_http_method, 0);
|
454
|
+
rb_define_method(request_class, "header", request_header, 0);
|
455
|
+
rb_define_method(request_class, "scope", request_scope, 0);
|
456
|
+
rb_define_method(request_class, "path", request_path, 0);
|
457
|
+
rb_define_method(request_class, "matched_accept", request_matched_accept, 0);
|
458
|
+
|
459
|
+
rb_define_method(request_class, "status", request_status, 0);
|
460
|
+
rb_define_method(request_class, "response_content_type", request_response_content_type, 0);
|
461
|
+
rb_define_method(request_class, "response_content_type=", request_response_content_type_eq, 1);
|
462
|
+
rb_define_method(request_class, "response_header", request_response_header, 0);
|
463
|
+
rb_define_method(request_class, "response_header_extra_lines", request_response_header_extra_lines, 0);
|
464
|
+
|
465
|
+
// hide internal methods in ext
|
466
|
+
rb_define_singleton_method(ext, "request_set_status", ext_request_set_status, 2);
|
467
|
+
rb_define_singleton_method(ext, "request_param", ext_request_param, 1);
|
468
|
+
rb_define_singleton_method(ext, "send_data", ext_send_data, 2);
|
469
|
+
rb_define_singleton_method(ext, "send_chunk", ext_send_chunk, 2);
|
470
|
+
// for test
|
471
|
+
rb_define_singleton_method(ext, "handle_request", ext_handle_request, 1);
|
472
|
+
rb_define_singleton_method(ext, "set_request_attrs", ext_set_request_attrs, 2);
|
473
|
+
rb_define_singleton_method(ext, "set_skip_on_url", ext_set_skip_on_url, 1);
|
474
|
+
}
|