agoo 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of agoo might be problematic. Click here for more details.

Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -9
  3. data/ext/agoo/agoo.c +45 -0
  4. data/ext/agoo/base64.c +107 -0
  5. data/ext/agoo/base64.h +15 -0
  6. data/ext/agoo/ccache.c +301 -0
  7. data/ext/agoo/ccache.h +53 -0
  8. data/ext/agoo/con.c +522 -82
  9. data/ext/agoo/con.h +7 -5
  10. data/ext/agoo/debug.c +121 -7
  11. data/ext/agoo/debug.h +11 -6
  12. data/ext/agoo/error_stream.c +5 -6
  13. data/ext/agoo/error_stream.h +1 -1
  14. data/ext/agoo/extconf.rb +2 -1
  15. data/ext/agoo/hook.c +4 -4
  16. data/ext/agoo/hook.h +1 -0
  17. data/ext/agoo/http.c +2 -2
  18. data/ext/agoo/http.h +2 -0
  19. data/ext/agoo/log.c +604 -219
  20. data/ext/agoo/log.h +20 -7
  21. data/ext/agoo/page.c +20 -23
  22. data/ext/agoo/page.h +2 -0
  23. data/ext/agoo/pub.c +111 -0
  24. data/ext/agoo/pub.h +40 -0
  25. data/ext/agoo/queue.c +2 -2
  26. data/ext/agoo/rack_logger.c +15 -71
  27. data/ext/agoo/rack_logger.h +1 -1
  28. data/ext/agoo/request.c +96 -21
  29. data/ext/agoo/request.h +23 -12
  30. data/ext/agoo/res.c +5 -2
  31. data/ext/agoo/res.h +4 -0
  32. data/ext/agoo/response.c +13 -12
  33. data/ext/agoo/response.h +1 -2
  34. data/ext/agoo/server.c +290 -428
  35. data/ext/agoo/server.h +10 -10
  36. data/ext/agoo/sha1.c +148 -0
  37. data/ext/agoo/sha1.h +10 -0
  38. data/ext/agoo/sse.c +26 -0
  39. data/ext/agoo/sse.h +12 -0
  40. data/ext/agoo/sub.c +111 -0
  41. data/ext/agoo/sub.h +36 -0
  42. data/ext/agoo/subscription.c +54 -0
  43. data/ext/agoo/subscription.h +18 -0
  44. data/ext/agoo/text.c +26 -4
  45. data/ext/agoo/text.h +2 -0
  46. data/ext/agoo/types.h +13 -0
  47. data/ext/agoo/upgraded.c +148 -0
  48. data/ext/agoo/upgraded.h +13 -0
  49. data/ext/agoo/websocket.c +248 -0
  50. data/ext/agoo/websocket.h +27 -0
  51. data/lib/agoo/version.rb +1 -1
  52. data/lib/rack/handler/agoo.rb +13 -6
  53. data/test/base_handler_test.rb +24 -22
  54. data/test/log_test.rb +146 -199
  55. data/test/rack_handler_test.rb +19 -20
  56. data/test/static_test.rb +30 -28
  57. metadata +23 -7
  58. data/test/rrr/test.rb +0 -26
  59. data/test/tests.rb +0 -8
@@ -8,6 +8,6 @@
8
8
  #include "server.h"
9
9
 
10
10
  extern void rack_logger_init(VALUE mod);
11
- extern VALUE rack_logger_new(Server server);
11
+ extern VALUE rack_logger_new();
12
12
 
13
13
  #endif // __AGOO_RACK_LOGGER_H__
@@ -19,6 +19,7 @@ static VALUE get_val = Qundef;
19
19
  static VALUE head_val = Qundef;
20
20
  static VALUE http_val = Qundef;
21
21
  static VALUE options_val = Qundef;
22
+ static VALUE patch_val = Qundef;
22
23
  static VALUE path_info_val = Qundef;
23
24
  static VALUE post_val = Qundef;
24
25
  static VALUE put_val = Qundef;
@@ -29,6 +30,7 @@ static VALUE rack_logger_val = Qundef;
29
30
  static VALUE rack_multiprocess_val = Qundef;
30
31
  static VALUE rack_multithread_val = Qundef;
31
32
  static VALUE rack_run_once_val = Qundef;
33
+ static VALUE rack_upgrade_val = Qundef;
32
34
  static VALUE rack_url_scheme_val = Qundef;
33
35
  static VALUE rack_version_val = Qundef;
34
36
  static VALUE rack_version_val_val = Qundef;
@@ -38,12 +40,33 @@ static VALUE server_name_val = Qundef;
38
40
  static VALUE server_port_val = Qundef;
39
41
  static VALUE slash_val = Qundef;
40
42
 
43
+ static VALUE sse_sym;
44
+ static VALUE websocket_sym;
45
+
41
46
  static VALUE stringio_class = Qundef;
42
47
 
43
48
  static ID new_id;
44
49
 
45
50
  static const char content_type[] = "Content-Type";
46
51
  static const char content_length[] = "Content-Length";
52
+ static const char connection_key[] = "Connection";
53
+ static const char upgrade_key[] = "Upgrade";
54
+ static const char websocket_val[] = "websocket";
55
+ static const char accept_key[] = "Accept";
56
+ static const char event_stream_val[] = "text/event-stream";
57
+
58
+ Req
59
+ request_create(size_t mlen) {
60
+ size_t size = mlen + sizeof(struct _Req) - 7;
61
+ Req req = (Req)malloc(size);
62
+
63
+ if (NULL != req) {
64
+ DEBUG_ALLOC(mem_req, req);
65
+ memset(req, 0, size);
66
+ req->mlen = mlen;
67
+ }
68
+ return req;
69
+ }
47
70
 
48
71
  static VALUE
