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
@@ -0,0 +1,53 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_CCACHE_H__
4
+ #define __AGOO_CCACHE_H__
5
+
6
+ #include <pthread.h>
7
+ #include <stdatomic.h>
8
+ #include <stdbool.h>
9
+ #include <stdint.h>
10
+
11
+ #include <ruby.h>
12
+
13
+ #define CC_BUCKET_SIZE 1024
14
+ #define CC_BUCKET_MASK 1023
15
+
16
+ struct _Con;
17
+
18
+ typedef struct _CSlot {
19
+ struct _CSlot *next;
20
+ uint64_t cid;
21
+ struct _Con *con;
22
+ VALUE handler;
23
+ atomic_int pending;
24
+ atomic_int ref_cnt;
25
+ bool on_empty;
26
+ bool on_close;
27
+ bool on_shut;
28
+ bool on_msg;
29
+ } *CSlot;
30
+
31
+ typedef struct _CCache {
32
+ CSlot buckets[CC_BUCKET_SIZE];
33
+ pthread_mutex_t lock;
34
+ VALUE wrap;
35
+ } *CCache;
36
+
37
+ extern void cc_init(CCache cc);
38
+ extern void cc_cleanup(CCache cc);
39
+ extern CSlot cc_set_con(CCache cc, struct _Con *con);
40
+ extern void cc_set_handler(CCache cc, uint64_t cid, VALUE handler, bool on_empty, bool on_close, bool on_shut, bool on_msg);
41
+ extern void cc_remove(CCache cc, uint64_t cid);
42
+
43
+ extern void cc_remove_con(CCache cc, uint64_t cid);
44
+ extern VALUE cc_ref_dec(CCache cc, uint64_t cid);
45
+
46
+ extern struct _Con* cc_get_con(CCache cc, uint64_t cid);
47
+ extern VALUE cc_get_handler(CCache cc, uint64_t cid);
48
+ extern int cc_get_pending(CCache cc, uint64_t cid);
49
+ extern CSlot cc_get_slot(CCache cc, uint64_t cid);
50
+
51
+ extern void cc_pending_inc(CCache cc, uint64_t cid);
52
+
53
+ #endif /* __AGOO_CCACHE_H__ */
@@ -8,14 +8,17 @@
8
8
  #include "dtime.h"
9
9
  #include "hook.h"
10
10
  #include "http.h"
11
+ #include "pub.h"
11
12
  #include "res.h"
12
13
  #include "server.h"
14
+ #include "sse.h"
15
+ #include "websocket.h"
13
16
 
14
17
  #define MAX_SOCK 4096
15
18
  #define CON_TIMEOUT 5.0
16
19
 
17
20
  Con
