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.

@@ -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
- typedef struct _Cache {
57
- Slot buckets[PAGE_BUCKET_SIZE];
58
- MimeSlot muckets[MIME_BUCKET_SIZE];
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(Cache cache, const char *path);
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, Cache cache, const char *path, int plen);
39
+ extern Page group_get(Err err, const char *path, int plen);
69
40
 
70
- extern void page_destroy(Page p);
71
- extern Page page_get(Err err, Cache cache, const char *path, int plen);
72
- extern void mime_set(Cache cache, const char *key, const char *value);
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__ */
@@ -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
 
@@ -33,9 +33,8 @@ typedef struct _Req {
33
33
  struct _Str query;
34
34
  struct _Str header;
35
35
  struct _Str body;
36
- VALUE handler;
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
@@ -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
- http_header_ok(ks, klen, vs, vlen);
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);
@@ -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)) {
@@ -8,6 +8,6 @@
8
8
  #include "hook.h"
9
9
  #include "method.h"
10
10
 
11
- extern Hook rhook_create(Method method, const char *pattern, VALUE handler);
11
+ extern Hook rhook_create(Method method, const char *pattern, VALUE handler, Queue q);
12
12
 
13
13
  #endif // __AGOO_RHOOK_H__
@@ -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
+ }