agoo 2.4.0 → 2.5.0
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.
Potentially problematic release.
This version of agoo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/ext/agoo/agoo.c +4 -3
- data/ext/agoo/bind.c +275 -0
- data/ext/agoo/bind.h +35 -0
- data/ext/agoo/con.c +22 -25
- data/ext/agoo/debug.c +2 -0
- data/ext/agoo/debug.h +1 -0
- data/ext/agoo/error_stream.c +1 -0
- data/ext/agoo/hook.c +16 -6
- data/ext/agoo/hook.h +3 -1
- data/ext/agoo/http.c +7 -7
- data/ext/agoo/http.h +3 -1
- data/ext/agoo/log.h +1 -0
- data/ext/agoo/page.c +231 -104
- data/ext/agoo/page.h +10 -38
- data/ext/agoo/request.c +5 -1
- data/ext/agoo/request.h +1 -2
- data/ext/agoo/response.c +5 -1
- data/ext/agoo/rhook.c +2 -2
- data/ext/agoo/rhook.h +1 -1
- data/ext/agoo/rserver.c +991 -0
- data/ext/agoo/rserver.h +42 -0
- data/ext/agoo/server.c +95 -978
- data/ext/agoo/server.h +16 -27
- data/ext/agoo/upgraded.c +28 -20
- data/ext/agoo/websocket.c +4 -7
- data/lib/agoo/version.rb +1 -1
- data/test/bind_test.rb +134 -0
- data/test/named_client.rb +7 -0
- data/test/named_server.rb +13 -0
- metadata +12 -2
data/ext/agoo/page.h
CHANGED
@@ -9,37 +9,14 @@
|
|
9
9
|
#include "err.h"
|
10
10
|
#include "text.h"
|
11
11
|
|
12
|
-
#define MAX_KEY_LEN 1024
|
13
|
-
#define PAGE_BUCKET_SIZE 1024
|
14
|
-
#define PAGE_BUCKET_MASK 1023
|
15
|
-
|
16
|
-
#define MAX_MIME_KEY_LEN 15
|
17
|
-
#define MIME_BUCKET_SIZE 64
|
18
|
-
#define MIME_BUCKET_MASK 63
|
19
|
-
|
20
12
|
typedef struct _Page {
|
21
13
|
Text resp;
|
22
14
|
char *path;
|
23
15
|
time_t mtime;
|
24
16
|
double last_check;
|
17
|
+
bool immutable;
|
25
18
|
} *Page;
|
26
19
|
|
27
|
-
typedef struct _Slot {
|
28
|
-
struct _Slot *next;
|
29
|
-
char key[MAX_KEY_LEN + 1];
|
30
|
-
Page value;
|
31
|
-
uint64_t hash;
|
32
|
-
int klen;
|
33
|
-
} *Slot;
|
34
|
-
|
35
|
-
typedef struct _MimeSlot {
|
36
|
-
struct _MimeSlot *next;
|
37
|
-
char key[MAX_MIME_KEY_LEN + 1];
|
38
|
-
char *value;
|
39
|
-
uint64_t hash;
|
40
|
-
int klen;
|
41
|
-
} *MimeSlot;
|
42
|
-
|
43
20
|
typedef struct _Dir {
|
44
21
|
struct _Dir *next;
|
45
22
|
char *path;
|
@@ -53,22 +30,17 @@ typedef struct _Group {
|
|
53
30
|
Dir dirs;
|
54
31
|
} *Group;
|
55
32
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
char *root;
|
60
|
-
Group groups;
|
61
|
-
} *Cache;
|
62
|
-
|
63
|
-
extern void pages_init(Cache cache);
|
64
|
-
extern void pages_cleanup(Cache cache);
|
33
|
+
extern void pages_init();
|
34
|
+
extern void pages_set_root(const char *root);
|
35
|
+
extern void pages_cleanup();
|
65
36
|
|
66
|
-
extern Group group_create(
|
37
|
+
extern Group group_create(const char *path);
|
67
38
|
extern void group_add(Group g, const char *dir);
|
68
|
-
extern Page group_get(Err err,
|
39
|
+
extern Page group_get(Err err, const char *path, int plen);
|
69
40
|
|
70
|
-
extern
|
71
|
-
extern Page
|
72
|
-
extern
|
41
|
+
extern Page page_create(const char *path);
|
42
|
+
extern Page page_immutable(Err err, const char *path, const char *content, int clen);
|
43
|
+
extern Page page_get(Err err, const char *path, int plen);
|
44
|
+
extern int mime_set(Err err, const char *key, const char *value);
|
73
45
|
|
74
46
|
#endif /* __AGOO_PAGE_H__ */
|
data/ext/agoo/request.c
CHANGED
@@ -69,6 +69,7 @@ request_create(size_t mlen) {
|
|
69
69
|
memset(req, 0, size);
|
70
70
|
req->env = Qnil;
|
71
71
|
req->mlen = mlen;
|
72
|
+
req->hook = NULL;
|
72
73
|
}
|
73
74
|
return req;
|
74
75
|
}
|
@@ -649,7 +650,10 @@ call(VALUE self) {
|
|
649
650
|
}
|
650
651
|
void
|
651
652
|
request_destroy(Req req) {
|
652
|
-
DEBUG_FREE(mem_req, req)
|
653
|
+
DEBUG_FREE(mem_req, req);
|
654
|
+
if (NULL != req->hook && PUSH_HOOK == req->hook->type) {
|
655
|
+
free(req->hook);
|
656
|
+
}
|
653
657
|
free(req);
|
654
658
|
}
|
655
659
|
|
data/ext/agoo/request.h
CHANGED
@@ -33,9 +33,8 @@ typedef struct _Req {
|
|
33
33
|
struct _Str query;
|
34
34
|
struct _Str header;
|
35
35
|
struct _Str body;
|
36
|
-
|
36
|
+
Hook hook;
|
37
37
|
VALUE env;
|
38
|
-
HookType handler_type;
|
39
38
|
Res res;
|
40
39
|
size_t mlen; // allocated msg length
|
41
40
|
char msg[8]; // expanded to be full message
|
data/ext/agoo/response.c
CHANGED
@@ -228,7 +228,11 @@ head_set(VALUE self, VALUE key, VALUE val) {
|
|
228
228
|
vlen = (int)RSTRING_LEN(val);
|
229
229
|
|
230
230
|
if (the_server.pedantic) {
|
231
|
-
|
231
|
+
struct _Err err = ERR_INIT;
|
232
|
+
|
233
|
+
if (ERR_OK != http_header_ok(&err, ks, klen, vs, vlen)) {
|
234
|
+
rb_raise(rb_eArgError, "%s", err.msg);
|
235
|
+
}
|
232
236
|
}
|
233
237
|
hlen = klen + vlen + 4;
|
234
238
|
h = (Header)ALLOC_N(char, sizeof(struct _Header) - 8 + hlen + 1);
|
data/ext/agoo/rhook.c
CHANGED
@@ -54,8 +54,8 @@ resolve_classpath(const char *name, size_t len) {
|
|
54
54
|
}
|
55
55
|
|
56
56
|
Hook
|
57
|
-
rhook_create(Method method, const char *pattern, VALUE handler) {
|
58
|
-
Hook hook = hook_create(method,pattern, NULL, RACK_HOOK);
|
57
|
+
rhook_create(Method method, const char *pattern, VALUE handler, Queue q) {
|
58
|
+
Hook hook = hook_create(method,pattern, NULL, RACK_HOOK, q);
|
59
59
|
|
60
60
|
if (NULL != hook) {
|
61
61
|
if (T_STRING == rb_type(handler)) {
|
data/ext/agoo/rhook.h
CHANGED
data/ext/agoo/rserver.c
ADDED
@@ -0,0 +1,991 @@
|
|
1
|
+
// Copyright (c) 2018, Peter Ohler, All rights reserved.
|
2
|
+
|
3
|
+
#include <netdb.h>
|
4
|
+
#include <netinet/tcp.h>
|
5
|
+
#include <signal.h>
|
6
|
+
#include <stdio.h>
|
7
|
+
#include <sys/wait.h>
|
8
|
+
|
9
|
+
#include <ruby.h>
|
10
|
+
#include <ruby/thread.h>
|
11
|
+
#include <ruby/encoding.h>
|
12
|
+
|
13
|
+
#include "bind.h"
|
14
|
+
#include "con.h"
|
15
|
+
#include "debug.h"
|
16
|
+
#include "dtime.h"
|
17
|
+
#include "err.h"
|
18
|
+
#include "http.h"
|
19
|
+
#include "page.h"
|
20
|
+
#include "pub.h"
|
21
|
+
#include "response.h"
|
22
|
+
#include "request.h"
|
23
|
+
#include "rhook.h"
|
24
|
+
#include "rserver.h"
|
25
|
+
#include "server.h"
|
26
|
+
#include "sse.h"
|
27
|
+
#include "sub.h"
|
28
|
+
#include "upgraded.h"
|
29
|
+
#include "websocket.h"
|
30
|
+
|
31
|
+
extern void agoo_shutdown();
|
32
|
+
|
33
|
+
static VALUE server_mod = Qundef;
|
34
|
+
|
35
|
+
static VALUE connect_sym;
|
36
|
+
static VALUE delete_sym;
|
37
|
+
static VALUE get_sym;
|
38
|
+
static VALUE head_sym;
|
39
|
+
static VALUE options_sym;
|
40
|
+
static VALUE post_sym;
|
41
|
+
static VALUE push_env_key;
|
42
|
+
static VALUE put_sym;
|
43
|
+
|
44
|
+
static VALUE rserver;
|
45
|
+
|
46
|
+
static ID call_id;
|
47
|
+
static ID each_id;
|
48
|
+
static ID on_close_id;
|
49
|
+
static ID on_drained_id;
|
50
|
+
static ID on_error_id;
|
51
|
+
static ID on_message_id;
|
52
|
+
static ID on_request_id;
|
53
|
+
static ID to_i_id;
|
54
|
+
|
55
|
+
static const char err500[] = "HTTP/1.1 500 Internal Server Error\r\n";
|
56
|
+
|
57
|
+
struct _RServer the_rserver = {};
|
58
|
+
|
59
|
+
static void
|
60
|
+
server_mark(void *ptr) {
|
61
|
+
Upgraded up;
|
62
|
+
|
63
|
+
rb_gc_mark(rserver);
|
64
|
+
pthread_mutex_lock(&the_rserver.up_lock);
|
65
|
+
for (up = the_rserver.up_list; NULL != up; up = up->next) {
|
66
|
+
if (Qnil != up->handler) {
|
67
|
+
rb_gc_mark(up->handler);
|
68
|
+
}
|
69
|
+
if (Qnil != up->env) {
|
70
|
+
rb_gc_mark(up->env);
|
71
|
+
}
|
72
|
+
if (Qnil != up->wrap) {
|
73
|
+
rb_gc_mark(up->wrap);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
pthread_mutex_unlock(&the_rserver.up_lock);
|
77
|
+
}
|
78
|
+
|
79
|
+
static void
|
80
|
+
url_bind(VALUE rurl) {
|
81
|
+
struct _Err err = ERR_INIT;
|
82
|
+
Bind b = bind_url(&err, StringValuePtr(rurl));
|
83
|
+
|
84
|
+
if (ERR_OK != err.code) {
|
85
|
+
rb_raise(rb_eArgError, "%s", err.msg);
|
86
|
+
}
|
87
|
+
server_bind(b);
|
88
|
+
}
|
89
|
+
|
90
|
+
static int
|
91
|
+
configure(Err err, int port, const char *root, VALUE options) {
|
92
|
+
pages_set_root(root);
|
93
|
+
the_server.thread_cnt = 0;
|
94
|
+
the_rserver.worker_cnt = 1;
|
95
|
+
the_server.running = 0;
|
96
|
+
the_server.listen_thread = 0;
|
97
|
+
the_server.con_thread = 0;
|
98
|
+
the_rserver.max_push_pending = 32;
|
99
|
+
the_server.root_first = false;
|
100
|
+
the_server.binds = NULL;
|
101
|
+
|
102
|
+
if (Qnil != options) {
|
103
|
+
VALUE v;
|
104
|
+
|
105
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("thread_count"))))) {
|
106
|
+
int tc = FIX2INT(v);
|
107
|
+
|
108
|
+
if (1 <= tc || tc < 1000) {
|
109
|
+
the_server.thread_cnt = tc;
|
110
|
+
} else {
|
111
|
+
rb_raise(rb_eArgError, "thread_count must be between 1 and 1000.");
|
112
|
+
}
|
113
|
+
}
|
114
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("worker_count"))))) {
|
115
|
+
int wc = FIX2INT(v);
|
116
|
+
|
117
|
+
if (1 <= wc || wc < MAX_WORKERS) {
|
118
|
+
the_rserver.worker_cnt = wc;
|
119
|
+
} else {
|
120
|
+
rb_raise(rb_eArgError, "thread_count must be between 1 and %d.", MAX_WORKERS);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("max_push_pending"))))) {
|
124
|
+
int tc = FIX2INT(v);
|
125
|
+
|
126
|
+
if (0 <= tc || tc < 1000) {
|
127
|
+
the_server.thread_cnt = tc;
|
128
|
+
} else {
|
129
|
+
rb_raise(rb_eArgError, "thread_count must be between 0 and 1000.");
|
130
|
+
}
|
131
|
+
}
|
132
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("pedantic"))))) {
|
133
|
+
the_server.pedantic = (Qtrue == v);
|
134
|
+
}
|
135
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("root_first"))))) {
|
136
|
+
the_server.root_first = (Qtrue == v);
|
137
|
+
}
|
138
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("Port"))))) {
|
139
|
+
if (rb_cInteger == rb_obj_class(v)) {
|
140
|
+
port = NUM2INT(v);
|
141
|
+
} else {
|
142
|
+
switch (rb_type(v)) {
|
143
|
+
case T_STRING:
|
144
|
+
port = atoi(StringValuePtr(v));
|
145
|
+
break;
|
146
|
+
case T_FIXNUM:
|
147
|
+
port = NUM2INT(v);
|
148
|
+
break;
|
149
|
+
default:
|
150
|
+
break;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}
|
154
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("bind"))))) {
|
155
|
+
int len;
|
156
|
+
int i;
|
157
|
+
|
158
|
+
switch (rb_type(v)) {
|
159
|
+
case T_STRING:
|
160
|
+
url_bind(v);
|
161
|
+
break;
|
162
|
+
case T_ARRAY:
|
163
|
+
len = RARRAY_LEN(v);
|
164
|
+
for (i = 0; i < len; i++) {
|
165
|
+
url_bind(rb_ary_entry(v, i));
|
166
|
+
}
|
167
|
+
break;
|
168
|
+
default:
|
169
|
+
rb_raise(rb_eArgError, "bind option must be a String or Array of Strings.");
|
170
|
+
break;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("quiet"))))) {
|
174
|
+
if (Qtrue == v) {
|
175
|
+
info_cat.on = false;
|
176
|
+
}
|
177
|
+
}
|
178
|
+
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("debug"))))) {
|
179
|
+
if (Qtrue == v) {
|
180
|
+
error_cat.on = true;
|
181
|
+
warn_cat.on = true;
|
182
|
+
info_cat.on = true;
|
183
|
+
debug_cat.on = true;
|
184
|
+
con_cat.on = true;
|
185
|
+
req_cat.on = true;
|
186
|
+
resp_cat.on = true;
|
187
|
+
eval_cat.on = true;
|
188
|
+
push_cat.on = true;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
if (0 < port) {
|
193
|
+
Bind b = bind_port(err, port);
|
194
|
+
|
195
|
+
if (ERR_OK != err->code) {
|
196
|
+
rb_raise(rb_eArgError, "%s", err->msg);
|
197
|
+
}
|
198
|
+
server_bind(b);
|
199
|
+
}
|
200
|
+
return ERR_OK;
|
201
|
+
}
|
202
|
+
|
203
|
+
/* Document-method: init
|
204
|
+
*
|
205
|
+
* call-seq: init(port, root, options)
|
206
|
+
*
|
207
|
+
* Configures the server that will listen on the designated _port_ and using
|
208
|
+
* the _root_ as the root of the static resources. Logging is feature based
|
209
|
+
* and not level based and the options reflect that approach. If bind option
|
210
|
+
* is to be used instead of the port then set the port to zero.
|
211
|
+
*
|
212
|
+
* - *options* [_Hash_] server options
|
213
|
+
*
|
214
|
+
* - *:pedantic* [_true_|_false_] if true response header and status codes are checked and an exception raised if they violate the rack spec at https://github.com/rack/rack/blob/master/SPEC, https://tools.ietf.org/html/rfc3875#section-4.1.18, or https://tools.ietf.org/html/rfc7230.
|
215
|
+
*
|
216
|
+
* - *:thread_count* [_Integer_] number of ruby worker threads. Defaults to one. If zero then the _start_ function will not return but instead will proess using the thread that called _start_. Usually the default is best unless the workers are making IO calls.
|
217
|
+
*
|
218
|
+
* - *:worker_count* [_Integer_] number of workers to fork. Defaults to one which is not to fork.
|
219
|
+
*
|
220
|
+
* - *:bind* [_String_|_Array_] a binding or array of binds. Examples are: "http://127.0.0.1:6464", "unix:///tmp/agoo.socket", "http://[::1]:6464, or to not restrict the address "http://:6464".
|
221
|
+
*/
|
222
|
+
static VALUE
|
223
|
+
rserver_init(int argc, VALUE *argv, VALUE self) {
|
224
|
+
struct _Err err = ERR_INIT;
|
225
|
+
int port;
|
226
|
+
const char *root;
|
227
|
+
VALUE options = Qnil;
|
228
|
+
|
229
|
+
if (argc < 2 || 3 < argc) {
|
230
|
+
rb_raise(rb_eArgError, "Wrong number of arguments to Agoo::Server.configure.");
|
231
|
+
}
|
232
|
+
port = FIX2INT(argv[0]);
|
233
|
+
rb_check_type(argv[1], T_STRING);
|
234
|
+
root = StringValuePtr(argv[1]);
|
235
|
+
if (3 <= argc) {
|
236
|
+
options = argv[2];
|
237
|
+
}
|
238
|
+
server_setup();
|
239
|
+
sub_init(&the_rserver.sub_cache);
|
240
|
+
|
241
|
+
if (ERR_OK != configure(&err, port, root, options)) {
|
242
|
+
rb_raise(rb_eArgError, "%s", err.msg);
|
243
|
+
}
|
244
|
+
queue_multi_init(&the_rserver.pub_queue, 256, true, false);
|
245
|
+
queue_multi_init(&the_rserver.eval_queue, 1024, false, true);
|
246
|
+
|
247
|
+
pthread_mutex_init(&the_rserver.up_lock, 0);
|
248
|
+
the_rserver.up_list = NULL;
|
249
|
+
|
250
|
+
the_server.inited = true;
|
251
|
+
|
252
|
+
return Qnil;
|
253
|
+
}
|
254
|
+
|
255
|
+
static const char bad500[] = "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: ";
|
256
|
+
|
257
|
+
static VALUE
|
258
|
+
rescue_error(VALUE x) {
|
259
|
+
Req req = (Req)x;
|
260
|
+
volatile VALUE info = rb_errinfo();
|
261
|
+
volatile VALUE msg = rb_funcall(info, rb_intern("message"), 0);
|
262
|
+
const char *classname = rb_obj_classname(info);
|
263
|
+
const char *ms = rb_string_value_ptr(&msg);
|
264
|
+
|
265
|
+
if (NULL == req->up) {
|
266
|
+
char buf[1024];
|
267
|
+
int len = (int)(strlen(classname) + 2 + strlen(ms));
|
268
|
+
int cnt;
|
269
|
+
Text message;
|
270
|
+
|
271
|
+
if ((int)(sizeof(buf) - sizeof(bad500) + 7) <= len) {
|
272
|
+
len = sizeof(buf) - sizeof(bad500) + 7;
|
273
|
+
}
|
274
|
+
cnt = snprintf(buf, sizeof(buf), "%s%d\r\n\r\n%s: %s", bad500, len, classname, ms);
|
275
|
+
message = text_create(buf, cnt);
|
276
|
+
|
277
|
+
req->res->close = true;
|
278
|
+
res_set_message(req->res, message);
|
279
|
+
queue_wakeup(&the_server.con_queue);
|
280
|
+
} else {
|
281
|
+
/*
|
282
|
+
volatile VALUE bt = rb_funcall(info, rb_intern("backtrace"), 0);
|
283
|
+
int blen = RARRAY_LEN(bt);
|
284
|
+
int i;
|
285
|
+
VALUE rline;
|
286
|
+
|
287
|
+
for (i = 0; i < blen; i++) {
|
288
|
+
rline = rb_ary_entry(bt, i);
|
289
|
+
}
|
290
|
+
*/
|
291
|
+
log_cat(&error_cat, "%s: %s", classname, ms);
|
292
|
+
}
|
293
|
+
return Qfalse;
|
294
|
+
}
|
295
|
+
|
296
|
+
static VALUE
|
297
|
+
handle_base_inner(void *x) {
|
298
|
+
Req req = (Req)x;
|
299
|
+
volatile VALUE rr = request_wrap(req);
|
300
|
+
volatile VALUE rres = response_new();
|
301
|
+
|
302
|
+
if (NULL != req->hook) {
|
303
|
+
rb_funcall((VALUE)req->hook->handler, on_request_id, 2, rr, rres);
|
304
|
+
}
|
305
|
+
DATA_PTR(rr) = NULL;
|
306
|
+
res_set_message(req->res, response_text(rres));
|
307
|
+
queue_wakeup(&the_server.con_queue);
|
308
|
+
|
309
|
+
return Qfalse;
|
310
|
+
}
|
311
|
+
|
312
|
+
static void*
|
313
|
+
handle_base(void *x) {
|
314
|
+
rb_rescue2(handle_base_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
|
315
|
+
|
316
|
+
return NULL;
|
317
|
+
}
|
318
|
+
|
319
|
+
static int
|
320
|
+
header_cb(VALUE key, VALUE value, Text *tp) {
|
321
|
+
const char *ks = StringValuePtr(key);
|
322
|
+
int klen = (int)RSTRING_LEN(key);
|
323
|
+
const char *vs = StringValuePtr(value);
|
324
|
+
int vlen = (int)RSTRING_LEN(value);
|
325
|
+
struct _Err err = ERR_INIT;
|
326
|
+
|
327
|
+
if (the_server.pedantic) {
|
328
|
+
if (ERR_OK != http_header_ok(&err, ks, klen, vs, vlen)) {
|
329
|
+
rb_raise(rb_eArgError, "%s", err.msg);
|
330
|
+
}
|
331
|
+
}
|
332
|
+
if (0 != strcasecmp("Content-Length", ks)) {
|
333
|
+
*tp = text_append(*tp, ks, klen);
|
334
|
+
*tp = text_append(*tp, ": ", 2);
|
335
|
+
*tp = text_append(*tp, vs, vlen);
|
336
|
+
*tp = text_append(*tp, "\r\n", 2);
|
337
|
+
}
|
338
|
+
return ST_CONTINUE;
|
339
|
+
}
|
340
|
+
|
341
|
+
static VALUE
|
342
|
+
header_each_cb(VALUE kv, Text *tp) {
|
343
|
+
header_cb(rb_ary_entry(kv, 0), rb_ary_entry(kv, 1), tp);
|
344
|
+
|
345
|
+
return Qnil;
|
346
|
+
}
|
347
|
+
|
348
|
+
static VALUE
|
349
|
+
body_len_cb(VALUE v, int *sizep) {
|
350
|
+
*sizep += (int)RSTRING_LEN(v);
|
351
|
+
|
352
|
+
return Qnil;
|
353
|
+
}
|
354
|
+
|
355
|
+
static VALUE
|
356
|
+
body_append_cb(VALUE v, Text *tp) {
|
357
|
+
*tp = text_append(*tp, StringValuePtr(v), (int)RSTRING_LEN(v));
|
358
|
+
|
359
|
+
return Qnil;
|
360
|
+
}
|
361
|
+
|
362
|
+
static VALUE
|
363
|
+
handle_rack_inner(void *x) {
|
364
|
+
Req req = (Req)x;
|
365
|
+
Text t;
|
366
|
+
volatile VALUE env = request_env(req, request_wrap(req));
|
367
|
+
volatile VALUE res = Qnil;
|
368
|
+
volatile VALUE hv;
|
369
|
+
volatile VALUE bv;
|
370
|
+
int code;
|
371
|
+
const char *status_msg;
|
372
|
+
int bsize = 0;
|
373
|
+
|
374
|
+
if (NULL == req->hook) {
|
375
|
+
return Qfalse;
|
376
|
+
}
|
377
|
+
res = rb_funcall((VALUE)req->hook->handler, call_id, 1, env);
|
378
|
+
if (req->res->con->hijacked) {
|
379
|
+
queue_wakeup(&the_server.con_queue);
|
380
|
+
return Qfalse;
|
381
|
+
}
|
382
|
+
rb_check_type(res, T_ARRAY);
|
383
|
+
if (3 != RARRAY_LEN(res)) {
|
384
|
+
rb_raise(rb_eArgError, "a rack call() response must be an array of 3 members.");
|
385
|
+
}
|
386
|
+
hv = rb_ary_entry(res, 0);
|
387
|
+
if (RUBY_T_FIXNUM == rb_type(hv)) {
|
388
|
+
code = NUM2INT(hv);
|
389
|
+
} else {
|
390
|
+
code = NUM2INT(rb_funcall(hv, to_i_id, 0));
|
391
|
+
}
|
392
|
+
status_msg = http_code_message(code);
|
393
|
+
if ('\0' == *status_msg) {
|
394
|
+
rb_raise(rb_eArgError, "invalid rack call() response status code (%d).", code);
|
395
|
+
}
|
396
|
+
hv = rb_ary_entry(res, 1);
|
397
|
+
if (!rb_respond_to(hv, each_id)) {
|
398
|
+
rb_raise(rb_eArgError, "invalid rack call() response headers does not respond to each.");
|
399
|
+
}
|
400
|
+
bv = rb_ary_entry(res, 2);
|
401
|
+
if (!rb_respond_to(bv, each_id)) {
|
402
|
+
rb_raise(rb_eArgError, "invalid rack call() response body does not respond to each.");
|
403
|
+
}
|
404
|
+
if (NULL == (t = text_allocate(1024))) {
|
405
|
+
rb_raise(rb_eArgError, "failed to allocate response.");
|
406
|
+
}
|
407
|
+
if (T_ARRAY == rb_type(bv)) {
|
408
|
+
int i;
|
409
|
+
int bcnt = (int)RARRAY_LEN(bv);
|
410
|
+
|
411
|
+
for (i = 0; i < bcnt; i++) {
|
412
|
+
bsize += (int)RSTRING_LEN(rb_ary_entry(bv, i));
|
413
|
+
}
|
414
|
+
} else {
|
415
|
+
if (HEAD == req->method) {
|
416
|
+
// Rack wraps the response in two layers, Rack::Lint and
|
417
|
+
// Rack::BodyProxy. It each is called on either with the HEAD
|
418
|
+
// method an exception is raised so the length can not be
|
419
|
+
// determined. This digs down to get the actual response so the
|
420
|
+
// length can be calculated. A very special case.
|
421
|
+
if (0 == strcmp("Rack::BodyProxy", rb_obj_classname(bv))) {
|
422
|
+
volatile VALUE body = rb_ivar_get(bv, rb_intern("@body"));
|
423
|
+
|
424
|
+
if (Qnil != body) {
|
425
|
+
body = rb_ivar_get(body, rb_intern("@body"));
|
426
|
+
}
|
427
|
+
if (Qnil != body) {
|
428
|
+
body = rb_ivar_get(body, rb_intern("@body"));
|
429
|
+
}
|
430
|
+
if (rb_respond_to(body, rb_intern("each"))) {
|
431
|
+
rb_iterate(rb_each, body, body_len_cb, (VALUE)&bsize);
|
432
|
+
}
|
433
|
+
} else {
|
434
|
+
rb_iterate(rb_each, bv, body_len_cb, (VALUE)&bsize);
|
435
|
+
}
|
436
|
+
} else {
|
437
|
+
rb_iterate(rb_each, bv, body_len_cb, (VALUE)&bsize);
|
438
|
+
}
|
439
|
+
}
|
440
|
+
switch (code) {
|
441
|
+
case 100:
|
442
|
+
case 101:
|
443
|
+
case 102:
|
444
|
+
case 204:
|
445
|
+
case 205:
|
446
|
+
case 304:
|
447
|
+
// Content-Type and Content-Length can not be present
|
448
|
+
t->len = snprintf(t->text, 1024, "HTTP/1.1 %d %s\r\n", code, status_msg);
|
449
|
+
break;
|
450
|
+
default:
|
451
|
+
// Note that using simply sprintf causes an abort with travis OSX tests.
|
452
|
+
t->len = snprintf(t->text, 1024, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n", code, status_msg, bsize);
|
453
|
+
break;
|
454
|
+
}
|
455
|
+
if (code < 300) {
|
456
|
+
VALUE handler = Qnil;
|
457
|
+
|
458
|
+
switch (req->upgrade) {
|
459
|
+
case UP_WS:
|
460
|
+
if (CON_WS != req->res->con_kind ||
|
461
|
+
Qnil == (handler = rb_hash_lookup(env, push_env_key))) {
|
462
|
+
strcpy(t->text, err500);
|
463
|
+
t->len = sizeof(err500) - 1;
|
464
|
+
break;
|
465
|
+
}
|
466
|
+
req->hook = hook_create(NONE, NULL, (void*)handler, PUSH_HOOK, &the_rserver.eval_queue);
|
467
|
+
upgraded_create(req->res->con, handler, request_env(req, Qnil));
|
468
|
+
|
469
|
+
t->len = snprintf(t->text, 1024, "HTTP/1.1 101 %s\r\n", status_msg);
|
470
|
+
t = ws_add_headers(req, t);
|
471
|
+
break;
|
472
|
+
case UP_SSE:
|
473
|
+
if (CON_SSE != req->res->con_kind ||
|
474
|
+
Qnil == (handler = rb_hash_lookup(env, push_env_key))) {
|
475
|
+
strcpy(t->text, err500);
|
476
|
+
t->len = sizeof(err500) - 1;
|
477
|
+
break;
|
478
|
+
}
|
479
|
+
req->hook = hook_create(NONE, NULL, (void*)handler, PUSH_HOOK, &the_rserver.eval_queue);
|
480
|
+
upgraded_create(req->res->con, handler, request_env(req, Qnil));
|
481
|
+
t = sse_upgrade(req, t);
|
482
|
+
res_set_message(req->res, t);
|
483
|
+
queue_wakeup(&the_server.con_queue);
|
484
|
+
return Qfalse;
|
485
|
+
default:
|
486
|
+
break;
|
487
|
+
}
|
488
|
+
}
|
489
|
+
if (HEAD == req->method) {
|
490
|
+
bsize = 0;
|
491
|
+
} else {
|
492
|
+
if (T_HASH == rb_type(hv)) {
|
493
|
+
rb_hash_foreach(hv, header_cb, (VALUE)&t);
|
494
|
+
} else {
|
495
|
+
rb_iterate(rb_each, hv, header_each_cb, (VALUE)&t);
|
496
|
+
}
|
497
|
+
}
|
498
|
+
t = text_append(t, "\r\n", 2);
|
499
|
+
if (0 < bsize) {
|
500
|
+
if (T_ARRAY == rb_type(bv)) {
|
501
|
+
VALUE v;
|
502
|
+
int i;
|
503
|
+
int bcnt = (int)RARRAY_LEN(bv);
|
504
|
+
|
505
|
+
for (i = 0; i < bcnt; i++) {
|
506
|
+
v = rb_ary_entry(bv, i);
|
507
|
+
t = text_append(t, StringValuePtr(v), (int)RSTRING_LEN(v));
|
508
|
+
}
|
509
|
+
} else {
|
510
|
+
rb_iterate(rb_each, bv, body_append_cb, (VALUE)&t);
|
511
|
+
}
|
512
|
+
}
|
513
|
+
res_set_message(req->res, t);
|
514
|
+
queue_wakeup(&the_server.con_queue);
|
515
|
+
|
516
|
+
return Qfalse;
|
517
|
+
}
|
518
|
+
|
519
|
+
static void*
|
520
|
+
handle_rack(void *x) {
|
521
|
+
//rb_gc_disable();
|
522
|
+
rb_rescue2(handle_rack_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
|
523
|
+
//rb_gc_enable();
|
524
|
+
//rb_gc();
|
525
|
+
|
526
|
+
return NULL;
|
527
|
+
}
|
528
|
+
|
529
|
+
static VALUE
|
530
|
+
handle_wab_inner(void *x) {
|
531
|
+
Req req = (Req)x;
|
532
|
+
volatile VALUE rr = request_wrap(req);
|
533
|
+
volatile VALUE rres = response_new();
|
534
|
+
|
535
|
+
if (NULL != req->hook) {
|
536
|
+
rb_funcall((VALUE)req->hook->handler, on_request_id, 2, rr, rres);
|
537
|
+
}
|
538
|
+
DATA_PTR(rr) = NULL;
|
539
|
+
res_set_message(req->res, response_text(rres));
|
540
|
+
queue_wakeup(&the_server.con_queue);
|
541
|
+
|
542
|
+
return Qfalse;
|
543
|
+
}
|
544
|
+
|
545
|
+
static void*
|
546
|
+
handle_wab(void *x) {
|
547
|
+
rb_rescue2(handle_wab_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
|
548
|
+
|
549
|
+
return NULL;
|
550
|
+
}
|
551
|
+
|
552
|
+
static VALUE
|
553
|
+
handle_push_inner(void *x) {
|
554
|
+
Req req = (Req)x;
|
555
|
+
|
556
|
+
switch (req->method) {
|
557
|
+
case ON_MSG:
|
558
|
+
if (req->up->on_msg && NULL != req->hook) {
|
559
|
+
rb_funcall((VALUE)req->hook->handler, on_message_id, 2, req->up->wrap, rb_str_new(req->msg, req->mlen));
|
560
|
+
}
|
561
|
+
break;
|
562
|
+
case ON_BIN:
|
563
|
+
if (req->up->on_msg && NULL != req->hook) {
|
564
|
+
volatile VALUE rstr = rb_str_new(req->msg, req->mlen);
|
565
|
+
|
566
|
+
rb_enc_associate(rstr, rb_ascii8bit_encoding());
|
567
|
+
rb_funcall((VALUE)req->hook->handler, on_message_id, 2, req->up->wrap, rstr);
|
568
|
+
}
|
569
|
+
break;
|
570
|
+
case ON_CLOSE:
|
571
|
+
upgraded_ref(req->up);
|
572
|
+
queue_push(&the_rserver.pub_queue, pub_close(req->up));
|
573
|
+
if (req->up->on_close && NULL != req->hook) {
|
574
|
+
rb_funcall((VALUE)req->hook->handler, on_close_id, 1, req->up->wrap);
|
575
|
+
}
|
576
|
+
break;
|
577
|
+
case ON_SHUTDOWN:
|
578
|
+
if (NULL != req->hook) {
|
579
|
+
rb_funcall((VALUE)req->hook->handler, rb_intern("on_shutdown"), 1, req->up->wrap);
|
580
|
+
}
|
581
|
+
break;
|
582
|
+
case ON_ERROR:
|
583
|
+
if (req->up->on_error && NULL != req->hook) {
|
584
|
+
volatile VALUE rstr = rb_str_new(req->msg, req->mlen);
|
585
|
+
|
586
|
+
rb_enc_associate(rstr, rb_ascii8bit_encoding());
|
587
|
+
rb_funcall((VALUE)req->hook->handler, on_error_id, 2, req->up->wrap, rstr);
|
588
|
+
}
|
589
|
+
break;
|
590
|
+
case ON_EMPTY:
|
591
|
+
if (req->up->on_empty && NULL != req->hook) {
|
592
|
+
rb_funcall((VALUE)req->hook->handler, on_drained_id, 1, req->up->wrap);
|
593
|
+
}
|
594
|
+
break;
|
595
|
+
default:
|
596
|
+
break;
|
597
|
+
}
|
598
|
+
upgraded_release(req->up);
|
599
|
+
|
600
|
+
return Qfalse;
|
601
|
+
}
|
602
|
+
|
603
|
+
static void*
|
604
|
+
handle_push(void *x) {
|
605
|
+
rb_rescue2(handle_push_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
|
606
|
+
return NULL;
|
607
|
+
}
|
608
|
+
|
609
|
+
static void
|
610
|
+
handle_protected(Req req, bool gvi) {
|
611
|
+
if (NULL == req->hook) {
|
612
|
+
return;
|
613
|
+
}
|
614
|
+
switch (req->hook->type) {
|
615
|
+
case BASE_HOOK:
|
616
|
+
if (gvi) {
|
617
|
+
rb_thread_call_with_gvl(handle_base, req);
|
618
|
+
} else {
|
619
|
+
handle_base(req);
|
620
|
+
}
|
621
|
+
break;
|
622
|
+
case RACK_HOOK:
|
623
|
+
if (gvi) {
|
624
|
+
rb_thread_call_with_gvl(handle_rack, req);
|
625
|
+
} else {
|
626
|
+
handle_rack(req);
|
627
|
+
}
|
628
|
+
break;
|
629
|
+
case WAB_HOOK:
|
630
|
+
if (gvi) {
|
631
|
+
rb_thread_call_with_gvl(handle_wab, req);
|
632
|
+
} else {
|
633
|
+
handle_wab(req);
|
634
|
+
}
|
635
|
+
break;
|
636
|
+
case PUSH_HOOK:
|
637
|
+
if (gvi) {
|
638
|
+
rb_thread_call_with_gvl(handle_push, req);
|
639
|
+
} else {
|
640
|
+
handle_push(req);
|
641
|
+
}
|
642
|
+
break;
|
643
|
+
default: {
|
644
|
+
char buf[256];
|
645
|
+
int cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n");
|
646
|
+
Text message = text_create(buf, cnt);
|
647
|
+
|
648
|
+
req->res->close = true;
|
649
|
+
res_set_message(req->res, message);
|
650
|
+
queue_wakeup(&the_server.con_queue);
|
651
|
+
break;
|
652
|
+
}
|
653
|
+
}
|
654
|
+
}
|
655
|
+
|
656
|
+
static void*
|
657
|
+
process_loop(void *ptr) {
|
658
|
+
Req req;
|
659
|
+
|
660
|
+
atomic_fetch_add(&the_server.running, 1);
|
661
|
+
while (the_server.active) {
|
662
|
+
if (NULL != (req = (Req)queue_pop(&the_rserver.eval_queue, 0.1))) {
|
663
|
+
handle_protected(req, true);
|
664
|
+
request_destroy(req);
|
665
|
+
}
|
666
|
+
}
|
667
|
+
atomic_fetch_sub(&the_server.running, 1);
|
668
|
+
|
669
|
+
return NULL;
|
670
|
+
}
|
671
|
+
|
672
|
+
static VALUE
|
673
|
+
wrap_process_loop(void *ptr) {
|
674
|
+
rb_thread_call_without_gvl(process_loop, NULL, RUBY_UBF_IO, NULL);
|
675
|
+
return Qnil;
|
676
|
+
}
|
677
|
+
|
678
|
+
/* Document-method: start
|
679
|
+
*
|
680
|
+
* call-seq: start()
|
681
|
+
*
|
682
|
+
* Start the server.
|
683
|
+
*/
|
684
|
+
static VALUE
|
685
|
+
rserver_start(VALUE self) {
|
686
|
+
VALUE *vp;
|
687
|
+
int i;
|
688
|
+
int pid;
|
689
|
+
double giveup;
|
690
|
+
struct _Err err = ERR_INIT;
|
691
|
+
VALUE agoo = rb_const_get_at(rb_cObject, rb_intern("Agoo"));
|
692
|
+
VALUE v = rb_const_get_at(agoo, rb_intern("VERSION"));
|
693
|
+
|
694
|
+
*the_rserver.worker_pids = getpid();
|
695
|
+
|
696
|
+
if (ERR_OK != setup_listen(&err)) {
|
697
|
+
rb_raise(rb_eIOError, "%s", err.msg);
|
698
|
+
}
|
699
|
+
for (i = 1; i < the_rserver.worker_cnt; i++) {
|
700
|
+
VALUE rpid = rb_funcall(rb_cObject, rb_intern("fork"), 0);
|
701
|
+
|
702
|
+
if (Qnil == rpid) {
|
703
|
+
pid = 0;
|
704
|
+
} else {
|
705
|
+
pid = NUM2INT(rpid);
|
706
|
+
}
|
707
|
+
if (0 > pid) { // error, use single process
|
708
|
+
log_cat(&error_cat, "Failed to fork. %s.", strerror(errno));
|
709
|
+
break;
|
710
|
+
} else if (0 == pid) {
|
711
|
+
log_start(true);
|
712
|
+
break;
|
713
|
+
} else {
|
714
|
+
the_rserver.worker_pids[i] = pid;
|
715
|
+
}
|
716
|
+
}
|
717
|
+
if (ERR_OK != server_start(&err, "Agoo", StringValuePtr(v))) {
|
718
|
+
rb_raise(rb_eStandardError, "%s", err.msg);
|
719
|
+
}
|
720
|
+
if (0 >= the_server.thread_cnt) {
|
721
|
+
Req req;
|
722
|
+
|
723
|
+
while (the_server.active) {
|
724
|
+
if (NULL != (req = (Req)queue_pop(&the_rserver.eval_queue, 0.1))) {
|
725
|
+
handle_protected(req, false);
|
726
|
+
request_destroy(req);
|
727
|
+
} else {
|
728
|
+
rb_thread_schedule();
|
729
|
+
}
|
730
|
+
|
731
|
+
}
|
732
|
+
} else {
|
733
|
+
the_rserver.eval_threads = (VALUE*)malloc(sizeof(VALUE) * (the_server.thread_cnt + 1));
|
734
|
+
DEBUG_ALLOC(mem_eval_threads, the_server.eval_threads);
|
735
|
+
|
736
|
+
for (i = the_server.thread_cnt, vp = the_rserver.eval_threads; 0 < i; i--, vp++) {
|
737
|
+
*vp = rb_thread_create(wrap_process_loop, NULL);
|
738
|
+
}
|
739
|
+
*vp = Qnil;
|
740
|
+
|
741
|
+
giveup = dtime() + 1.0;
|
742
|
+
while (dtime() < giveup) {
|
743
|
+
// The processing threads will not start until this thread
|
744
|
+
// releases ownership so do that and then see if the threads has
|
745
|
+
// been started yet.
|
746
|
+
rb_thread_schedule();
|
747
|
+
if (2 + the_server.thread_cnt <= atomic_load(&the_server.running)) {
|
748
|
+
break;
|
749
|
+
}
|
750
|
+
}
|
751
|
+
}
|
752
|
+
return Qnil;
|
753
|
+
}
|
754
|
+
|
755
|
+
static void
|
756
|
+
stop_runners() {
|
757
|
+
sub_cleanup(&the_rserver.sub_cache);
|
758
|
+
// The preferred method of waiting for the ruby threads would be either a
|
759
|
+
// join or even a kill but since we may not have the gvl here that would
|
760
|
+
// cause a segfault. Instead we set a timeout and wait for the running
|
761
|
+
// counter to drop to zero.
|
762
|
+
if (NULL != the_rserver.eval_threads) {
|
763
|
+
double timeout = dtime() + 2.0;
|
764
|
+
|
765
|
+
while (dtime() < timeout) {
|
766
|
+
if (0 >= atomic_load(&the_server.running)) {
|
767
|
+
break;
|
768
|
+
}
|
769
|
+
dsleep(0.02);
|
770
|
+
}
|
771
|
+
DEBUG_FREE(mem_eval_threads, the_rserver.eval_threads);
|
772
|
+
free(the_rserver.eval_threads);
|
773
|
+
the_rserver.eval_threads = NULL;
|
774
|
+
}
|
775
|
+
}
|
776
|
+
|
777
|
+
/* Document-method: shutdown
|
778
|
+
*
|
779
|
+
* call-seq: shutdown()
|
780
|
+
*
|
781
|
+
* Shutdown the server. Logs and queues are flushed before shutting down.
|
782
|
+
*/
|
783
|
+
VALUE
|
784
|
+
rserver_shutdown(VALUE self) {
|
785
|
+
if (the_server.inited) {
|
786
|
+
server_shutdown("Agoo", stop_runners);
|
787
|
+
|
788
|
+
queue_cleanup(&the_rserver.pub_queue);
|
789
|
+
queue_cleanup(&the_rserver.eval_queue);
|
790
|
+
|
791
|
+
if (1 < the_rserver.worker_cnt && getpid() == *the_rserver.worker_pids) {
|
792
|
+
int i;
|
793
|
+
int status;
|
794
|
+
int exit_cnt = 1;
|
795
|
+
int j;
|
796
|
+
|
797
|
+
for (i = 1; i < the_rserver.worker_cnt; i++) {
|
798
|
+
kill(the_rserver.worker_pids[i], SIGKILL);
|
799
|
+
}
|
800
|
+
for (j = 0; j < 20; j++) {
|
801
|
+
for (i = 1; i < the_rserver.worker_cnt; i++) {
|
802
|
+
if (0 == the_rserver.worker_pids[i]) {
|
803
|
+
continue;
|
804
|
+
}
|
805
|
+
if (0 < waitpid(the_rserver.worker_pids[i], &status, WNOHANG)) {
|
806
|
+
if (WIFEXITED(status)) {
|
807
|
+
//printf("exited, status=%d for %d\n", the_server.worker_pids[i], WEXITSTATUS(status));
|
808
|
+
the_rserver.worker_pids[i] = 0;
|
809
|
+
exit_cnt++;
|
810
|
+
} else if (WIFSIGNALED(status)) {
|
811
|
+
//printf("*** killed by signal %d for %d\n", the_server.worker_pids[i], WTERMSIG(status));
|
812
|
+
the_rserver.worker_pids[i] = 0;
|
813
|
+
exit_cnt++;
|
814
|
+
}
|
815
|
+
}
|
816
|
+
}
|
817
|
+
if (the_rserver.worker_cnt <= exit_cnt) {
|
818
|
+
break;
|
819
|
+
}
|
820
|
+
dsleep(0.2);
|
821
|
+
}
|
822
|
+
if (exit_cnt < the_rserver.worker_cnt) {
|
823
|
+
printf("*-*-* Some workers did not exit.\n");
|
824
|
+
}
|
825
|
+
}
|
826
|
+
}
|
827
|
+
return Qnil;
|
828
|
+
}
|
829
|
+
|
830
|
+
/* Document-method: handle
|
831
|
+
*
|
832
|
+
* call-seq: handle(method, pattern, handler)
|
833
|
+
*
|
834
|
+
* Registers a handler for the HTTP method and path pattern specified. The
|
835
|
+
* path pattern follows glob like rules in that a single * matches a single
|
836
|
+
* token bounded by the `/` character and a double ** matches all remaining.
|
837
|
+
*/
|
838
|
+
static VALUE
|
839
|
+
handle(VALUE self, VALUE method, VALUE pattern, VALUE handler) {
|
840
|
+
Hook hook;
|
841
|
+
Method meth = ALL;
|
842
|
+
const char *pat;
|
843
|
+
|
844
|
+
rb_check_type(pattern, T_STRING);
|
845
|
+
pat = StringValuePtr(pattern);
|
846
|
+
|
847
|
+
if (connect_sym == method) {
|
848
|
+
meth = CONNECT;
|
849
|
+
} else if (delete_sym == method) {
|
850
|
+
meth = DELETE;
|
851
|
+
} else if (get_sym == method) {
|
852
|
+
meth = GET;
|
853
|
+
} else if (head_sym == method) {
|
854
|
+
meth = HEAD;
|
855
|
+
} else if (options_sym == method) {
|
856
|
+
meth = OPTIONS;
|
857
|
+
} else if (post_sym == method) {
|
858
|
+
meth = POST;
|
859
|
+
} else if (put_sym == method) {
|
860
|
+
meth = PUT;
|
861
|
+
} else if (Qnil == method) {
|
862
|
+
meth = ALL;
|
863
|
+
} else {
|
864
|
+
rb_raise(rb_eArgError, "invalid method");
|
865
|
+
}
|
866
|
+
if (NULL == (hook = rhook_create(meth, pat, handler, &the_rserver.eval_queue))) {
|
867
|
+
rb_raise(rb_eStandardError, "out of memory.");
|
868
|
+
} else {
|
869
|
+
Hook h;
|
870
|
+
Hook prev = NULL;
|
871
|
+
|
872
|
+
for (h = the_server.hooks; NULL != h; h = h->next) {
|
873
|
+
prev = h;
|
874
|
+
}
|
875
|
+
if (NULL != prev) {
|
876
|
+
prev->next = hook;
|
877
|
+
} else {
|
878
|
+
the_server.hooks = hook;
|
879
|
+
}
|
880
|
+
rb_gc_register_address((VALUE*)&hook->handler);
|
881
|
+
}
|
882
|
+
return Qnil;
|
883
|
+
}
|
884
|
+
|
885
|
+
/* Document-method: handle_not_found
|
886
|
+
*
|
887
|
+
* call-seq: not_found_handle(handler)
|
888
|
+
*
|
889
|
+
* Registers a handler to be called when no other hook is found and no static
|
890
|
+
* file is found.
|
891
|
+
*/
|
892
|
+
static VALUE
|
893
|
+
handle_not_found(VALUE self, VALUE handler) {
|
894
|
+
if (NULL == (the_server.hook404 = rhook_create(GET, "/", handler, &the_rserver.eval_queue))) {
|
895
|
+
rb_raise(rb_eStandardError, "out of memory.");
|
896
|
+
}
|
897
|
+
rb_gc_register_address((VALUE*)&the_server.hook404->handler);
|
898
|
+
|
899
|
+
return Qnil;
|
900
|
+
}
|
901
|
+
|
902
|
+
/* Document-method: add_mime
|
903
|
+
*
|
904
|
+
* call-seq: add_mime(suffix, type)
|
905
|
+
*
|
906
|
+
* Adds a mime type by associating a type string with a suffix. This is used
|
907
|
+
* for static files.
|
908
|
+
*/
|
909
|
+
static VALUE
|
910
|
+
add_mime(VALUE self, VALUE suffix, VALUE type) {
|
911
|
+
struct _Err err = ERR_INIT;
|
912
|
+
|
913
|
+
if (ERR_OK != mime_set(&err, StringValuePtr(suffix), StringValuePtr(type))) {
|
914
|
+
rb_raise(rb_eArgError, "%s", err.msg);
|
915
|
+
}
|
916
|
+
return Qnil;
|
917
|
+
}
|
918
|
+
|
919
|
+
/* Document-method: path_group
|
920
|
+
*
|
921
|
+
* call-seq: path_group(path, dirs)
|
922
|
+
*
|
923
|
+
* Sets up a path group where the path defines a group of directories to
|
924
|
+
* search for a file. For example a path of '/assets' could be mapped to a set
|
925
|
+
* of [ 'home/user/images', '/home/user/app/assets/images' ].
|
926
|
+
*/
|
927
|
+
static VALUE
|
928
|
+
path_group(VALUE self, VALUE path, VALUE dirs) {
|
929
|
+
Group g;
|
930
|
+
|
931
|
+
rb_check_type(path, T_STRING);
|
932
|
+
rb_check_type(dirs, T_ARRAY);
|
933
|
+
|
934
|
+
if (NULL != (g = group_create(StringValuePtr(path)))) {
|
935
|
+
int i;
|
936
|
+
int dcnt = (int)RARRAY_LEN(dirs);
|
937
|
+
VALUE entry;
|
938
|
+
|
939
|
+
for (i = dcnt - 1; 0 <= i; i--) {
|
940
|
+
entry = rb_ary_entry(dirs, i);
|
941
|
+
if (T_STRING != rb_type(entry)) {
|
942
|
+
entry = rb_funcall(entry, rb_intern("to_s"), 0);
|
943
|
+
}
|
944
|
+
group_add(g, StringValuePtr(entry));
|
945
|
+
}
|
946
|
+
}
|
947
|
+
return Qnil;
|
948
|
+
}
|
949
|
+
|
950
|
+
/* Document-class: Agoo::Server
|
951
|
+
*
|
952
|
+
* An HTTP server that support the rack API as well as some other optimized
|
953
|
+
* APIs for handling HTTP requests.
|
954
|
+
*/
|
955
|
+
void
|
956
|
+
server_init(VALUE mod) {
|
957
|
+
server_mod = rb_define_module_under(mod, "Server");
|
958
|
+
|
959
|
+
rb_define_module_function(server_mod, "init", rserver_init, -1);
|
960
|
+
rb_define_module_function(server_mod, "start", rserver_start, 0);
|
961
|
+
rb_define_module_function(server_mod, "shutdown", rserver_shutdown, 0);
|
962
|
+
|
963
|
+
rb_define_module_function(server_mod, "handle", handle, 3);
|
964
|
+
rb_define_module_function(server_mod, "handle_not_found", handle_not_found, 1);
|
965
|
+
rb_define_module_function(server_mod, "add_mime", add_mime, 2);
|
966
|
+
rb_define_module_function(server_mod, "path_group", path_group, 2);
|
967
|
+
|
968
|
+
call_id = rb_intern("call");
|
969
|
+
each_id = rb_intern("each");
|
970
|
+
on_close_id = rb_intern("on_close");
|
971
|
+
on_drained_id = rb_intern("on_drained");
|
972
|
+
on_error_id = rb_intern("on_error");
|
973
|
+
on_message_id = rb_intern("on_message");
|
974
|
+
on_request_id = rb_intern("on_request");
|
975
|
+
to_i_id = rb_intern("to_i");
|
976
|
+
|
977
|
+
connect_sym = ID2SYM(rb_intern("CONNECT")); rb_gc_register_address(&connect_sym);
|
978
|
+
delete_sym = ID2SYM(rb_intern("DELETE")); rb_gc_register_address(&delete_sym);
|
979
|
+
get_sym = ID2SYM(rb_intern("GET")); rb_gc_register_address(&get_sym);
|
980
|
+
head_sym = ID2SYM(rb_intern("HEAD")); rb_gc_register_address(&head_sym);
|
981
|
+
options_sym = ID2SYM(rb_intern("OPTIONS")); rb_gc_register_address(&options_sym);
|
982
|
+
post_sym = ID2SYM(rb_intern("POST")); rb_gc_register_address(&post_sym);
|
983
|
+
put_sym = ID2SYM(rb_intern("PUT")); rb_gc_register_address(&put_sym);
|
984
|
+
|
985
|
+
push_env_key = rb_str_new_cstr("rack.upgrade"); rb_gc_register_address(&push_env_key);
|
986
|
+
|
987
|
+
rserver = Data_Wrap_Struct(rb_cObject, server_mark, NULL, strdup("dummy"));
|
988
|
+
rb_gc_register_address(&rserver);
|
989
|
+
|
990
|
+
http_init();
|
991
|
+
}
|