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.

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