agoo 2.5.4 → 2.5.5

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