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,18 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_SUBSCRIPTION_H__
4
+ #define __AGOO_SUBSCRIPTION_H__
5
+
6
+ #include <ruby.h>
7
+
8
+ typedef struct _Subscription {
9
+ VALUE self;
10
+ uint64_t cid;
11
+ uint64_t id;
12
+ VALUE handler;
13
+ } *Subscription;
14
+
15
+ extern void subscription_init(VALUE mod);
16
+ extern VALUE subscription_new(uint64_t cid, uint64_t id, VALUE handler);
17
+
18
+ #endif // __AGOO_SUBSCRIPTION_H__
@@ -12,9 +12,10 @@ text_create(const char *str, int len) {
12
12
  Text t = (Text)malloc(sizeof(struct _Text) - TEXT_MIN_SIZE + len + 1);
13
13
 
14
14
  if (NULL != t) {
15
- DEBUG_ALLOC(mem_text)
15
+ DEBUG_ALLOC(mem_text, t)
16
16
  t->len = len;
17
17
  t->alen = len;
18
+ t->bin = false;
18
19
  atomic_init(&t->ref_cnt, 0);
19
20
  memcpy(t->text, str, len);
20
21
  t->text[len] = '\0';
@@ -27,9 +28,10 @@ text_allocate(int len) {
27
28
  Text t = (Text)malloc(sizeof(struct _Text) - TEXT_MIN_SIZE + len + 1);
28
29
 
29
30
  if (NULL != t) {
30
- DEBUG_ALLOC(mem_text)
31
+ DEBUG_ALLOC(mem_text, t)
31
32
  t->len = 0;
32
33
  t->alen = len;
34
+ t->bin = false;
33
35
  atomic_init(&t->ref_cnt, 0);
34
36
  *t->text = '\0';
35
37
  }
@@ -44,7 +46,7 @@ text_ref(Text t) {
44
46
  void
45
47
  text_release(Text t) {
46
48
  if (1 >= atomic_fetch_sub(&t->ref_cnt, 1)) {
47
- DEBUG_FREE(mem_text)
49
+ DEBUG_FREE(mem_text, t)
48
50
  free(t);
49
51
  }
50
52
  }
@@ -61,7 +63,6 @@ text_append(Text t, const char *s, int len) {
61
63
  if (NULL == (t = (Text)realloc(t, size))) {
62
64
  return NULL;
63
65
  }
64
- DEBUG_ALLOC(mem_text)
65
66
  t->alen = new_len;
66
67
  }
67
68
  memcpy(t->text + t->len, s, len);
@@ -70,3 +71,24 @@ text_append(Text t, const char *s, int len) {
70
71
 
71
72
  return t;
72
73
  }
74
+
75
+ Text
76
+ text_prepend(Text t, const char *s, int len) {
77
+ if (0 >= len) {
78
+ len = (int)strlen(s);
79
+ }
80
+ if (t->alen <= t->len + len) {
81
+ long new_len = t->alen + t->alen / 2;
82
+ size_t size = sizeof(struct _Text) - TEXT_MIN_SIZE + new_len + 1;
83
+
84
+ if (NULL == (t = (Text)realloc(t, size))) {
85
+ return NULL;
86
+ }
87
+ t->alen = new_len;
88
+ }
89
+ memmove(t->text + len, t->text, t->len + 1);
90
+ memcpy(t->text, s, len);
91
+ t->len += len;
92
+
93
+ return t;
94
+ }
@@ -12,6 +12,7 @@ typedef struct _Text {
12
12
  long len; // length of valid text
13
13
  long alen; // size of allocated text
14
14
  atomic_int ref_cnt;
15
+ bool bin;
15
16
  char text[TEXT_MIN_SIZE];
16
17
  } *Text;
17
18
 
@@ -20,5 +21,6 @@ extern Text text_allocate(int len);
20
21
  extern void text_ref(Text t);
21
22
  extern void text_release(Text t);
22
23
  extern Text text_append(Text t, const char *s, int len);
24
+ extern Text text_prepend(Text t, const char *s, int len);
23
25
 
24
26
  #endif /* __AGOO_TEXT_H__ */
@@ -11,8 +11,21 @@ typedef enum {
11
11
  OPTIONS = 'O',
12
12
  POST = 'P',
13
13
  PUT = 'U',
14
+ PATCH = 'T',
14
15
  ALL = 'A',
15
16
  NONE = '\0',
17
+
18
+ ON_MSG = 'M', // use for on_message callback
19
+ ON_BIN = 'B', // use for on_message callback with binary (ASCII8BIT)
20
+ ON_CLOSE = 'X', // use for on_close callback
21
+ ON_SHUTDOWN = 'S', // use for on_shotdown callback
22
+ ON_EMPTY = 'E', // use for on_drained callback
16
23
  } Method;
17
24
 
25
+ typedef enum {
26
+ CON_HTTP = 'H',
27
+ CON_WS = 'W',
28
+ CON_SSE = 'S',
29
+ } ConKind;
30
+
18
31
  #endif // __AGOO_TYPES_H__
@@ -0,0 +1,148 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdio.h>
4
+
5
+ #include <ruby/encoding.h>
6
+
7
+ #include "pub.h"
8
+ #include "server.h"
9
+ #include "upgraded.h"
10
+
11
+ static VALUE upgraded_mod = Qundef;
12
+
13
+ static ID cid_id = 0;
14
+ static ID sid_id = 0;
15
+ static ID on_open_id = 0;
16
+ static ID to_s_id = 0;
17
+
18
+ /* Document-method: write
19
+ *
20
+ * call-seq: write(msg)
21
+ *
22
+ * Writes a message to the WebSocket or SSe connection.
23
+ */
24
+ static VALUE
25
+ up_write(VALUE self, VALUE msg) {
26
+ uint64_t cid = RB_FIX2ULONG(rb_ivar_get(self, cid_id));
27
+ Pub p;
28
+
29
+ if (!the_server.active) {
30
+ rb_raise(rb_eIOError, "Server shutdown.");
31
+ }
32
+ if (0 < the_server.max_push_pending) {
33
+ int pending = cc_get_pending(&the_server.con_cache, cid);
34
+
35
+ if (the_server.max_push_pending <= pending) {
36
+ rb_raise(rb_eIOError, "Too many writes pending. Try again later.");
37
+ }
38
+ }
39
+ if (T_STRING == rb_type(msg)) {
40
+ if (RB_ENCODING_IS_ASCII8BIT(msg)) {
41
+ p = pub_write(cid, StringValuePtr(msg), RSTRING_LEN(msg), true);
42
+ } else {
43
+ p = pub_write(cid, StringValuePtr(msg), RSTRING_LEN(msg), false);
44
+ }
45
+ } else {
46
+ volatile VALUE rs = rb_funcall(msg, to_s_id, 0);
47
+
48
+ p = pub_write(cid, StringValuePtr(rs), RSTRING_LEN(rs), false);
49
+ }
50
+ cc_pending_inc(&the_server.con_cache, cid);
51
+ queue_push(&the_server.pub_queue, p);
52
+
53
+ return Qnil;
54
+ }
55
+
56
+ /* Document-method: subscribe
57
+ *
58
+ * call-seq: subscribe(subject)
59
+ *
60
+ * Subscribes to messages published on the specified subject. (not implemented
61
+ * yet)
62
+ */
63
+ static VALUE
64
+ up_subscribe(VALUE self, VALUE subject) {
65
+
66
+ // printf("*** subscribe called\n");
67
+
68
+ // increment _sid
69
+ // create subscription
70
+ // push pub onto server pub_queue
71
+ // TBD create subscription object and return it
72
+
73
+ return Qnil;
74
+ }
75
+
76
+ /* Document-method: close
77
+ *
78
+ * call-seq: close()
79
+ *
80
+ * Closes the connections associated with the handler.
81
+ */
82
+ static VALUE
83
+ up_close(VALUE self) {
84
+ uint64_t cid = RB_FIX2ULONG(rb_ivar_get(self, cid_id));
85
+
86
+ if (!the_server.active) {
87
+ rb_raise(rb_eIOError, "Server shutdown.");
88
+ }
89
+ queue_push(&the_server.pub_queue, pub_close(cid));
90
+
91
+ return Qnil;
92
+ }
93
+
94
+ /* Document-method: pending
95
+ *
96
+ * call-seq: pending()
97
+ *
98
+ * Returns the number of pending WebSocket or SSE writes. If the connection is
99
+ * closed then -1 is returned.
100
+ */
101
+ static VALUE
102
+ pending(VALUE self) {
103
+ uint64_t cid = RB_FIX2ULONG(rb_ivar_get(self, cid_id));
104
+
105
+ if (!the_server.active) {
106
+ rb_raise(rb_eIOError, "Server shutdown.");
107
+ }
108
+ return INT2NUM(cc_get_pending(&the_server.con_cache, cid));
109
+ }
110
+
111
+ void
112
+ upgraded_extend(uint64_t cid, VALUE obj) {
113
+ if (!the_server.active) {
114
+ rb_raise(rb_eIOError, "Server shutdown.");
115
+ }
116
+ rb_ivar_set(obj, cid_id, ULONG2NUM(cid));
117
+ rb_ivar_set(obj, sid_id, ULONG2NUM(0)); // used as counter for subscription id
118
+ rb_extend_object(obj, upgraded_mod);
119
+ if (rb_respond_to(obj, on_open_id)) {
120
+ rb_funcall(obj, on_open_id, 0);
121
+ }
122
+ cc_set_handler(&the_server.con_cache, cid, obj,
123
+ rb_respond_to(obj, rb_intern("on_drained")),
124
+ rb_respond_to(obj, rb_intern("on_close")),
125
+ rb_respond_to(obj, rb_intern("on_shutdown")),
126
+ rb_respond_to(obj, rb_intern("on_message")));
127
+ rb_funcall(obj, rb_intern("to_s"), 0);
128
+ }
129
+
130
+ /* Document-module: Agoo::Upgraded
131
+ *
132
+ * Adds methods to a handler of WebSocket and SSe connections.
133
+ */
134
+ void
135
+ upgraded_init(VALUE mod) {
136
+ upgraded_mod = rb_define_module_under(mod, "Upgraded");
137
+
138
+ rb_define_method(upgraded_mod, "write", up_write, 1);
139
+ rb_define_method(upgraded_mod, "subscribe", up_subscribe, 1);
140
+ rb_define_method(upgraded_mod, "close", up_close, 0);
141
+ rb_define_method(upgraded_mod, "pending", pending, 0);
142
+
143
+ cid_id = rb_intern("_cid");
144
+ sid_id = rb_intern("_sid");
145
+ on_open_id = rb_intern("on_open");
146
+ to_s_id = rb_intern("to_s");
147
+
148
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_UPGRADED_H__
4
+ #define __AGOO_UPGRADED_H__
5
+
6
+ #include <stdint.h>
7
+
8
+ #include <ruby.h>
9
+
10
+ extern void upgraded_init(VALUE mod);
11
+ extern void upgraded_extend(uint64_t cid, VALUE obj);
12
+
13
+ #endif // __AGOO_UPGRADED_H__
@@ -0,0 +1,248 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdint.h>
4
+
5
+ #include "base64.h"
6
+ #include "con.h"
7
+ #include "debug.h"
8
+ #include "request.h"
9
+ #include "sha1.h"
10
+ #include "text.h"
11
+ #include "websocket.h"
12
+
13
+ #define MAX_KEY_LEN 1024
14
+
15
+ static const char up_con[] = "Upgrade: websocket\r\nConnection: Upgrade\r\n";
16
+ static const char ws_magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
17
+ static const char ws_protocol[] = "Sec-WebSocket-Protocol: ";
18
+ static const char ws_accept[] = "Sec-WebSocket-Accept: ";
19
+
20
+ //static const uint8_t close_msg[] = "\x88\x02\x03\xE8";
21
+
22
+ Text
23
+ ws_add_headers(Req req, Text t) {
24
+ int klen = 0;
25
+ const char *key;
26
+
27
+ t = text_append(t, up_con, sizeof(up_con) - 1);
28
+ if (NULL != (key = con_header_value(req->header.start, req->header.len, "Sec-WebSocket-Key", &klen)) &&
29
+ klen + sizeof(ws_magic) < MAX_KEY_LEN) {
30
+ char buf[MAX_KEY_LEN];
31
+ unsigned char sha[32];
32
+ int len = klen + sizeof(ws_magic) - 1;
33
+
34
+ strncpy(buf, key, klen);
35
+ strcpy(buf + klen, ws_magic);
36
+ sha1((unsigned char*)buf, len, sha);
37
+ len = b64_to(sha, SHA1_DIGEST_SIZE, buf);
38
+
39
+ t = text_append(t, ws_accept, sizeof(ws_accept) - 1);
40
+ t = text_append(t, buf, len);
41
+ t = text_append(t, "\r\n", 2);
42
+ }
43
+ if (NULL != (key = con_header_value(req->header.start, req->header.len, "Sec-WebSocket-Protocol", &klen))) {
44
+ t = text_append(t, ws_protocol, sizeof(ws_protocol) - 1);
45
+ t = text_append(t, key, klen);
46
+ t = text_append(t, "\r\n", 2);
47
+ }
48
+ return t;
49
+ }
50
+
51
+ Text
52
+ ws_expand(Text t) {
53
+ uint8_t buf[16];
54
+ uint8_t *b = buf;
55
+ uint8_t opcode = t->bin ? WS_OP_BIN : WS_OP_TEXT;
56
+
57
+ *b++ = 0x80 | (uint8_t)opcode;
58
+ // send unmasked
59
+ if (125 >= t->len) {
60
+ *b++ = (uint8_t)t->len;
61
+ } else if (0xFFFF >= t->len) {
62
+ *b++ = (uint8_t)0x7E;
63
+ *b++ = (uint8_t)((t->len >> 8) & 0xFF);
64
+ *b++ = (uint8_t)(t->len & 0xFF);
65
+ } else {
66
+ int i;
67
+
68
+ *b++ = (uint8_t)0x7F;
69
+ for (i = 56; 0 <= i; i -= 8) {
70
+ *b++ = (uint8_t)((t->len >> i) & 0xFF);
71
+ }
72
+ }
73
+ return text_prepend(t, (const char*)buf, (int)(b - buf));
74
+ }
75
+
76
+ size_t
77
+ ws_decode(char *buf, size_t mlen) {
78
+ uint8_t *b = (uint8_t*)buf;
79
+ bool is_masked;
80
+ uint8_t *payload;
81
+ uint64_t plen;
82
+
83
+ b++; // op
84
+ is_masked = 0x80 & *b;
85
+ plen = 0x7F & *b;
86
+ b++;
87
+ if (126 == plen) {
88
+ plen = *b++;
89
+ plen = (plen << 8) | *b++;
90
+ } else if (127 == plen) {
91
+ plen = *b++;
92
+ plen = (plen << 8) | *b++;
93
+ plen = (plen << 8) | *b++;
94
+ plen = (plen << 8) | *b++;
95
+ plen = (plen << 8) | *b++;
96
+ plen = (plen << 8) | *b++;
97
+ plen = (plen << 8) | *b++;
98
+ plen = (plen << 8) | *b++;
99
+ }
100
+ if (is_masked) {
101
+ uint8_t mask[4];
102
+ uint64_t i;
103
+
104
+ for (i = 0; i < 4; i++, b++) {
105
+ mask[i] = *b;
106
+ }
107
+ payload = b;
108
+ for (i = 0; i < plen; i++, b++) {
109
+ *b = *b ^ mask[i % 4];
110
+ }
111
+ } else {
112
+ payload = b;
113
+ b += plen;
114
+ }
115
+ memmove(buf, payload, plen);
116
+ buf[plen] = '\0';
117
+
118
+ return plen;
119
+ }
120
+
121
+ // if -1 then err, 0 not ready yet, positive is completed length
122
+ long
123
+ ws_calc_len(Con c, uint8_t *buf, size_t cnt) {
124
+ uint8_t *b = buf;
125
+ bool is_masked;
126
+ uint64_t plen;
127
+
128
+ if (0 == (0x80 & *b)) {
129
+ log_cat(&error_cat, "FIN must be 1. Websocket continuation not implemented on connection %llu.", c->id);
130
+ return -1;
131
+ }
132
+ b++;
133
+ is_masked = 0x80 & *b;
134
+ plen = 0x7F & *b;
135
+ if (cnt < 3) {
136
+ return 0; // not read yet
137
+ }
138
+ b++;
139
+ if (126 == plen) {
140
+ if (cnt < 5) {
141
+ return 0; // not read yet
142
+ }
143
+ plen = *b++;
144
+ plen = (plen << 8) | *b++;
145
+ } else if (127 == plen) {
146
+ if (c->bcnt < 11) {
147
+ return 0; // not read yet
148
+ }
149
+ plen = *b++;
150
+ plen = (plen << 8) | *b++;
151
+ plen = (plen << 8) | *b++;
152
+ plen = (plen << 8) | *b++;
153
+ plen = (plen << 8) | *b++;
154
+ plen = (plen << 8) | *b++;
155
+ plen = (plen << 8) | *b++;
156
+ plen = (plen << 8) | *b++;
157
+ }
158
+ return (long)(b - buf) + (is_masked ? 4 : 0) + plen;
159
+ }
160
+
161
+ // Return true on error otherwise false.
162
+ bool
163
+ ws_create_req(Con c, long mlen) {
164
+ uint8_t op = 0x0F & *c->buf;
165
+
166
+ if (NULL == (c->req = request_create(mlen))) {
167
+ log_cat(&error_cat, "Out of memory attempting to allocate request.");
168
+ return true;
169
+ }
170
+ if (NULL == c->slot || Qnil == c->slot->handler) {
171
+ return true;
172
+ }
173
+ memset(c->req, 0, sizeof(struct _Req));
174
+ if ((long)c->bcnt <= mlen) {
175
+ memcpy(c->req->msg, c->buf, c->bcnt);
176
+ if ((long)c->bcnt < mlen) {
177
+ memset(c->req->msg + c->bcnt, 0, mlen - c->bcnt);
178
+ }
179
+ } else {
180
+ memcpy(c->req->msg, c->buf, mlen);
181
+ }
182
+ c->req->msg[mlen] = '\0';
183
+ c->req->mlen = mlen;
184
+ c->req->method = (WS_OP_BIN == op) ? ON_BIN : ON_MSG;
185
+ c->req->upgrade = UP_NONE;
186
+ c->req->cid = c->id;
187
+ c->req->res = NULL;
188
+ c->req->handler_type = PUSH_HOOK;
189
+ if (c->slot->on_msg) {
190
+ c->req->handler = c->slot->handler;
191
+ } else {
192
+ c->req->handler = Qnil;
193
+ }
194
+ return false;
195
+ }
196
+
197
+ void
198
+ ws_req_close(Con c) {
199
+ if (NULL != c->slot && Qnil != c->slot->handler && c->slot->on_close) {
200
+ Req req = request_create(0);
201
+
202
+ req->cid = c->id;
203
+ req->method = ON_CLOSE;
204
+ req->handler_type = PUSH_HOOK;
205
+ req->handler = c->slot->handler;
206
+ queue_push(&the_server.eval_queue, (void*)req);
207
+ }
208
+ }
209
+
210
+ void
211
+ ws_ping(Con c) {
212
+ Res res;
213
+
214
+ if (NULL == (res = res_create())) {
215
+ log_cat(&error_cat, "Memory allocation of response failed on connection %llu.", c->id);
216
+ } else {
217
+ DEBUG_ALLOC(mem_res, res)
218
+ if (NULL == c->res_tail) {
219
+ c->res_head = res;
220
+ } else {
221
+ c->res_tail->next = res;
222
+ }
223
+ c->res_tail = res;
224
+ res->close = false;
225
+ res->con_kind = CON_WS;
226
+ res->ping = true;
227
+ }
228
+ }
229
+
230
+ void
231
+ ws_pong(Con c) {
232
+ Res res;
233
+
234
+ if (NULL == (res = res_create())) {
235
+ log_cat(&error_cat, "Memory allocation of response failed on connection %llu.", c->id);
236
+ } else {
237
+ DEBUG_ALLOC(mem_res, res)
238
+ if (NULL == c->res_tail) {
239
+ c->res_head = res;
240
+ } else {
241
+ c->res_tail->next = res;
242
+ }
243
+ c->res_tail = res;
244
+ res->close = false;
245
+ res->con_kind = CON_WS;
246
+ res->pong = true;
247
+ }
248
+ }