18
- con_create(Err err, Server server, int sock, uint64_t id) {
21
+ con_create(Err err, int sock, uint64_t id) {
19
22
  Con c;
20
23
 
21
24
  if (MAX_SOCK <= sock) {
@@ -25,26 +28,33 @@ con_create(Err err, Server server, int sock, uint64_t id) {
25
28
  if (NULL == (c = (Con)malloc(sizeof(struct _Con)))) {
26
29
  err_set(err, ERR_MEMORY, "Failed to allocate memory for a connection.");
27
30
  } else {
28
- DEBUG_ALLOC(mem_con)
31
+ DEBUG_ALLOC(mem_con, c)
29
32
  memset(c, 0, sizeof(struct _Con));
30
33
  c->sock = sock;
31
- c->iid = id;
32
- c->server = server;
34
+ c->id = id;
35
+ c->kind = CON_HTTP;
36
+ c->timeout = dtime() + CON_TIMEOUT;
33
37
  }
34
38
  return c;
35
39
  }
36
40
 
37
41
  void
38
42
  con_destroy(Con c) {
43
+ if (CON_WS == c->kind || CON_SSE == c->kind) {
44
+ ws_req_close(c);
45
+ }
39
46
  if (0 < c->sock) {
40
47
  close(c->sock);
41
48
  c->sock = 0;
42
49
  }
43
50
  if (NULL != c->req) {
44
- free(c->req);
45
- DEBUG_FREE(mem_req)
51
+ request_destroy(c->req);
52
+ }
53
+ if (NULL != c->slot) {
54
+ cc_remove_con(&the_server.con_cache, c->id);
55
+ c->slot = NULL;
46
56
  }
47
- DEBUG_FREE(mem_con)
57
+ DEBUG_FREE(mem_con, c)
48
58
  free(c);
49
59
  }
50
60
 
@@ -85,13 +95,12 @@ bad_request(Con c, int status, int line) {
85
95
  const char *msg = http_code_message(status);
86
96
 
87
97
  if (NULL == (res = res_create())) {
88
- log_cat(&c->server->error_cat, "memory allocation of response failed on connection %llu @ %d.", c->iid, line);
98
+ log_cat(&error_cat, "memory allocation of response failed on connection %llu @ %d.", c->id, line);
89
99
  } else {
90
100
  char buf[256];
91
101
  int cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 %d %s\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n", status, msg);
92
102
  Text message = text_create(buf, cnt);
93
103
 
94
- DEBUG_ALLOC(mem_res)
95
104
  if (NULL == c->res_tail) {
96
105
  c->res_head = res;
97
106
  } else {
@@ -122,7 +131,6 @@ should_close(const char *header, int hlen) {
122
131
  // negative of message length - when message is handled here.
123
132
  static long
124
133
  con_header_read(Con c) {
125
- Server server = c->server;
126
134
  char *hend = strstr(c->buf, "\r\n\r\n");
127
135
  Method method;
128
136
  char *path;
@@ -140,9 +148,9 @@ con_header_read(Con c) {
140
148
  }
141
149
  return 0;
142
150
  }
143
- if (server->req_cat.on) {
151
+ if (req_cat.on) {
144
152
  *hend = '\0';
145
- log_cat(&server->req_cat, "%llu: %s", c->iid, c->buf);
153
+ log_cat(&req_cat, "%llu: %s", c->id, c->buf);
146
154
  *hend = '\r';
147
155
  }
148
156
  for (b = c->buf; ' ' != *b; b++) {
@@ -231,17 +239,17 @@ con_header_read(Con c) {
231
239
  qend = b;
232
240
  }
233
241
  mlen = hend - c->buf + 4 + clen;
234
- if (NULL == (hook = hook_find(server->hooks, method, path, pend))) {
242
+ if (NULL == (hook = hook_find(the_server.hooks, method, path, pend))) {
235
243
  if (GET == method) {
236
244
  struct _Err err = ERR_INIT;
237
- Page p = page_get(&err, &server->pages, server->root, path, (int)(pend - path));
245
+ Page p = page_get(&err, &the_server.pages, the_server.root, path, (int)(pend - path));
238
246
  Res res;
239
247
 
240
248
  if (NULL == p) {
241
- if (NULL != server->hook404) {
249
+ if (NULL != the_server.hook404) {
242
250
  // There would be too many parameters to pass to a
243
251
  // separate function so just goto the hook processing.
244
- hook = server->hook404;
252
+ hook = the_server.hook404;
245
253
  goto HOOKED;
246
254
  }
247
255
  return bad_request(c, 404, __LINE__);
@@ -258,8 +266,6 @@ con_header_read(Con c) {
258
266
 
259
267
  b = strstr(c->buf, "\r\n");
260
268
  res->close = should_close(b, (int)(hend - b));
261
-
262
- text_ref(p->resp);
263
269
  res_set_message(res, p->resp);
264
270
 
265
271
  return -mlen;
@@ -267,10 +273,9 @@ con_header_read(Con c) {
267
273
  }
268
274
  HOOKED:
269
275
  // Create request and populate.
270
- if (NULL == (c->req = (Req)malloc(mlen + sizeof(struct _Req) - 8 + 1))) {
276
+ if (NULL == (c->req = request_create(mlen))) {
271
277
  return bad_request(c, 413, __LINE__);
272
278
  }
273
- DEBUG_ALLOC(mem_req)
274
279
  if ((long)c->bcnt <= mlen) {
275
280
  memcpy(c->req->msg, c->buf, c->bcnt);
276
281
  if ((long)c->bcnt < mlen) {
@@ -280,13 +285,13 @@ HOOKED:
280
285
  memcpy(c->req->msg, c->buf, mlen);
281
286
  }
282
287
  c->req->msg[mlen] = '\0';
283
- c->req->server = server;
284
288
  c->req->method = method;
289
+ c->req->upgrade = UP_NONE;
290
+ c->req->cid = c->id;
285
291
  c->req->path.start = c->req->msg + (path - c->buf);
286
292
  c->req->path.len = (int)(pend - path);
287
293
  c->req->query.start = c->req->msg + (query - c->buf);
288
294
  c->req->query.len = (int)(qend - query);
289
- c->req->mlen = mlen;
290
295
  c->req->body.start = c->req->msg + (hend - c->buf + 4);
291
296
  c->req->body.len = (unsigned int)clen;
292
297
  b = strstr(b, "\r\n");
@@ -303,9 +308,36 @@ HOOKED:
303
308
  return mlen;
304
309
  }
305
310
 
306
- // return true to remove/close connection
311
+ static void
312
+ check_upgrade(Con c) {
313
+ const char *v;
314
+ int vlen = 0;
315
+
316
+ if (NULL == c->req) {
317
+ return;
318
+ }
319
+ if (NULL != (v = con_header_value(c->req->header.start, c->req->header.len, "Connection", &vlen))) {
320
+ if (NULL != strstr(v, "Upgrade")) {
321
+ if (NULL != (v = con_header_value(c->req->header.start, c->req->header.len, "Upgrade", &vlen))) {
322
+ if (0 == strncasecmp("WebSocket", v, vlen)) {
323
+ c->res_tail->close = false;
324
+ c->res_tail->con_kind = CON_WS;
325
+ return;
326
+ }
327
+ }
328
+ }
329
+ }
330
+ if (NULL != (v = con_header_value(c->req->header.start, c->req->header.len, "Accept", &vlen))) {
331
+ if (0 == strncasecmp("text/event-stream", v, vlen)) {
332
+ c->res_tail->close = false;
333
+ c->res_tail->con_kind = CON_SSE;
334
+ return;
335
+ }
336
+ }
337
+ }
338
+
307
339
  static bool
308
- con_read(Con c) {
340
+ con_http_read(Con c) {
309
341
  ssize_t cnt;
310
342
 
311
343
  if (NULL != c->req) {
@@ -318,9 +350,9 @@ con_read(Con c) {
318
350
  // If nothing read then no need to complain. Just close.
319
351
  if (0 < c->bcnt) {
320
352
  if (0 == cnt) {
321
- log_cat(&c->server->warn_cat, "Nothing to read. Client closed socket on connection %llu.", c->iid);
353
+ log_cat(&warn_cat, "Nothing to read. Client closed socket on connection %llu.", c->id);
322
354
  } else {
323
- log_cat(&c->server->warn_cat, "Failed to read request. %s.", strerror(errno));
355
+ log_cat(&warn_cat, "Failed to read request. %s.", strerror(errno));
324
356
  }
325
357
  }
326
358
  return true;
@@ -351,14 +383,16 @@ con_read(Con c) {
351
383
  }
352
384
  if (NULL != c->req) {
353
385
  if (c->req->mlen <= c->bcnt) {
386
+ Req req;
354
387
  Res res;
388
+ long mlen;
355
389
 
356
- if (c->server->debug_cat.on && NULL != c->req && NULL != c->req->body.start) {
357
- log_cat(&c->server->debug_cat, "request on %llu: %s", c->iid, c->req->body.start);
390
+ if (debug_cat.on && NULL != c->req && NULL != c->req->body.start) {
391
+ log_cat(&debug_cat, "request on %llu: %s", c->id, c->req->body.start);
358
392
  }
359
393
  if (NULL == (res = res_create())) {
360
394
  c->req = NULL;
361
- log_cat(&c->server->error_cat, "memory allocation of response failed on connection %llu.", c->iid);
395
+ log_cat(&error_cat, "memory allocation of response failed on connection %llu.", c->id);
362
396
  return bad_request(c, 500, __LINE__);
363
397
  } else {
364
398
  if (NULL == c->res_tail) {
@@ -370,17 +404,19 @@ con_read(Con c) {
370
404
  res->close = should_close(c->req->header.start, c->req->header.len);
371
405
  }
372
406
  c->req->res = res;
373
- queue_push(&c->server->eval_queue, (void*)c->req);
374
- if (c->req->mlen < c->bcnt) {
375
- memmove(c->buf, c->buf + c->req->mlen, c->bcnt - c->req->mlen);
376
- c->bcnt -= c->req->mlen;
407
+ mlen = c->req->mlen;
408
+ check_upgrade(c);
409
+ req = c->req;
410
+ c->req = NULL;
411
+ queue_push(&the_server.eval_queue, (void*)req);
412
+ if (mlen < (long)c->bcnt) {
413
+ memmove(c->buf, c->buf + mlen, c->bcnt - mlen);
414
+ c->bcnt -= mlen;
377
415
  } else {
378
416
  c->bcnt = 0;
379
417
  *c->buf = '\0';
380
- c->req = NULL;
381
418
  break;
382
419
  }
383
- c->req = NULL;
384
420
  continue;
385
421
  }
386
422
  }
@@ -389,33 +425,137 @@ con_read(Con c) {
389
425
  return false;
390
426
  }
391
427
 
428
+ static bool
429
+ con_ws_read(Con c) {
430
+ ssize_t cnt;
431
+ uint8_t *b;
432
+ uint8_t op;
433
+ long mlen;
434
+
435
+ if (NULL != c->req) {
436
+ cnt = recv(c->sock, c->req->msg + c->bcnt, c->req->mlen - c->bcnt, 0);
437
+ } else {
438
+ cnt = recv(c->sock, c->buf + c->bcnt, sizeof(c->buf) - c->bcnt - 1, 0);
439
+ }
440
+ c->timeout = dtime() + CON_TIMEOUT;
441
+ if (0 >= cnt) {
442
+ // If nothing read then no need to complain. Just close.
443
+ if (0 < c->bcnt) {
444
+ if (0 == cnt) {
445
+ log_cat(&warn_cat, "Nothing to read. Client closed socket on connection %llu.", c->id);
446
+ } else {
447
+ log_cat(&warn_cat, "Failed to read WebSocket message. %s.", strerror(errno));
448
+ }
449
+ }
450
+ return true;
451
+ }
452
+ c->bcnt += cnt;
453
+ while (true) {
454
+ if (NULL == c->req) {
455
+ if (c->bcnt < 2) {
456
+ return false; // Try again.
457
+ }
458
+ b = (uint8_t*)c->buf;
459
+ if (0 >= (mlen = ws_calc_len(c, b, c->bcnt))) {
460
+ return (mlen < 0);
461
+ }
462
+ op = 0x0F & *b;
463
+ switch (op) {
464
+ case WS_OP_TEXT:
465
+ case WS_OP_BIN:
466
+ if (ws_create_req(c, mlen)) {
467
+ return true;
468
+ }
469
+ break;
470
+ case WS_OP_CLOSE:
471
+ return true;
472
+ case WS_OP_PING:
473
+ ws_pong(c);
474
+ break;
475
+ case WS_OP_PONG:
476
+ // ignore
477
+ break;
478
+ case WS_OP_CONT:
479
+ default:
480
+ log_cat(&error_cat, "WebSocket op 0x%02x not supported on %llu.", op, c->id);
481
+ return true;
482
+ }
483
+ }
484
+ if (NULL != c->req) {
485
+ mlen = c->req->mlen;
486
+ c->req->mlen = ws_decode(c->req->msg, c->req->mlen);
487
+ if (mlen <= (long)c->bcnt) {
488
+ if (debug_cat.on) {
489
+ if (ON_MSG == c->req->method) {
490
+ log_cat(&debug_cat, "WebSocket message on %llu: %s", c->id, c->req->msg);
491
+ } else {
492
+ log_cat(&debug_cat, "WebSocket binary message on %llu", c->id);
493
+ }
494
+ }
495
+ }
496
+ queue_push(&the_server.eval_queue, (void*)c->req);
497
+ if (mlen < (long)c->bcnt) {
498
+ memmove(c->buf, c->buf + mlen, c->bcnt - mlen);
499
+ c->bcnt -= mlen;
500
+ } else {
501
+ c->bcnt = 0;
502
+ *c->buf = '\0';
503
+ c->req = NULL;
504
+ break;
505
+ }
506
+ c->req = NULL;
507
+ continue;
508
+ }
509
+ break;
510
+ }
511
+ return false;
512
+ }
513
+
392
514
  // return true to remove/close connection
393
515
  static bool
394
- con_write(Con c) {
516
+ con_read(Con c) {
517
+ switch (c->kind) {
518
+ case CON_HTTP:
519
+ return con_http_read(c);
520
+ case CON_WS:
521
+ return con_ws_read(c);
522
+ default:
523
+ break;
524
+ }
525
+ return true;
526
+ }
527
+
528
+ // return true to remove/close connection
529
+ static bool
530
+ con_http_write(Con c) {
395
531
  Text message = res_message(c->res_head);
396
532
  ssize_t cnt;
397
533
 
398
534
  c->timeout = dtime() + CON_TIMEOUT;
399
535
  if (0 == c->wcnt) {
400
- if (c->server->resp_cat.on) {
536
+ if (resp_cat.on) {
401
537
  char buf[4096];
402
538
  char *hend = strstr(message->text, "\r\n\r\n");
403
539
 
404
540
  if (NULL == hend) {
405
541
  hend = message->text + message->len;
406
542
  }
543
+ if ((long)sizeof(buf) <= hend - message->text) {
544
+ hend = message->text + sizeof(buf) - 1;
545
+ }
407
546
  memcpy(buf, message->text, hend - message->text);
408
- log_cat(&c->server->resp_cat, "%llu: %s", c->iid, buf);
547
+ buf[hend - message->text] = '\0';
548
+ log_cat(&resp_cat, "%llu: %s", c->id, buf);
409
549
  }
410
- if (c->server->debug_cat.on) {
411
- log_cat(&c->server->debug_cat, "response on %llu: %s", c->iid, message->text);
550
+ if (debug_cat.on) {
551
+ log_cat(&debug_cat, "response on %llu: %s", c->id, message->text);
412
552
  }
413
553
  }
414
554
  if (0 > (cnt = send(c->sock, message->text + c->wcnt, message->len - c->wcnt, 0))) {
415
555
  if (EAGAIN == errno) {
416
556
  return false;
417
557
  }
418
- log_cat(&c->server->error_cat, "Socket error @ %llu.", c->iid);
558
+ log_cat(&error_cat, "Socket error @ %llu.", c->id);
419
559
 
420
560
  return true;
421
561
  }
@@ -436,12 +576,313 @@ con_write(Con c) {
436
576
  return false;
437
577
  }
438
578
 
579
+ static const char ping_msg[] = "\x89\x00";
580
+ static const char pong_msg[] = "\x8a\x00";
581
+
582
+ static bool
583
+ con_ws_write(Con c) {
584
+ Res res = c->res_head;
585
+ Text message = res_message(res);
586
+ ssize_t cnt;
587
+
588
+ if (NULL == message) {
589
+ if (res->ping) {
590
+ if (0 > (cnt = send(c->sock, ping_msg, sizeof(ping_msg) - 1, 0))) {
591
+ if (EAGAIN == errno) {
592
+ return false;
593
+ }
594
+ log_cat(&error_cat, "Socket error @ %llu.", c->id);
595
+ ws_req_close(c);
596
+ res_destroy(res);
597
+
598
+ return true;
599
+ }
600
+ } else if (res->pong) {
601
+ if (0 > (cnt = send(c->sock, pong_msg, sizeof(pong_msg) - 1, 0))) {
602
+ if (EAGAIN == errno) {
603
+ return false;
604
+ }
605
+ log_cat(&error_cat, "Socket error @ %llu.", c->id);
606
+ ws_req_close(c);
607
+ res_destroy(res);
608
+
609
+ return true;
610
+ }
611
+ } else {
612
+ ws_req_close(c);
613
+ res_destroy(res);
614
+ return true;
615
+ }
616
+ c->res_head = res->next;
617
+ if (res == c->res_tail) {
618
+ c->res_tail = NULL;
619
+ }
620
+ return false;
621
+ }
622
+ c->timeout = dtime() + CON_TIMEOUT;
623
+ if (0 == c->wcnt) {
624
+ Text t;
625
+
626
+ if (push_cat.on) {
627
+ if (message->bin) {
628
+ log_cat(&push_cat, "%llu binary", c->id);
629
+ } else {
630
+ log_cat(&push_cat, "%llu: %s", c->id, message->text);
631
+ }
632
+ }
633
+ t = ws_expand(message);
634
+ if (t != message) {
635
+ res_set_message(res, t);
636
+ message = t;
637
+ }
638
+ }
639
+ if (0 > (cnt = send(c->sock, message->text + c->wcnt, message->len - c->wcnt, 0))) {
640
+ if (EAGAIN == errno) {
641
+ return false;
642
+ }
643
+ log_cat(&error_cat, "Socket error @ %llu.", c->id);
644
+ ws_req_close(c);
645
+
646
+ return true;
647
+ }
648
+ c->wcnt += cnt;
649
+ if (c->wcnt == message->len) { // finished
650
+ Res res = c->res_head;
651
+ bool done = res->close;
652
+ int pending;
653
+
654
+ c->res_head = res->next;
655
+ if (res == c->res_tail) {
656
+ c->res_tail = NULL;
657
+ }
658
+ c->wcnt = 0;
659
+ res_destroy(res);
660
+ if (1 == (pending = atomic_fetch_sub(&c->slot->pending, 1))) {
661
+ if (NULL != c->slot && Qnil != c->slot->handler && c->slot->on_empty) {
662
+ Req req = request_create(0);
663
+
664
+ req->cid = c->id;
665
+ req->method = ON_EMPTY;
666
+ req->handler_type = PUSH_HOOK;
667
+ req->handler = c->slot->handler;
668
+ queue_push(&the_server.eval_queue, (void*)req);
669
+ }
670
+ }
671
+ return done;
672
+ }
673
+ return false;
674
+ }
675
+
676
+ static bool
677
+ con_sse_write(Con c) {
678
+ Res res = c->res_head;
679
+ Text message = res_message(res);
680
+ ssize_t cnt;
681
+
682
+ if (NULL == message) {
683
+ ws_req_close(c);
684
+ res_destroy(res);
685
+ return true;
686
+ }
687
+ c->timeout = dtime() + CON_TIMEOUT *2;
688
+ if (0 == c->wcnt) {
689
+ Text t;
690
+
691
+ if (push_cat.on) {
692
+ log_cat(&push_cat, "%llu: %s", c->id, message->text);
693
+ }
694
+ t = sse_expand(message);
695
+ if (t != message) {
696
+ res_set_message(res, t);
697
+ message = t;
698
+ }
699
+ }
700
+ if (0 > (cnt = send(c->sock, message->text + c->wcnt, message->len - c->wcnt, 0))) {
701
+ if (EAGAIN == errno) {
702
+ return false;
703
+ }
704
+ log_cat(&error_cat, "Socket error @ %llu.", c->id);
705
+ ws_req_close(c);
706
+
707
+ return true;
708
+ }
709
+ c->wcnt += cnt;
710
+ if (c->wcnt == message->len) { // finished
711
+ Res res = c->res_head;
712
+ bool done = res->close;
713
+ int pending;
714
+
715
+ c->res_head = res->next;
716
+ if (res == c->res_tail) {
717
+ c->res_tail = NULL;
718
+ }
719
+ c->wcnt = 0;
720
+ res_destroy(res);
721
+ if (1 == (pending = atomic_fetch_sub(&c->slot->pending, 1))) {
722
+ if (NULL != c->slot && Qnil != c->slot->handler && c->slot->on_empty) {
723
+ Req req = request_create(0);
724
+
725
+ req->cid = c->id;
726
+ req->method = ON_EMPTY;
727
+ req->handler_type = PUSH_HOOK;
728
+ req->handler = c->slot->handler;
729
+ queue_push(&the_server.eval_queue, (void*)req);
730
+ }
731
+ }
732
+ return done;
733
+ }
734
+ return false;
735
+ }
736
+
737
+ static bool
738
+ con_write(Con c) {
739
+ bool remove = true;
740
+ ConKind kind = c->res_head->con_kind;
741
+
742
+ switch (c->kind) {
743
+ case CON_HTTP:
744
+ remove = con_http_write(c);
745
+ break;
746
+ case CON_WS:
747
+ remove = con_ws_write(c);
748
+ break;
749
+ case CON_SSE:
750
+ remove = con_sse_write(c);
751
+ break;
752
+ default:
753
+ break;
754
+ }
755
+ if (kind != c->kind) {
756
+ c->kind = kind;
757
+ if (CON_HTTP != kind && !remove) {
758
+ c->slot = cc_set_con(&the_server.con_cache, c);
759
+ }
760
+ }
761
+ return remove;
762
+ }
763
+
764
+ static void
765
+ process_pub_con(Pub pub) {
766
+ CSlot slot;
767
+
768
+ switch (pub->kind) {
769
+ case PUB_CLOSE:
770
+ if (NULL == (slot = cc_get_slot(&the_server.con_cache, pub->cid)) || NULL == slot->con) {
771
+ log_cat(&warn_cat, "Socket %llu already closed.", pub->cid);
772
+ pub_destroy(pub);
773
+ return;
774
+ } else {
775
+ Res res = res_create();;
776
+
777
+ if (NULL != res) {
778
+ if (NULL == slot->con->res_tail) {
779
+ slot->con->res_head = res;
780
+ } else {
781
+ slot->con->res_tail->next = res;
782
+ }
783
+ slot->con->res_tail = res;
784
+ res->con_kind = slot->con->kind;
785
+ res->close = true;
786
+ }
787
+ }
788
+ break;
789
+ case PUB_WRITE: {
790
+ if (NULL == (slot = cc_get_slot(&the_server.con_cache, pub->cid)) || NULL == slot->con) {
791
+ log_cat(&warn_cat, "Socket %llu already closed. WebSocket write failed.", pub->cid);
792
+ pub_destroy(pub);
793
+ return;
794
+ } else {
795
+ Res res = res_create();
796
+
797
+ if (NULL != res) {
798
+ if (NULL == slot->con->res_tail) {
799
+ slot->con->res_head = res;
800
+ } else {
801
+ slot->con->res_tail->next = res;
802
+ }
803
+ slot->con->res_tail = res;
804
+ res->con_kind = slot->con->kind;
805
+ res_set_message(res, pub->msg);
806
+ }
807
+ }
808
+ break;
809
+ case PUB_SUB:
810
+ // TBD handle a subscribe when implementing pub/sub
811
+ break;
812
+ case PUB_UN:
813
+ // TBD handle a subscribe when implementing pub/sub
814
+ break;
815
+ case PUB_MSG:
816
+ // TBD handle a subscribe when implementing pub/sub
817
+ break;
818
+ }
819
+ default:
820
+ break;
821
+ }
822
+ pub_destroy(pub);
823
+ }
824
+
825
+ static struct pollfd*
826
+ poll_setup(Con *ca, int ccnt, struct pollfd *pp) {
827
+ Con *cp;
828
+ Con *end = ca + MAX_SOCK;
829
+ Con c;
830
+ int i;
831
+
832
+ // The first two pollfd are for the con_queue and the pub_queue in that
833
+ // order.
834
+ pp->fd = queue_listen(&the_server.con_queue);
835
+ pp->events = POLLIN;
836
+ pp->revents = 0;
837
+ pp++;
838
+ pp->fd = queue_listen(&the_server.pub_queue);
839
+ pp->events = POLLIN;
840
+ pp->revents = 0;
841
+ pp++;
842
+ for (i = ccnt, cp = ca; 0 < i && cp < end; cp++) {
843
+ if (NULL == *cp) {
844
+ continue;
845
+ }
846
+ c = *cp;
847
+ c->pp = pp;
848
+ pp->fd = c->sock;
849
+ pp->events = 0;
850
+ switch (c->kind) {
851
+ case CON_HTTP:
852
+ if (NULL != c->res_head && NULL != res_message(c->res_head)) {
853
+ pp->events = POLLIN | POLLOUT;
854
+ } else {
855
+ pp->events = POLLIN;
856
+ }
857
+ break;
858
+ case CON_WS:
859
+ if (NULL != c->res_head && (c->res_head->close || c->res_head->ping || NULL != res_message(c->res_head))) {
860
+ pp->events = POLLIN | POLLOUT;
861
+ } else {
862
+ pp->events = POLLIN;
863
+ }
864
+ break;
865
+ case CON_SSE:
866
+ if (NULL != c->res_head && NULL != res_message(c->res_head)) {
867
+ pp->events = POLLOUT;
868
+ }
869
+ break;
870
+ default:
871
+ continue; // should never get here
872
+ break;
873
+ }
874
+ pp->revents = 0;
875
+ i--;
876
+ pp++;
877
+ }
878
+ return pp;
879
+ }
880
+
439
881
  void*
440
882
  con_loop(void *x) {
441
- Server server = (Server)x;
442
883
  Con c;
443
- Con ca[MAX_SOCK];
444
- struct pollfd pa[MAX_SOCK];
884
+ Con ca[MAX_SOCK + 2];
885
+ struct pollfd pa[MAX_SOCK + 2];
445
886
  struct pollfd *pp;
446
887
  Con *end = ca + MAX_SOCK;
447
888
  Con *cp;
@@ -449,57 +890,47 @@ con_loop(void *x) {
449
890
  int i;
450
891
  long mcnt = 0;
451
892
  double now;
893
+ Pub pub;
452
894
 
453
- atomic_fetch_add(&server->running, 1);
895
+ atomic_fetch_add(&the_server.running, 1);
454
896
  memset(ca, 0, sizeof(ca));
455
897
  memset(pa, 0, sizeof(pa));
456
- while (server->active) {
457
- while (NULL != (c = (Con)queue_pop(&server->con_queue, 0.0))) {
898
+ while (the_server.active) {
899
+ while (NULL != (c = (Con)queue_pop(&the_server.con_queue, 0.0))) {
458
900
  mcnt++;
459
901
  ca[c->sock] = c;
460
902
  ccnt++;
461
903
  }
462
- pp = pa;
463
- pp->fd = queue_listen(&server->con_queue);
464
- pp->events = POLLIN;
465
- pp->revents = 0;
466
- pp++;
467
- for (i = ccnt, cp = ca; 0 < i && cp < end; cp++) {
468
- if (NULL == *cp) {
469
- continue;
470
- }
471
- c = *cp;
472
- c->pp = pp;
473
- pp->fd = c->sock;
474
- if (NULL != c->res_head && NULL != res_message(c->res_head)) {
475
- pp->events = POLLIN | POLLOUT;
476
- } else {
477
- pp->events = POLLIN;
478
- }
479
- pp->revents = 0;
480
- i--;
481
- pp++;
904
+ while (NULL != (pub = (Pub)queue_pop(&the_server.pub_queue, 0.0))) {
905
+ process_pub_con(pub);
482
906
  }
907
+ pp = poll_setup(ca, ccnt, pa);
483
908
  if (0 > (i = poll(pa, (nfds_t)(pp - pa), 100))) {
484
909
  if (EAGAIN == errno) {
485
910
  continue;
486
911
  }
487
- log_cat(&server->error_cat, "Polling error. %s.", strerror(errno));
912
+ log_cat(&error_cat, "Polling error. %s.", strerror(errno));
488
913
  // Either a signal or something bad like out of memory. Might as well exit.
489
914
  break;
490
915
  }
491
916
  now = dtime();
492
917
 
493
- if (0 == i) { // nothing to read or write
494
- // TBD check for cons to close
495
- continue;
496
- }
497
- if (0 != (pa->revents & POLLIN)) {
498
- queue_release(&server->con_queue);
499
- while (NULL != (c = (Con)queue_pop(&server->con_queue, 0.0))) {
500
- mcnt++;
501
- ca[c->sock] = c;
502
- ccnt++;
918
+ if (0 < i) {
919
+ // Check con_queue if an event is waiting.
920
+ if (0 != (pa->revents & POLLIN)) {
921
+ queue_release(&the_server.con_queue);
922
+ while (NULL != (c = (Con)queue_pop(&the_server.con_queue, 0.0))) {
923
+ mcnt++;
924
+ ca[c->sock] = c;
925
+ ccnt++;
926
+ }
927
+ }
928
+ // Check pub_queue if an event is waiting.
929
+ if (0 != (pa[1].revents & POLLIN)) {
930
+ queue_release(&the_server.pub_queue);
931
+ while (NULL != (pub = (Pub)queue_pop(&the_server.pub_queue, 0.0))) {
932
+ process_pub_con(pub);
933
+ }
503
934
  }
504
935
  }
505
936
  for (i = ccnt, cp = ca; 0 < i && cp < end; cp++) {
@@ -522,9 +953,9 @@ con_loop(void *x) {
522
953
  if (0 != (pp->revents & (POLLERR | POLLHUP | POLLNVAL))) {
523
954
  if (0 < c->bcnt) {
524
955
  if (0 != (pp->revents & (POLLHUP | POLLNVAL))) {
525
- log_cat(&server->error_cat, "Socket %llu closed.", c->iid);
956
+ log_cat(&error_cat, "Socket %llu closed.", c->id);
526
957
  } else if (!c->closing) {
527
- log_cat(&server->error_cat, "Socket %llu error. %s", c->iid, strerror(errno));
958
+ log_cat(&error_cat, "Socket %llu error. %s", c->id, strerror(errno));
528
959
  }
529
960
  }
530
961
  goto CON_RM;
@@ -533,6 +964,10 @@ con_loop(void *x) {
533
964
  continue;
534
965
  } else if (c->closing) {
535
966
  goto CON_RM;
967
+ } else if (CON_WS == c->kind || CON_SSE == c->kind) {
968
+ c->timeout = dtime() + CON_TIMEOUT;
969
+ ws_ping(c);
970
+ continue;
536
971
  } else {
537
972
  c->closing = true;
538
973
  c->timeout = now + 0.5;
@@ -543,11 +978,16 @@ con_loop(void *x) {
543
978
  CON_RM:
544
979
  ca[c->sock] = NULL;
545
980
  ccnt--;
546
- log_cat(&server->con_cat, "Connection %llu closed.", c->iid);
981
+ log_cat(&con_cat, "Connection %llu closed.", c->id);
547
982
  con_destroy(c);
548
983
  }
549
984
  }
550
- atomic_fetch_sub(&server->running, 1);
985
+ for (i = ccnt, cp = ca; 0 < i && cp < end; cp++) {
986
+ if (NULL != *cp) {
987
+ con_destroy(*cp);
988
+ }
989
+ }
990
+ atomic_fetch_sub(&the_server.running, 1);
551
991
 
552
992
  return NULL;
553
993
  }