49
72
  req_method(Req r) {
@@ -60,6 +83,7 @@ req_method(Req r) {
60
83
  case OPTIONS: m = options_val; break;
61
84
  case POST: m = post_val; break;
62
85
  case PUT: m = put_val; break;
86
+ case PATCH: m = patch_val; break;
63
87
  default: m = Qnil; break;
64
88
  }
65
89
  return m;
@@ -218,6 +242,26 @@ server_port(VALUE self) {
218
242
  return req_server_port((Req)DATA_PTR(self));
219
243
  }
220
244
 
245
+ static VALUE
246
+ req_rack_upgrade(Req r) {
247
+ switch (r->upgrade) {
248
+ case UP_WS: return websocket_sym;
249
+ case UP_SSE: return sse_sym;
250
+ default: return Qnil;
251
+ }
252
+ }
253
+
254
+ /* Document-method: rack_upgrade?
255
+ *
256
+ * call-seq: rack_upgrade?()
257
+ *
258
+ * Returns the URL scheme or either _http_ or _https_ as a string.
259
+ */
260
+ static VALUE
261
+ rack_upgrade(VALUE self) {
262
+ return req_rack_upgrade((Req)DATA_PTR(self));
263
+ }
264
+
221
265
  /* Document-method: rack_version
222
266
  *
223
267
  * call-seq: rack_version()
@@ -271,7 +315,7 @@ rack_input(VALUE self) {
271
315
 
272
316
  static VALUE
273
317
  req_rack_errors(Req r) {
274
- return error_stream_new(r->server);
318
+ return error_stream_new();
275
319
  }
276
320
 
277
321
  /* Document-method: rack_errors
@@ -291,7 +335,7 @@ req_rack_multithread(Req r) {
291
335
  if (NULL == r) {
292
336
  rb_raise(rb_eArgError, "Request is no longer valid.");
293
337
  }
294
- if (NULL != r->server && 1 < r->server->thread_cnt) {
338
+ if (1 < the_server.thread_cnt) {
295
339
  return Qtrue;
296
340
  }
297
341
  return Qfalse;
@@ -354,13 +398,16 @@ add_header_value(VALUE hh, const char *key, int klen, const char *val, int vlen)
354
398
 
355
399
  static void
356
400
  fill_headers(Req r, VALUE hash) {
357
- char *h = r->header.start;
358
- char *end = h + r->header.len;
359
- char *key = h;
360
- char *kend = key;
361
- char *val = NULL;
362
- char *vend;
363
-
401
+ char *h = r->header.start;
402
+ char *end = h + r->header.len + 1; // +1 for last \r
403
+ char *key = h;
404
+ char *kend = key;
405
+ char *val = NULL;
406
+ char *vend;
407
+ int klen;
408
+ bool upgrade = false;
409
+ bool ws = false;
410
+
364
411
  if (NULL == r) {
365
412
  rb_raise(rb_eArgError, "Request is no longer valid.");
366
413
  }
@@ -370,22 +417,40 @@ fill_headers(Req r, VALUE hash) {
370
417
  kend = h;
371
418
  val = h + 1;
372
419
  break;
373
- case ' ':
374
- if (NULL != val) {
375
- val++;
376
- } else {
377
- // TBD handle trailing spaces as well
378
- key++;
379
- }
380
- break;
381
420
  case '\r':
382
421
  if (NULL != val) {
422
+ for (; ' ' == *val; val++) {
423
+ }
383
424
  vend = h;
384
425
  }
426
+ if (NULL != key) {
427
+ for (; ' ' == *key; key++) {
428
+ }
429
+ }
385
430
  if ('\n' == *(h + 1)) {
386
431
  h++;
387
432
  }
388
- add_header_value(hash, key, (int)(kend - key), val, (int)(vend - val));
433
+ klen = (int)(kend - key);
434
+ add_header_value(hash, key, klen, val, (int)(vend - val));
435
+ if (sizeof(upgrade_key) - 1 == klen && 0 == strncasecmp(key, upgrade_key, sizeof(upgrade_key) - 1)) {
436
+ if (sizeof(websocket_val) - 1 == vend - val &&
437
+ 0 == strncasecmp(val, websocket_val, sizeof(websocket_val) - 1)) {
438
+ ws = true;
439
+ }
440
+ } else if (sizeof(connection_key) - 1 == klen && 0 == strncasecmp(key, connection_key, sizeof(connection_key) - 1)) {
441
+ char buf[1024];
442
+
443
+ strncpy(buf, val, vend - val);
444
+ buf[sizeof(buf)-1] = '\0';
445
+ if (NULL != strstr(buf, upgrade_key)) {
446
+ upgrade = true;
447
+ }
448
+ } else if (sizeof(accept_key) - 1 == klen && 0 == strncasecmp(key, accept_key, sizeof(accept_key) - 1)) {
449
+ if (sizeof(event_stream_val) - 1 == vend - val &&
450
+ 0 == strncasecmp(val, event_stream_val, sizeof(event_stream_val) - 1)) {
451
+ r->upgrade = UP_SSE;
452
+ }
453
+ }
389
454
  key = h + 1;
390
455
  kend = NULL;
391
456
  val = NULL;
@@ -395,6 +460,9 @@ fill_headers(Req r, VALUE hash) {
395
460
  break;
396
461
  }
397
462
  }
463
+ if (upgrade && ws) {
464
+ r->upgrade = UP_WS;
465
+ }
398
466
  }
399
467
 
400
468
  /* Document-method: headers
@@ -439,7 +507,7 @@ body(VALUE self) {
439
507
 
440
508
  static VALUE
441
509
  req_rack_logger(Req req) {
442
- return rack_logger_new(req->server);
510
+ return rack_logger_new();
443
511
  }
444
512
 
445
513
  /* Document-method: rack_logger
@@ -484,6 +552,7 @@ request_env(Req req) {
484
552
  rb_hash_aset(env, rack_multiprocess_val, Qfalse);
485
553
  rb_hash_aset(env, rack_run_once_val, Qfalse);
486
554
  rb_hash_aset(env, rack_logger_val, req_rack_logger(req));
555
+ rb_hash_aset(env, rack_upgrade_val, req_rack_upgrade(req));
487
556
 
488
557
  return env;
489
558
  }
@@ -520,7 +589,7 @@ to_s(VALUE self) {
520
589
 
521
590
  void
522
591
  request_destroy(Req req) {
523
- DEBUG_FREE(mem_req)
592
+ DEBUG_FREE(mem_req, req)
524
593
  free(req);
525
594
  }
526
595
 
@@ -557,6 +626,7 @@ request_init(VALUE mod) {
557
626
  rb_define_method(req_class, "rack_multithread", rack_multithread, 0);
558
627
  rb_define_method(req_class, "rack_multiprocess", rack_multiprocess, 0);
559
628
  rb_define_method(req_class, "rack_run_once", rack_run_once, 0);
629
+ rb_define_method(req_class, "rack_upgrade?", rack_upgrade, 0);
560
630
  rb_define_method(req_class, "headers", headers, 0);
561
631
  rb_define_method(req_class, "body", body, 0);
562
632
  rb_define_method(req_class, "rack_logger", rack_logger, 0);
@@ -579,6 +649,7 @@ request_init(VALUE mod) {
579
649
  head_val = rb_str_new_cstr("HEAD"); rb_gc_register_address(&head_val);
580
650
  http_val = rb_str_new_cstr("http"); rb_gc_register_address(&http_val);
581
651
  options_val = rb_str_new_cstr("OPTIONS"); rb_gc_register_address(&options_val);
652
+ patch_val = rb_str_new_cstr("PATCH"); rb_gc_register_address(&patch_val);
582
653
  path_info_val = rb_str_new_cstr("PATH_INFO"); rb_gc_register_address(&path_info_val);
583
654
  post_val = rb_str_new_cstr("POST"); rb_gc_register_address(&post_val);
584
655
  put_val = rb_str_new_cstr("PUT"); rb_gc_register_address(&put_val);
@@ -587,8 +658,9 @@ request_init(VALUE mod) {
587
658
  rack_input_val = rb_str_new_cstr("rack.input"); rb_gc_register_address(&rack_input_val);
588
659
  rack_logger_val = rb_str_new_cstr("rack.logger"); rb_gc_register_address(&rack_logger_val);
589
660
  rack_multiprocess_val = rb_str_new_cstr("rack.multiprocess");rb_gc_register_address(&rack_multiprocess_val);
590
- rack_multithread_val = rb_str_new_cstr("rack.multithread");rb_gc_register_address(&rack_multithread_val);
661
+ rack_multithread_val = rb_str_new_cstr("rack.multithread"); rb_gc_register_address(&rack_multithread_val);
591
662
  rack_run_once_val = rb_str_new_cstr("rack.run_once"); rb_gc_register_address(&rack_run_once_val);
663
+ rack_upgrade_val = rb_str_new_cstr("rack.upgrade?"); rb_gc_register_address(&rack_upgrade_val);
592
664
  rack_url_scheme_val = rb_str_new_cstr("rack.url_scheme"); rb_gc_register_address(&rack_url_scheme_val);
593
665
  rack_version_val = rb_str_new_cstr("rack.version"); rb_gc_register_address(&rack_version_val);
594
666
  request_method_val = rb_str_new_cstr("REQUEST_METHOD"); rb_gc_register_address(&request_method_val);
@@ -596,4 +668,7 @@ request_init(VALUE mod) {
596
668
  server_name_val = rb_str_new_cstr("SERVER_NAME"); rb_gc_register_address(&server_name_val);
597
669
  server_port_val = rb_str_new_cstr("SERVER_PORT"); rb_gc_register_address(&server_port_val);
598
670
  slash_val = rb_str_new_cstr("/"); rb_gc_register_address(&slash_val);
671
+
672
+ sse_sym = ID2SYM(rb_intern("sse")); rb_gc_register_address(&sse_sym);
673
+ websocket_sym = ID2SYM(rb_intern("websocket")); rb_gc_register_address(&websocket_sym);
599
674
  }
@@ -3,32 +3,43 @@
3
3
  #ifndef __AGOO_REQUEST_H__
4
4
  #define __AGOO_REQUEST_H__
5
5
 
6
+ #include <stdint.h>
7
+
6
8
  #include <ruby.h>
7
9
 
8
10
  #include "hook.h"
9
11
  #include "res.h"
10
- #include "server.h"
11
12
  #include "types.h"
12
13
 
14
+ struct _Server;
15
+
16
+ typedef enum {
17
+ UP_NONE = '\0',
18
+ UP_WS = 'W',
19
+ UP_SSE = 'S',
20
+ } Upgrade;
21
+
13
22
  typedef struct _Str {
14
23
  char *start;
15
24
  unsigned int len;
16
25
  } *Str;
17
26
 
18
27
  typedef struct _Req {
19
- Server server;
20
- Method method;
21
- struct _Str path;
22
- struct _Str query;
23
- struct _Str header;
24
- struct _Str body;
25
- VALUE handler;
26
- HookType handler_type;
27
- Res res;
28
- size_t mlen; // allocated msg length
29
- char msg[8]; // expanded to be full message
28
+ Method method;
29
+ Upgrade upgrade;
30
+ uint64_t cid;
31
+ struct _Str path;
32
+ struct _Str query;
33
+ struct _Str header;
34
+ struct _Str body;
35
+ VALUE handler;
36
+ HookType handler_type;
37
+ Res res;
38
+ size_t mlen; // allocated msg length
39
+ char msg[8]; // expanded to be full message
30
40
  } *Req;
31
41
 
42
+ extern Req request_create(size_t mlen);
32
43
  extern void request_init(VALUE mod);
33
44
  extern VALUE request_wrap(Req req);
34
45
  extern VALUE request_env(Req req);
@@ -10,10 +10,13 @@ res_create() {
10
10
  Res res = (Res)malloc(sizeof(struct _Res));
11
11
 
12
12
  if (NULL != res) {
13
- DEBUG_ALLOC(mem_res)
13
+ DEBUG_ALLOC(mem_res, res)
14
14
  res->next = NULL;
15
15
  atomic_init(&res->message, NULL);
16
+ res->con_kind = CON_HTTP;
16
17
  res->close = false;
18
+ res->ping = false;
19
+ res->pong = false;
17
20
  }
18
21
  return res;
19
22
  }
@@ -26,7 +29,7 @@ res_destroy(Res res) {
26
29
  if (NULL != message) {
27
30
  text_release(message);
28
31
  }
29
- DEBUG_FREE(mem_res)
32
+ DEBUG_FREE(mem_res, res)
30
33
  free(res);
31
34
  }
32
35
  }
@@ -9,11 +9,15 @@
9
9
  #include <ruby.h>
10
10
 
11
11
  #include "text.h"
12
+ #include "types.h"
12
13
 
13
14
  typedef struct _Res {
14
15
  struct _Res *next;
15
16
  _Atomic(Text) message;
17
+ ConKind con_kind;
16
18
  bool close;
19
+ bool ping;
20
+ bool pong;
17
21
  } *Res;
18
22
 
19
23
  extern Res res_create();
@@ -52,23 +52,23 @@ response_free(void *ptr) {
52
52
 
53
53
  while (NULL != (h = res->headers)) {
54
54
  res->headers = h->next;
55
- DEBUG_FREE(mem_header)
55
+ DEBUG_FREE(mem_header, h);
56
+ // TBD
56
57
  xfree(h);
57
58
  }
58
- DEBUG_FREE(mem_res_body);
59
- DEBUG_FREE(mem_response);
59
+ DEBUG_FREE(mem_res_body, res->body);
60
+ DEBUG_FREE(mem_response, ptr);
60
61
  free(res->body); // allocated with strdup
61
62
  xfree(ptr);
62
63
  }
63
64
 
64
65
  VALUE
65
- response_new(Server server ) {
66
+ response_new( ) {
66
67
  Response res = ALLOC(struct _Response);
67
68
 
68
- DEBUG_ALLOC(mem_response)
69
+ DEBUG_ALLOC(mem_response, res)
69
70
  memset(res, 0, sizeof(struct _Response));
70
71
  res->code = 200;
71
- res->server = server;
72
72
 
73
73
  return Data_Wrap_Struct(res_class, NULL, response_free, res);
74
74
  }
@@ -85,7 +85,7 @@ to_s(VALUE self) {
85
85
  int len = response_len(res);
86
86
  char *s = ALLOC_N(char, len + 1);
87
87
 
88
- DEBUG_ALLOC(mem_to_s)
88
+ DEBUG_ALLOC(mem_to_s, s)
89
89
  response_fill(res, s);
90
90
 
91
91
  return rb_str_new(s, len);
@@ -133,10 +133,11 @@ body_set(VALUE self, VALUE val) {
133
133
 
134
134
  if (T_STRING == rb_type(val)) {
135
135
  res->body = strdup(StringValuePtr(val));
136
- DEBUG_ALLOC(mem_res_body)
136
+ DEBUG_ALLOC(mem_res_body, res->body)
137
137
  res->blen = (int)RSTRING_LEN(val);
138
138
  } else {
139
- // TBD use Oj
139
+ rb_raise(rb_eArgError, "Expected a string");
140
+ // TBD use Oj to encode val
140
141
  }
141
142
  return Qnil;
142
143
  }
@@ -215,7 +216,7 @@ head_set(VALUE self, VALUE key, VALUE val) {
215
216
  } else {
216
217
  prev->next = h->next;
217
218
  }
218
- DEBUG_FREE(mem_header)
219
+ DEBUG_FREE(mem_header, h);
219
220
  xfree(h);
220
221
  break;
221
222
  }
@@ -227,12 +228,12 @@ head_set(VALUE self, VALUE key, VALUE val) {
227
228
  vs = StringValuePtr(val);
228
229
  vlen = (int)RSTRING_LEN(val);
229
230
 
230
- if (res->server->pedantic) {
231
+ if (the_server.pedantic) {
231
232
  http_header_ok(ks, klen, vs, vlen);
232
233
  }
233
234
  hlen = klen + vlen + 4;
234
235
  h = (Header)ALLOC_N(char, sizeof(struct _Header) - 8 + hlen + 1);
235
- DEBUG_ALLOC(mem_header)
236
+ DEBUG_ALLOC(mem_header, h)
236
237
 
237
238
  h->next = NULL;
238
239
  h->len = hlen;
@@ -22,12 +22,11 @@ typedef struct _Response {
22
22
  Header headers;
23
23
  int blen;
24
24
  char *body;
25
- Server server;
26
25
  } *Response;
27
26
 
28
27
  extern void response_init(VALUE mod);
29
28
 
30
- extern VALUE response_new(Server server);
29
+ extern VALUE response_new();
31
30
  extern Text response_text(VALUE self);
32
31
 
33
32
  #endif // __AGOO_RESPONSE_H__
@@ -6,7 +6,6 @@
6
6
  #include <netdb.h>
7
7
  #include <netinet/tcp.h>
8
8
  #include <poll.h>
9
- #include <signal.h>
10
9
  #include <stdarg.h>
11
10
  #include <stdio.h>
12
11
  #include <stdlib.h>
@@ -19,6 +18,7 @@
19
18
 
20
19
  #include <ruby.h>
21
20
  #include <ruby/thread.h>
21
+ #include <ruby/encoding.h>
22
22
 
23
23
  #include "con.h"
24
24
  #include "debug.h"
@@ -28,8 +28,14 @@
28
28
  #include "response.h"
29
29
  #include "request.h"
30
30
  #include "server.h"
31
+ #include "sse.h"
32
+ #include "sub.h"
33
+ #include "upgraded.h"
34
+ #include "websocket.h"
31
35
 
32
- static VALUE server_class = Qundef;
36
+ extern void agoo_shutdown();
37
+
38
+ static VALUE server_mod = Qundef;
33
39
 
34
40
  static VALUE connect_sym;
35
41
  static VALUE delete_sym;
@@ -37,102 +43,79 @@ static VALUE get_sym;
37
43
  static VALUE head_sym;
38
44
  static VALUE options_sym;
39
45
  static VALUE post_sym;
46
+ static VALUE push_env_key;
40
47
  static VALUE put_sym;
41
48
 
42
49
  static ID call_id;
43
50
  static ID each_id;
51
+ static ID on_close_id;
52
+ static ID on_drained_id;
53
+ static ID on_message_id;
44
54
  static ID on_request_id;
45
55
  static ID to_i_id;
46
56
 
47
- static Server the_server = NULL;
57
+ static const char err500[] = "HTTP/1.1 500 Internal Server Error\r\n";
48
58
 
49
- static void
50
- shutdown_server(Server server) {
51
- if (NULL != server && server->active) {
52
- the_server = NULL;
53
-
54
- log_cat(&server->info_cat, "Agoo shutting down.");
55
- server->active = false;
56
- if (0 != server->listen_thread) {
57
- pthread_join(server->listen_thread, NULL);
58
- server->listen_thread = 0;
59
- }
60
- if (0 != server->con_thread) {
61
- pthread_join(server->con_thread, NULL);
62
- server->con_thread = 0;
63
- }
64
- queue_cleanup(&server->con_queue);
65
- // The preferred method to of waiting for the ruby threads would be
66
- // either a join or even a kill but since we don't have the gvi here
67
- // that would cause a segfault. Instead we set a timeout and wait for
68
- // the running counter to drop to zero.
69
- if (NULL != server->eval_threads) {
70
- double timeout = dtime() + 2.0;
71
-
72
- while (dtime() < timeout) {
73
- if (0 >= atomic_load(&server->running)) {
74
- break;
59
+ struct _Server the_server = {false};
60
+
61
+ void
62
+ server_shutdown() {
63
+ if (the_server.inited) {
64
+ log_cat(&info_cat, "Agoo shutting down.");
65
+ the_server.inited = false;
66
+ if (the_server.active) {
67
+ the_server.active = false;
68
+ if (0 != the_server.listen_thread) {
69
+ pthread_join(the_server.listen_thread, NULL);
70
+ the_server.listen_thread = 0;
71
+ }
72
+ if (0 != the_server.con_thread) {
73
+ pthread_join(the_server.con_thread, NULL);
74
+ the_server.con_thread = 0;
75
+ }
76
+ sub_cleanup(&the_server.sub_cache);
77
+ cc_cleanup(&the_server.con_cache);
78
+ // The preferred method to of waiting for the ruby threads would
79
+ // be either a join or even a kill but since we may not have the
80
+ // gvl here that would cause a segfault. Instead we set a timeout
81
+ // and wait for the running counter to drop to zero.
82
+ if (NULL != the_server.eval_threads) {
83
+ double timeout = dtime() + 2.0;
84
+
85
+ while (dtime() < timeout) {
86
+ if (0 >= atomic_load(&the_server.running)) {
87
+ break;
88
+ }
89
+ dsleep(0.02);
75
90
  }
76
- dsleep(0.02);
91
+ DEBUG_FREE(mem_eval_threads, the_server.eval_threads);
92
+ free(the_server.eval_threads); // TBD use regular malloc and free
93
+ the_server.eval_threads = NULL;
77
94
  }
78
- DEBUG_FREE(mem_eval_threads)
79
- xfree(server->eval_threads);
80
- server->eval_threads = NULL;
81
- }
82
- while (NULL != server->hooks) {
83
- Hook h = server->hooks;
95
+ while (NULL != the_server.hooks) {
96
+ Hook h = the_server.hooks;
84
97
 
85
- server->hooks = h->next;
86
- hook_destroy(h);
98
+ the_server.hooks = h->next;
99
+ hook_destroy(h);
100
+ }
87
101
  }
88
- queue_cleanup(&server->eval_queue);
89
- log_close(&server->log);
90
- debug_print_stats();
102
+ queue_cleanup(&the_server.con_queue);
103
+ queue_cleanup(&the_server.pub_queue);
104
+ queue_cleanup(&the_server.eval_queue);
105
+ cache_cleanup(&the_server.pages);
106
+ http_cleanup();
91
107
  }
92
108
  }
93
109
 
94
- static void
95
- sig_handler(int sig) {
96
- if (NULL != the_server) {
97
- shutdown_server(the_server);
98
- }
99
- // Use exit instead of rb_exit as rb_exit segfaults most of the time.
100
- //rb_exit(0);
101
- exit(0);
102
- }
103
-
104
- static void
105
- server_free(void *ptr) {
106
- shutdown_server((Server)ptr);
107
- // Commented out for now as it causes a segfault later. Some thread seems
108
- //to be pointing at it even though they have exited so live with a memory
109
- //leak that only shows up when the program exits.
110
- //xfree(ptr);
111
- DEBUG_FREE(mem_server)
112
- the_server = NULL;
113
- }
114
-
115
110
  static int
116
- configure(Err err, Server s, int port, const char *root, VALUE options) {
117
- s->port = port;
118
- s->root = strdup(root);
119
- s->thread_cnt = 0;
120
- s->running = 0;
121
- s->listen_thread = 0;
122
- s->con_thread = 0;
123
- s->log.cats = NULL;
124
- log_cat_reg(&s->log, &s->error_cat, "ERROR", ERROR, RED, true);
125
- log_cat_reg(&s->log, &s->warn_cat, "WARN", WARN, YELLOW, true);
126
- log_cat_reg(&s->log, &s->info_cat, "INFO", INFO, GREEN, true);
127
- log_cat_reg(&s->log, &s->debug_cat, "DEBUG", DEBUG, GRAY, false);
128
- log_cat_reg(&s->log, &s->con_cat, "connect", INFO, GREEN, false);
129
- log_cat_reg(&s->log, &s->req_cat, "request", INFO, CYAN, false);
130
- log_cat_reg(&s->log, &s->resp_cat, "response", INFO, DARK_CYAN, false);
131
- log_cat_reg(&s->log, &s->eval_cat, "eval", INFO, BLUE, false);
132
-
133
- if (ERR_OK != log_init(err, &s->log, options)) {
134
- return err->code;
135
- }
111
+ configure(Err err, int port, const char *root, VALUE options) {
112
+ the_server.port = port;
113
+ the_server.root = strdup(root);
114
+ the_server.thread_cnt = 0;
115
+ the_server.running = 0;
116
+ the_server.listen_thread = 0;
117
+ the_server.con_thread = 0;
118
+ the_server.max_push_pending = 32;
136
119
  if (Qnil != options) {
137
120
  VALUE v;
138
121
 
@@ -140,24 +123,33 @@ configure(Err err, Server s, int port, const char *root, VALUE options) {
140
123
  int tc = FIX2INT(v);
141
124
 
142
125
  if (1 <= tc || tc < 1000) {
143
- s->thread_cnt = tc;
126
+ the_server.thread_cnt = tc;
144
127
  } else {
145
128
  rb_raise(rb_eArgError, "thread_count must be between 1 and 1000.");
146
129
  }
147
130
  }
131
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("max_push_pending"))))) {
132
+ int tc = FIX2INT(v);
133
+
134
+ if (0 <= tc || tc < 1000) {
135
+ the_server.thread_cnt = tc;
136
+ } else {
137
+ rb_raise(rb_eArgError, "thread_count must be between 0 and 1000.");
138
+ }
139
+ }
148
140
  if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("pedantic"))))) {
149
- s->pedantic = (Qtrue == v);
141
+ the_server.pedantic = (Qtrue == v);
150
142
  }
151
143
  if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("Port"))))) {
152
144
  if (rb_cInteger == rb_obj_class(v)) {
153
- s->port = NUM2INT(v);
145
+ the_server.port = NUM2INT(v);
154
146
  } else {
155
147
  switch (rb_type(v)) {
156
148
  case T_STRING:
157
- s->port = atoi(StringValuePtr(v));
149
+ the_server.port = atoi(StringValuePtr(v));
158
150
  break;
159
151
  case T_FIXNUM:
160
- s->port = NUM2INT(v);
152
+ the_server.port = NUM2INT(v);
161
153
  break;
162
154
  default:
163
155
  break;
@@ -166,30 +158,31 @@ configure(Err err, Server s, int port, const char *root, VALUE options) {
166
158
  }
167
159
  if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("quiet"))))) {
168
160
  if (Qtrue == v) {
169
- s->info_cat.on = false;
161
+ info_cat.on = false;
170
162
  }
171
163
  }
172
164
  if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("debug"))))) {
173
165
  if (Qtrue == v) {
174
- s->error_cat.on = true;
175
- s->warn_cat.on = true;
176
- s->info_cat.on = true;
177
- s->debug_cat.on = true;
178
- s->con_cat.on = true;
179
- s->req_cat.on = true;
180
- s->resp_cat.on = true;
181
- s->eval_cat.on = true;
166
+ error_cat.on = true;
167
+ warn_cat.on = true;
168
+ info_cat.on = true;
169
+ debug_cat.on = true;
170
+ con_cat.on = true;
171
+ req_cat.on = true;
172
+ resp_cat.on = true;
173
+ eval_cat.on = true;
174
+ push_cat.on = true;
182
175
  }
183
176
  }
184
177
  }
185
178
  return ERR_OK;
186
179
  }
187
180
 
188
- /* Document-method: new
181
+ /* Document-method: init
189
182
  *
190
- * call-seq: new(port, root, options)
183
+ * call-seq: init(port, root, options)
191
184
  *
192
- * Creates a new server that will listen on the designated _port_ and using
185
+ * Configures the server that will listen on the designated _port_ and using
193
186
  * the _root_ as the root of the static resources. Logging is feature based
194
187
  * and not level based and the options reflect that approach.
195
188
  *
@@ -198,35 +191,16 @@ configure(Err err, Server s, int port, const char *root, VALUE options) {
198
191
  * - *:pedantic* [_true_|_false_] if true response header and status codes are check 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.
199
192
  *
200
193
  * - *: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.
201
- *
202
- * - *:log_dir* [_String_] directory to place log files in. If nil or empty then no log files are written.
203
- *
204
- * - *:log_console* [_true_|_false_] if true log entry are display on the console.
205
- *
206
- * - *:log_classic* [_true_|_false_] if true log entry follow a classic format. If false log entries are JSON.
207
- *
208
- * - *:log_colorize* [_true_|_false_] if true log entries are colorized.
209
- *
210
- * - *:log_states* [_Hash_] a map of logging categories and whether they should be on or off. Categories are:
211
- * - *:ERROR* errors
212
- * - *:WARN* warnings
213
- * - *:INFO* infomational
214
- * - *:DEBUG* debugging
215
- * - *:connect* openning and closing of connections
216
- * - *:request* requests
217
- * - *:response* responses
218
- * - *:eval* handler evaluationss
219
194
  */
220
195
  static VALUE
221
- server_new(int argc, VALUE *argv, VALUE self) {
222
- Server s;
196
+ rserver_init(int argc, VALUE *argv, VALUE self) {
223
197
  struct _Err err = ERR_INIT;
224
198
  int port;
225
199
  const char *root;
226
200
  VALUE options = Qnil;
227
-
201
+
228
202
  if (argc < 2 || 3 < argc) {
229
- rb_raise(rb_eArgError, "Wrong number of arguments to Agoo::Server.new.");
203
+ rb_raise(rb_eArgError, "Wrong number of arguments to Agoo::Server.configure.");
230
204
  }
231
205
  port = FIX2INT(argv[0]);
232
206
  rb_check_type(argv[1], T_STRING);
@@ -234,27 +208,25 @@ server_new(int argc, VALUE *argv, VALUE self) {
234
208
  if (3 <= argc) {
235
209
  options = argv[2];
236
210
  }
237
- s = ALLOC(struct _Server);
238
- DEBUG_ALLOC(mem_server)
239
- memset(s, 0, sizeof(struct _Server));
240
- if (ERR_OK != configure(&err, s, port, root, options)) {
241
- xfree(s);
242
- DEBUG_FREE(mem_server)
243
- // TBD raise Agoo specific exception
211
+ memset(&the_server, 0, sizeof(struct _Server));
212
+ if (ERR_OK != configure(&err, port, root, options)) {
244
213
  rb_raise(rb_eArgError, "%s", err.msg);
245
214
  }
246
- queue_multi_init(&s->con_queue, 256, false, false);
247
- queue_multi_init(&s->eval_queue, 1024, false, true);
215
+ queue_multi_init(&the_server.con_queue, 256, false, false);
216
+ queue_multi_init(&the_server.pub_queue, 256, true, false);
217
+ queue_multi_init(&the_server.eval_queue, 1024, false, true);
248
218
 
249
- cache_init(&s->pages);
250
- the_server = s;
219
+ cache_init(&the_server.pages);
220
+ cc_init(&the_server.con_cache);
221
+ sub_init(&the_server.sub_cache);
251
222
 
252
- return Data_Wrap_Struct(server_class, NULL, server_free, s);
223
+ the_server.inited = true;
224
+
225
+ return Qnil;
253
226
  }
254
227
 
255
228
  static void*
256
229
  listen_loop(void *x) {
257
- Server server = (Server)x;
258
230
  struct sockaddr_in addr;
259
231
  int optval = 1;
260
232
  struct pollfd pa[1];
@@ -267,10 +239,9 @@ listen_loop(void *x) {
267
239
  int i;
268
240
  uint64_t cnt = 0;
269
241
 
270
- atomic_fetch_add(&server->running, 1);
271
242
  if (0 >= (pa->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) {
272
- log_cat(&server->error_cat, "Server failed to open server socket. %s.", strerror(errno));
273
- atomic_fetch_sub(&server->running, 1);
243
+ log_cat(&error_cat, "Server failed to open server socket. %s.", strerror(errno));
244
+ atomic_fetch_sub(&the_server.running, 1);
274
245
  return NULL;
275
246
  }
276
247
  #ifdef OSX_OS
@@ -279,26 +250,26 @@ listen_loop(void *x) {
279
250
  memset(&addr, 0, sizeof(addr));
280
251
  addr.sin_family = AF_INET;
281
252
  addr.sin_addr.s_addr = INADDR_ANY;
282
- addr.sin_port = htons(server->port);
253
+ addr.sin_port = htons(the_server.port);
283
254
  setsockopt(pa->fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
284
255
  setsockopt(pa->fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
285
256
  if (0 > bind(fp->fd, (struct sockaddr*)&addr, sizeof(addr))) {
286
- log_cat(&server->error_cat, "Server failed to bind server socket. %s.", strerror(errno));
287
- atomic_fetch_sub(&server->running, 1);
257
+ log_cat(&error_cat, "Server failed to bind server socket. %s.", strerror(errno));
258
+ atomic_fetch_sub(&the_server.running, 1);
288
259
  return NULL;
289
260
  }
290
261
  listen(pa->fd, 1000);
291
- server->ready = true;
292
262
  pa->events = POLLIN;
293
263
  pa->revents = 0;
294
264
 
295
265
  memset(&client_addr, 0, sizeof(client_addr));
296
- while (server->active) {
266
+ atomic_fetch_add(&the_server.running, 1);
267
+ while (the_server.active) {
297
268
  if (0 > (i = poll(pa, 1, 100))) {
298
269
  if (EAGAIN == errno) {
299
270
  continue;
300
271
  }
301
- log_cat(&server->error_cat, "Server polling error. %s.", strerror(errno));
272
+ log_cat(&error_cat, "Server polling error. %s.", strerror(errno));
302
273
  // Either a signal or something bad like out of memory. Might as well exit.
303
274
  break;
304
275
  }
@@ -307,9 +278,9 @@ listen_loop(void *x) {
307
278
  }
308
279
  if (0 != (pa->revents & POLLIN)) {
309
280
  if (0 > (client_sock = accept(pa->fd, (struct sockaddr*)&client_addr, &alen))) {
310
- log_cat(&server->error_cat, "Server accept connection failed. %s.", strerror(errno));
311
- } else if (NULL == (con = con_create(&err, server, client_sock, ++cnt))) {
312
- log_cat(&server->error_cat, "Server accept connection failed. %s.", err.msg);
281
+ log_cat(&error_cat, "Server accept connection failed. %s.", strerror(errno));
282
+ } else if (NULL == (con = con_create(&err, client_sock, ++cnt))) {
283
+ log_cat(&error_cat, "Server accept connection failed. %s.", err.msg);
313
284
  close(client_sock);
314
285
  cnt--;
315
286
  err_clear(&err);
@@ -320,22 +291,22 @@ listen_loop(void *x) {
320
291
  fcntl(client_sock, F_SETFL, O_NONBLOCK);
321
292
  setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
322
293
  setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
323
- log_cat(&server->con_cat, "Server accepted connection %llu on port %d [%d]", (unsigned long long)cnt, server->port, con->sock);
324
- queue_push(&server->con_queue, (void*)con);
294
+ log_cat(&con_cat, "Server accepted connection %llu on port %d [%d]", (unsigned long long)cnt, the_server.port, con->sock);
295
+ queue_push(&the_server.con_queue, (void*)con);
325
296
  }
326
297
  }
327
298
  if (0 != (pa->revents & (POLLERR | POLLHUP | POLLNVAL))) {
328
299
  if (0 != (pa->revents & (POLLHUP | POLLNVAL))) {
329
- log_cat(&server->error_cat, "Agoo server socket on port %d closed.", server->port);
300
+ log_cat(&error_cat, "Agoo server socket on port %d closed.", the_server.port);
330
301
  } else {
331
- log_cat(&server->error_cat, "Agoo server socket on port %d error.", server->port);
302
+ log_cat(&error_cat, "Agoo server socket on port %d error.", the_server.port);
332
303
  }
333
- server->active = false;
304
+ the_server.active = false;
334
305
  }
335
306
  pa->revents = 0;
336
307
  }
337
308
  close(pa->fd);
338
- atomic_fetch_sub(&server->running, 1);
309
+ atomic_fetch_sub(&the_server.running, 1);
339
310
 
340
311
  return NULL;
341
312
  }
@@ -362,7 +333,7 @@ rescue_error(VALUE x) {
362
333
 
363
334
  req->res->close = true;
364
335
  res_set_message(req->res, message);
365
- queue_wakeup(&req->server->con_queue);
336
+ queue_wakeup(&the_server.con_queue);
366
337
 
367
338
  return Qfalse;
368
339
  }
@@ -371,13 +342,13 @@ static VALUE
371
342
  handle_base_inner(void *x) {
372
343
  Req req = (Req)x;
373
344
  volatile VALUE rr = request_wrap(req);
374
- volatile VALUE rres = response_new(req->server);
345
+ volatile VALUE rres = response_new();
375
346
 
376
347
  rb_funcall(req->handler, on_request_id, 2, rr, rres);
377
348
  DATA_PTR(rr) = NULL;
378
349
 
379
350
  res_set_message(req->res, response_text(rres));
380
- queue_wakeup(&req->server->con_queue);
351
+ queue_wakeup(&the_server.con_queue);
381
352
 
382
353
  return Qfalse;
383
354
  }
@@ -512,13 +483,44 @@ handle_rack_inner(void *x) {
512
483
  t->len = snprintf(t->text, 1024, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n", code, status_msg, bsize);
513
484
  break;
514
485
  }
486
+ if (code < 300) {
487
+ switch (req->upgrade) {
488
+ case UP_WS:
489
+ if (CON_WS != req->res->con_kind ||
490
+ Qnil == (req->handler = rb_hash_lookup(env, push_env_key))) {
491
+ strcpy(t->text, err500);
492
+ t->len = sizeof(err500) - 1;
493
+ break;
494
+ }
495
+ req->handler_type = PUSH_HOOK;
496
+ upgraded_extend(req->cid, req->handler);
497
+ t->len = snprintf(t->text, 1024, "HTTP/1.1 101 %s\r\n", status_msg);
498
+ t = ws_add_headers(req, t);
499
+ break;
500
+ case UP_SSE:
501
+ if (CON_SSE != req->res->con_kind ||
502
+ Qnil == (req->handler = rb_hash_lookup(env, push_env_key))) {
503
+ strcpy(t->text, err500);
504
+ t->len = sizeof(err500) - 1;
505
+ break;
506
+ }
507
+ req->handler_type = PUSH_HOOK;
508
+ upgraded_extend(req->cid, req->handler);
509
+ t = sse_upgrade(req, t);
510
+ res_set_message(req->res, t);
511
+ queue_wakeup(&the_server.con_queue);
512
+ return Qfalse;
513
+ default:
514
+ break;
515
+ }
516
+ }
515
517
  if (HEAD == req->method) {
516
518
  bsize = 0;
517
519
  } else {
518
520
  if (T_HASH == rb_type(hv)) {
519
521
  rb_hash_foreach(hv, header_cb, (VALUE)&t);
520
522
  } else {
521
- rb_iterate (rb_each, hv, header_each_cb, (VALUE)&t);
523
+ rb_iterate(rb_each, hv, header_each_cb, (VALUE)&t);
522
524
  }
523
525
  }
524
526
  t = text_append(t, "\r\n", 2);
@@ -537,7 +539,7 @@ handle_rack_inner(void *x) {
537
539
  }
538
540
  }
539
541
  res_set_message(req->res, t);
540
- queue_wakeup(&req->server->con_queue);
542
+ queue_wakeup(&the_server.con_queue);
541
543
 
542
544
  return Qfalse;
543
545
  }
@@ -559,13 +561,13 @@ static VALUE
559
561
  handle_wab_inner(void *x) {
560
562
  Req req = (Req)x;
561
563
  volatile VALUE rr = request_wrap(req);
562
- volatile VALUE rres = response_new(req->server);
564
+ volatile VALUE rres = response_new();
563
565
 
564
566
  rb_funcall(req->handler, on_request_id, 2, rr, rres);
565
567
  DATA_PTR(rr) = NULL;
566
568
 
567
569
  res_set_message(req->res, response_text(rres));
568
- queue_wakeup(&req->server->con_queue);
570
+ queue_wakeup(&the_server.con_queue);
569
571
 
570
572
  return Qfalse;
571
573
  }
@@ -576,17 +578,73 @@ handle_wab(void *x) {
576
578
 
577
579
  return NULL;
578
580
  }
581
+
582
+ static VALUE
583
+ handle_push_inner(void *x) {
584
+ Req req = (Req)x;
585
+
586
+ switch (req->method) {
587
+ case ON_MSG:
588
+ rb_funcall(req->handler, on_message_id, 1, rb_str_new(req->msg, req->mlen));
589
+ break;
590
+ case ON_BIN: {
591
+ volatile VALUE rstr = rb_str_new(req->msg, req->mlen);
592
+
593
+ rb_enc_associate(rstr, rb_ascii8bit_encoding());
594
+ rb_funcall(req->handler, on_message_id, 1, rstr);
595
+ break;
596
+ }
597
+ case ON_CLOSE:
598
+ rb_funcall(req->handler, on_close_id, 0);
599
+ break;
600
+ case ON_SHUTDOWN:
601
+ rb_funcall(req->handler, rb_intern("on_shutdown"), 0);
602
+ break;
603
+ case ON_EMPTY:
604
+ rb_funcall(req->handler, on_drained_id, 0);
605
+ break;
606
+ default:
607
+ break;
608
+ }
609
+ return Qfalse;
610
+ }
611
+
612
+ static void*
613
+ handle_push(void *x) {
614
+ rb_rescue2(handle_push_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
615
+ return NULL;
616
+ }
617
+
579
618
  static void
580
- handle_protected(Req req) {
619
+ handle_protected(Req req, bool gvi) {
581
620
  switch (req->handler_type) {
582
621
  case BASE_HOOK:
583
- rb_thread_call_with_gvl(handle_base, req);
622
+ if (gvi) {
623
+ rb_thread_call_with_gvl(handle_base, req);
624
+ } else {
625
+ handle_base(req);
626
+ }
584
627
  break;
585
628
  case RACK_HOOK:
586
- rb_thread_call_with_gvl(handle_rack, req);
629
+ if (gvi) {
630
+ rb_thread_call_with_gvl(handle_rack, req);
631
+ } else {
632
+ handle_rack(req);
633
+ }
587
634
  break;
588
635
  case WAB_HOOK:
589
- rb_thread_call_with_gvl(handle_wab, req);
636
+ if (gvi) {
637
+ rb_thread_call_with_gvl(handle_wab, req);
638
+ } else {
639
+ handle_wab(req);
640
+ }
641
+ break;
642
+ case PUSH_HOOK:
643
+ if (gvi) {
644
+ rb_thread_call_with_gvl(handle_push, req);
645
+ } else {
646
+ handle_push(req);
647
+ }
590
648
  break;
591
649
  default: {
592
650
  char buf[256];
@@ -595,7 +653,7 @@ handle_protected(Req req) {
595
653
 
596
654
  req->res->close = true;
597
655
  res_set_message(req->res, message);
598
- queue_wakeup(&req->server->con_queue);
656
+ queue_wakeup(&the_server.con_queue);
599
657
  break;
600
658
  }
601
659
  }
@@ -603,25 +661,23 @@ handle_protected(Req req) {
603
661
 
604
662
  static void*
605
663
  process_loop(void *ptr) {
606
- Server server = (Server)ptr;
607
- Req req;
664
+ Req req;
608
665
 
609
- atomic_fetch_add(&server->running, 1);
610
- while (server->active) {
611
- if (NULL != (req = (Req)queue_pop(&server->eval_queue, 0.1))) {
612
- handle_protected(req);
613
- free(req);
614
- DEBUG_FREE(mem_req)
666
+ atomic_fetch_add(&the_server.running, 1);
667
+ while (the_server.active) {
668
+ if (NULL != (req = (Req)queue_pop(&the_server.eval_queue, 0.1))) {
669
+ handle_protected(req, true);
670
+ request_destroy(req);
615
671
  }
616
672
  }
617
- atomic_fetch_sub(&server->running, 1);
618
-
673
+ atomic_fetch_sub(&the_server.running, 1);
674
+
619
675
  return NULL;
620
676
  }
621
677
 
622
678
  static VALUE
623
679
  wrap_process_loop(void *ptr) {
624
- rb_thread_call_without_gvl(process_loop, ptr, RUBY_UBF_IO, NULL);
680
+ rb_thread_call_without_gvl(process_loop, NULL, RUBY_UBF_IO, NULL);
625
681
  return Qnil;
626
682
  }
627
683
 
@@ -632,73 +688,60 @@ wrap_process_loop(void *ptr) {
632
688
  * Start the server.
633
689
  */
634
690
  static VALUE
635
- start(VALUE self) {
636
- Server server = (Server)DATA_PTR(self);
691
+ server_start(VALUE self) {
637
692
  VALUE *vp;
638
693
  int i;
639
694
  double giveup;
640
695
 
641
- server->active = true;
642
- server->ready = false;
696
+ the_server.active = true;
643
697
 
644
- pthread_create(&server->listen_thread, NULL, listen_loop, server);
645
- pthread_create(&server->con_thread, NULL, con_loop, server);
698
+ pthread_create(&the_server.listen_thread, NULL, listen_loop, NULL);
699
+ pthread_create(&the_server.con_thread, NULL, con_loop, NULL);
646
700
 
647
701
  giveup = dtime() + 1.0;
648
702
  while (dtime() < giveup) {
649
- if (server->ready) {
703
+ if (2 <= atomic_load(&the_server.running)) {
650
704
  break;
651
705
  }
652
- dsleep(0.001);
706
+ dsleep(0.01);
653
707
  }
654
- signal(SIGINT, sig_handler);
655
- signal(SIGTERM, sig_handler);
656
- signal(SIGPIPE, SIG_IGN);
657
-
658
- if (server->info_cat.on) {
708
+ if (info_cat.on) {
659
709
  VALUE agoo = rb_const_get_at(rb_cObject, rb_intern("Agoo"));
660
710
  VALUE v = rb_const_get_at(agoo, rb_intern("VERSION"));
661
711
 
662
- log_cat(&server->info_cat, "Agoo %s listening on port %d.", StringValuePtr(v), server->port);
712
+ log_cat(&info_cat, "Agoo %s listening on port %d.", StringValuePtr(v), the_server.port);
663
713
  }
664
- if (0 >= server->thread_cnt) {
714
+ if (0 >= the_server.thread_cnt) {
665
715
  Req req;
666
716
 
667
- while (server->active) {
668
- if (NULL != (req = (Req)queue_pop(&server->eval_queue, 0.1))) {
669
- switch (req->handler_type) {
670
- case BASE_HOOK:
671
- handle_base(req);
672
- break;
673
- case RACK_HOOK:
674
- handle_rack(req);
675
- break;
676
- case WAB_HOOK:
677
- handle_wab(req);
678
- break;
679
- default: {
680
- char buf[256];
681
- int cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n");
682
- Text message = text_create(buf, cnt);
683
-
684
- req->res->close = true;
685
- res_set_message(req->res, message);
686
- queue_wakeup(&req->server->con_queue);
687
- break;
688
- }
689
- }
690
- free(req);
691
- DEBUG_FREE(mem_req)
717
+ while (the_server.active) {
718
+ if (NULL != (req = (Req)queue_pop(&the_server.eval_queue, 0.1))) {
719
+ handle_protected(req, false);
720
+ request_destroy(req);
721
+ } else {
722
+ rb_thread_schedule();
692
723
  }
724
+
693
725
  }
694
726
  } else {
695
- server->eval_threads = ALLOC_N(VALUE, server->thread_cnt + 1);
696
- DEBUG_ALLOC(mem_eval_threads)
727
+ the_server.eval_threads = (VALUE*)malloc(sizeof(VALUE) * (the_server.thread_cnt + 1));
728
+ DEBUG_ALLOC(mem_eval_threads, the_server.eval_threads);
697
729
 
698
- for (i = server->thread_cnt, vp = server->eval_threads; 0 < i; i--, vp++) {
699
- *vp = rb_thread_create(wrap_process_loop, server);
730
+ for (i = the_server.thread_cnt, vp = the_server.eval_threads; 0 < i; i--, vp++) {
731
+ *vp = rb_thread_create(wrap_process_loop, NULL);
700
732
  }
701
733
  *vp = Qnil;
734
+
735
+ giveup = dtime() + 1.0;
736
+ while (dtime() < giveup) {
737
+ // The processing threads will not start until this thread
738
+ // releases ownership so do that and then see if the threads has
739
+ // been started yet.
740
+ rb_thread_schedule();
741
+ if (2 + the_server.thread_cnt <= atomic_load(&the_server.running)) {
742
+ break;
743
+ }
744
+ }
702
745
  }
703
746
  return Qnil;
704
747
  }
@@ -710,176 +753,8 @@ start(VALUE self) {
710
753
  * Shutdown the server. Logs and queues are flushed before shutting down.
711
754
  */
712
755
  static VALUE
713
- server_shutdown(VALUE self) {
714
- shutdown_server((Server)DATA_PTR(self));
715
- return Qnil;
716
- }
717
-
718
- /* Document-method: error?
719
- *
720
- * call-seq: error?()
721
- *
722
- * Returns true is errors are being logged.
723
- */
724
- static VALUE
725
- log_errorp(VALUE self) {
726
- return ((Server)DATA_PTR(self))->error_cat.on ? Qtrue : Qfalse;
727
- }
728
-
729
- /* Document-method: warn?
730
- *
731
- * call-seq: warn?()
732
- *
733
- * Returns true is warnings are being logged.
734
- */
735
- static VALUE
736
- log_warnp(VALUE self) {
737
- return ((Server)DATA_PTR(self))->warn_cat.on ? Qtrue : Qfalse;
738
- }
739
-
740
- /* Document-method: info?
741
- *
742
- * call-seq: info?()
743
- *
744
- * Returns true is info entries are being logged.
745
- */
746
- static VALUE
747
- log_infop(VALUE self) {
748
- return ((Server)DATA_PTR(self))->info_cat.on ? Qtrue : Qfalse;
749
- }
750
-
751
- /* Document-method: debug?
752
- *
753
- * call-seq: debug?()
754
- *
755
- * Returns true is debug entries are being logged.
756
- */
757
- static VALUE
758
- log_debugp(VALUE self) {
759
- return ((Server)DATA_PTR(self))->debug_cat.on ? Qtrue : Qfalse;
760
- }
761
-
762
- /* Document-method: eval?
763
- *
764
- * call-seq: eval?()
765
- *
766
- * Returns true is handler evaluation entries are being logged.
767
- */
768
- static VALUE
769
- log_evalp(VALUE self) {
770
- return ((Server)DATA_PTR(self))->eval_cat.on ? Qtrue : Qfalse;
771
- }
772
-
773
- /* Document-method: error
774
- *
775
- * call-seq: error(msg)
776
- *
777
- * Log an error message.
778
- */
779
- static VALUE
780
- log_error(VALUE self, VALUE msg) {
781
- log_cat(&((Server)DATA_PTR(self))->error_cat, "%s", StringValuePtr(msg));
782
- return Qnil;
783
- }
784
-
785
- /* Document-method: warn
786
- *
787
- * call-seq: warn(msg)
788
- *
789
- * Log a warn message.
790
- */
791
- static VALUE
792
- log_warn(VALUE self, VALUE msg) {
793
- log_cat(&((Server)DATA_PTR(self))->warn_cat, "%s", StringValuePtr(msg));
794
- return Qnil;
795
- }
796
-
797
- /* Document-method: info
798
- *
799
- * call-seq: info(msg)
800
- *
801
- * Log an info message.
802
- */
803
- static VALUE
804
- log_info(VALUE self, VALUE msg) {
805
- log_cat(&((Server)DATA_PTR(self))->info_cat, "%s", StringValuePtr(msg));
806
- return Qnil;
807
- }
808
-
809
- /* Document-method: debug
810
- *
811
- * call-seq: debug(msg)
812
- *
813
- * Log a debug message.
814
- */
815
- static VALUE
816
- log_debug(VALUE self, VALUE msg) {
817
- log_cat(&((Server)DATA_PTR(self))->debug_cat, "%s", StringValuePtr(msg));
818
- return Qnil;
819
- }
820
-
821
- /* Document-method: log_eval
822
- *
823
- * call-seq: log_eval(msg)
824
- *
825
- * Log an eval message.
826
- */
827
- static VALUE
828
- log_eval(VALUE self, VALUE msg) {
829
- log_cat(&((Server)DATA_PTR(self))->eval_cat, "%s", StringValuePtr(msg));
830
- return Qnil;
831
- }
832
-
833
- /* Document-method: log_state
834
- *
835
- * call-seq: log_state(label)
836
- *
837
- * Return the logging state of the category identified by the _label_.
838
- */
839
- static VALUE
840
- log_state(VALUE self, VALUE label) {
841
- Server server = (Server)DATA_PTR(self);
842
- LogCat cat = log_cat_find(&server->log, StringValuePtr(label));
843
-
844
- if (NULL == cat) {
845
- rb_raise(rb_eArgError, "%s is not a valid log category.", StringValuePtr(label));
846
- }
847
- return cat->on ? Qtrue : Qfalse;
848
- }
849
-
850
- /* Document-method: set_log_state
851
- *
852
- * call-seq: set_log_state(label, state)
853
- *
854
- * Set the logging state of the category identified by the _label_.
855
- */
856
- static VALUE
857
- set_log_state(VALUE self, VALUE label, VALUE on) {
858
- Server server = (Server)DATA_PTR(self);
859
- LogCat cat = log_cat_find(&server->log, StringValuePtr(label));
860
-
861
- if (NULL == cat) {
862
- rb_raise(rb_eArgError, "%s is not a valid log category.", StringValuePtr(label));
863
- }
864
- cat->on = (Qtrue == on);
865
-
866
- return Qnil;
867
- }
868
-
869
- /* Document-method: log_flush
870
- *
871
- * call-seq: log_flush()
872
- *
873
- * Flush the log queue and write all entries to disk or the console. The call
874
- * waits for the flush to complete.
875
- */
876
- static VALUE
877
- server_log_flush(VALUE self, VALUE to) {
878
- double timeout = NUM2DBL(to);
879
-
880
- if (!log_flush(&((Server)DATA_PTR(self))->log, timeout)) {
881
- rb_raise(rb_eStandardError, "timed out waiting for log flush.");
882
- }
756
+ rserver_shutdown(VALUE self) {
757
+ server_shutdown();
883
758
  return Qnil;
884
759
  }
885
760
 
@@ -893,7 +768,6 @@ server_log_flush(VALUE self, VALUE to) {
893
768
  */
894
769
  static VALUE
895
770
  handle(VALUE self, VALUE method, VALUE pattern, VALUE handler) {
896
- Server server = (Server)DATA_PTR(self);
897
771
  Hook hook;
898
772
  Method meth = ALL;
899
773
  const char *pat;
@@ -926,13 +800,13 @@ handle(VALUE self, VALUE method, VALUE pattern, VALUE handler) {
926
800
  Hook h;
927
801
  Hook prev = NULL;
928
802
 
929
- for (h = server->hooks; NULL != h; h = h->next) {
803
+ for (h = the_server.hooks; NULL != h; h = h->next) {
930
804
  prev = h;
931
805
  }
932
806
  if (NULL != prev) {
933
807
  prev->next = hook;
934
808
  } else {
935
- server->hooks = hook;
809
+ the_server.hooks = hook;
936
810
  }
937
811
  rb_gc_register_address(&hook->handler);
938
812
  }
@@ -948,12 +822,10 @@ handle(VALUE self, VALUE method, VALUE pattern, VALUE handler) {
948
822
  */
949
823
  static VALUE
950
824
  handle_not_found(VALUE self, VALUE handler) {
951
- Server server = (Server)DATA_PTR(self);
952
-
953
- if (NULL == (server->hook404 = hook_create(GET, "/", handler))) {
825
+ if (NULL == (the_server.hook404 = hook_create(GET, "/", handler))) {
954
826
  rb_raise(rb_eStandardError, "out of memory.");
955
827
  }
956
- rb_gc_register_address(&server->hook404->handler);
828
+ rb_gc_register_address(&the_server.hook404->handler);
957
829
 
958
830
  return Qnil;
959
831
  }
@@ -967,7 +839,7 @@ handle_not_found(VALUE self, VALUE handler) {
967
839
  */
968
840
  static VALUE
969
841
  add_mime(VALUE self, VALUE suffix, VALUE type) {
970
- mime_set(&((Server)DATA_PTR(self))->pages, StringValuePtr(suffix), StringValuePtr(type));
842
+ mime_set(&the_server.pages, StringValuePtr(suffix), StringValuePtr(type));
971
843
 
972
844
  return Qnil;
973
845
  }
@@ -979,43 +851,33 @@ add_mime(VALUE self, VALUE suffix, VALUE type) {
979
851
  */
980
852
  void
981
853
  server_init(VALUE mod) {
982
- server_class = rb_define_class_under(mod, "Server", rb_cObject);
983
-
984
- rb_define_module_function(server_class, "new", server_new, -1);
985
- rb_define_method(server_class, "start", start, 0);
986
- rb_define_method(server_class, "shutdown", server_shutdown, 0);
987
-
988
- rb_define_method(server_class, "error?", log_errorp, 0);
989
- rb_define_method(server_class, "warn?", log_warnp, 0);
990
- rb_define_method(server_class, "info?", log_infop, 0);
991
- rb_define_method(server_class, "debug?", log_debugp, 0);
992
- rb_define_method(server_class, "log_eval?", log_evalp, 0);
993
- rb_define_method(server_class, "error", log_error, 1);
994
- rb_define_method(server_class, "warn", log_warn, 1);
995
- rb_define_method(server_class, "info", log_info, 1);
996
- rb_define_method(server_class, "debug", log_debug, 1);
997
- rb_define_method(server_class, "log_eval", log_eval, 1);
998
-
999
- rb_define_method(server_class, "log_state", log_state, 1);
1000
- rb_define_method(server_class, "set_log_state", set_log_state, 2);
1001
- rb_define_method(server_class, "log_flush", server_log_flush, 1);
1002
-
1003
- rb_define_method(server_class, "handle", handle, 3);
1004
- rb_define_method(server_class, "handle_not_found", handle_not_found, 1);
1005
- rb_define_method(server_class, "add_mime", add_mime, 2);
854
+ server_mod = rb_define_module_under(mod, "Server");
855
+
856
+ rb_define_module_function(server_mod, "init", rserver_init, -1);
857
+ rb_define_module_function(server_mod, "start", server_start, 0);
858
+ rb_define_module_function(server_mod, "shutdown", rserver_shutdown, 0);
859
+
860
+ rb_define_module_function(server_mod, "handle", handle, 3);
861
+ rb_define_module_function(server_mod, "handle_not_found", handle_not_found, 1);
862
+ rb_define_module_function(server_mod, "add_mime", add_mime, 2);
1006
863
 
1007
864
  call_id = rb_intern("call");
1008
865
  each_id = rb_intern("each");
866
+ on_close_id = rb_intern("on_close");
867
+ on_drained_id = rb_intern("on_drained");
868
+ on_message_id = rb_intern("on_message");
1009
869
  on_request_id = rb_intern("on_request");
1010
870
  to_i_id = rb_intern("to_i");
1011
871
 
1012
- connect_sym = ID2SYM(rb_intern("CONNECT")); rb_gc_register_address(&connect_sym);
1013
- delete_sym = ID2SYM(rb_intern("DELETE")); rb_gc_register_address(&delete_sym);
1014
- get_sym = ID2SYM(rb_intern("GET")); rb_gc_register_address(&get_sym);
1015
- head_sym = ID2SYM(rb_intern("HEAD")); rb_gc_register_address(&head_sym);
1016
- options_sym = ID2SYM(rb_intern("OPTIONS")); rb_gc_register_address(&options_sym);
1017
- post_sym = ID2SYM(rb_intern("POST")); rb_gc_register_address(&post_sym);
1018
- put_sym = ID2SYM(rb_intern("PUT")); rb_gc_register_address(&put_sym);
872
+ connect_sym = ID2SYM(rb_intern("CONNECT")); rb_gc_register_address(&connect_sym);
873
+ delete_sym = ID2SYM(rb_intern("DELETE")); rb_gc_register_address(&delete_sym);
874
+ get_sym = ID2SYM(rb_intern("GET")); rb_gc_register_address(&get_sym);
875
+ head_sym = ID2SYM(rb_intern("HEAD")); rb_gc_register_address(&head_sym);
876
+ options_sym = ID2SYM(rb_intern("OPTIONS")); rb_gc_register_address(&options_sym);
877
+ post_sym = ID2SYM(rb_intern("POST")); rb_gc_register_address(&post_sym);
878
+ put_sym = ID2SYM(rb_intern("PUT")); rb_gc_register_address(&put_sym);
879
+
880
+ push_env_key = rb_str_new_cstr("rack.upgrade"); rb_gc_register_address(&push_env_key);
1019
881
 
1020
882
  http_init();
1021
883
  }