agoo 0.9.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.

@@ -0,0 +1,36 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_REQUEST_H__
4
+ #define __AGOO_REQUEST_H__
5
+
6
+ #include <ruby.h>
7
+
8
+ #include "hook.h"
9
+ #include "res.h"
10
+ #include "server.h"
11
+ #include "types.h"
12
+
13
+ typedef struct _Str {
14
+ char *start;
15
+ unsigned int len;
16
+ } *Str;
17
+
18
+ typedef struct _Req {
19
+ Server server;
20
+ Method method;
21
+ struct _Str path;
22
+ struct _Str query;
23
+ struct _Str header;
24
+ struct _Str body;
25
+ VALUE handler;
26
+ HookType handler_type;
27
+ Res res;
28
+ size_t mlen; // allocated msg length
29
+ char msg[8]; // expanded to be full message
30
+ } *Req;
31
+
32
+ extern void request_init(VALUE mod);
33
+ extern VALUE request_wrap(Req req);
34
+ extern VALUE request_env(Req req);
35
+
36
+ #endif // __AGOO_REQUEST_H__
data/ext/agoo/res.c ADDED
@@ -0,0 +1,38 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdlib.h>
4
+
5
+ #include "res.h"
6
+
7
+ Res
8
+ res_create() {
9
+ Res res = (Res)malloc(sizeof(struct _Res));
10
+
11
+ if (NULL != res) {
12
+ res->next = NULL;
13
+ atomic_init(&res->message, NULL);
14
+ res->close = false;
15
+ }
16
+ return res;
17
+ }
18
+
19
+ void
20
+ res_destroy(Res res) {
21
+ if (NULL != res) {
22
+ Text message = res_message(res);
23
+
24
+ if (NULL != message) {
25
+ text_release(message);
26
+ }
27
+ free(res);
28
+ }
29
+ }
30
+
31
+ void
32
+ res_set_message(Res res, Text t) {
33
+ if (NULL != t) {
34
+ text_ref(t);
35
+ }
36
+ atomic_store(&res->message, t);
37
+ }
38
+
data/ext/agoo/res.h ADDED
@@ -0,0 +1,28 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_RES_H__
4
+ #define __AGOO_RES_H__
5
+
6
+ #include <stdatomic.h>
7
+ #include <stdbool.h>
8
+
9
+ #include <ruby.h>
10
+
11
+ #include "text.h"
12
+
13
+ typedef struct _Res {
14
+ struct _Res *next;
15
+ _Atomic(Text) message;
16
+ bool close;
17
+ } *Res;
18
+
19
+ extern Res res_create();
20
+ extern void res_destroy(Res res);
21
+ extern void res_set_message(Res res, Text t);
22
+
23
+ static inline Text
24
+ res_message(Res res) {
25
+ return atomic_load(&res->message);
26
+ }
27
+
28
+ #endif // __AGOO_RES_H__
@@ -0,0 +1,271 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdlib.h>
4
+
5
+ #include "http.h"
6
+ #include "response.h"
7
+ #include "text.h"
8
+
9
+ static VALUE res_class = Qundef;
10
+
11
+ static int
12
+ response_len(Response res) {
13
+ char c;
14
+ const char *msg = http_code_message(res->code);
15
+ int len = snprintf(&c, 1, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n", res->code, msg, res->blen);
16
+ Header h;
17
+
18
+ for (h = res->headers; NULL != h; h = h->next) {
19
+ len += h->len;
20
+ }
21
+ len += 2; // for additional \r\n before body
22
+ len += res->blen;
23
+
24
+ return len;
25
+ }
26
+
27
+ static void
28
+ response_fill(Response res, char *buf) {
29
+ Header h;
30
+ const char *msg = http_code_message(res->code);
31
+
32
+ buf += sprintf(buf, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n", res->code, msg, res->blen);
33
+
34
+ for (h = res->headers; NULL != h; h = h->next) {
35
+ strncpy(buf, h->text, h->len);
36
+ buf += h->len;
37
+ }
38
+ *buf++ = '\r';
39
+ *buf++ = '\n';
40
+ if (NULL != res->body) {
41
+ memcpy(buf, res->body, res->blen);
42
+ }
43
+ }
44
+
45
+ static void
46
+ response_free(void *ptr) {
47
+ Response res = (Response)ptr;
48
+ Header h;
49
+
50
+ while (NULL != (h = res->headers)) {
51
+ res->headers = h->next;
52
+ xfree(h);
53
+ }
54
+ free(res->body); // allocated with strdup
55
+ xfree(ptr);
56
+ }
57
+
58
+ VALUE
59
+ response_new(Server server ) {
60
+ Response res = ALLOC(struct _Response);
61
+
62
+ memset(res, 0, sizeof(struct _Response));
63
+ res->code = 200;
64
+ res->server = server;
65
+
66
+ return Data_Wrap_Struct(res_class, NULL, response_free, res);
67
+ }
68
+
69
+ /* Document-method: to_s
70
+ *
71
+ * call-seq: to_s()
72
+ *
73
+ * Returns a string representation of the response.
74
+ */
75
+ static VALUE
76
+ to_s(VALUE self) {
77
+ Response res = (Response)DATA_PTR(self);
78
+ int len = response_len(res);
79
+ char *s = ALLOC_N(char, len + 1);
80
+
81
+ response_fill(res, s);
82
+
83
+ return rb_str_new(s, len);
84
+ }
85
+
86
+ /* Document-method: content
87
+ *
88
+ * call-seq: content()
89
+ *
90
+ * alias for _body_
91
+ */
92
+
93
+ /* Document-method: body
94
+ *
95
+ * call-seq: body()
96
+ *
97
+ * Gets the HTTP body for the response.
98
+ */
99
+ static VALUE
100
+ body_get(VALUE self) {
101
+ Response res = (Response)DATA_PTR(self);
102
+
103
+ if (NULL == res->body) {
104
+ return Qnil;
105
+ }
106
+ return rb_str_new(res->body, res->blen);
107
+ }
108
+
109
+ /* Document-method: content=
110
+ *
111
+ * call-seq: content=(str)
112
+ *
113
+ * alias for _body=_
114
+ */
115
+
116
+ /* Document-method: body=
117
+ *
118
+ * call-seq: body=(str)
119
+ *
120
+ * Sets the HTTP body for the response.
121
+ */
122
+ static VALUE
123
+ body_set(VALUE self, VALUE val) {
124
+ Response res = (Response)DATA_PTR(self);
125
+
126
+ if (T_STRING == rb_type(val)) {
127
+ res->body = strdup(StringValuePtr(val));
128
+ res->blen = RSTRING_LEN(val);
129
+ } else {
130
+ // TBD use Oj
131
+ }
132
+ return Qnil;
133
+ }
134
+
135
+ /* Document-method: code
136
+ *
137
+ * call-seq: code()
138
+ *
139
+ * Gets the HTTP status code for the response.
140
+ */
141
+ static VALUE
142
+ code_get(VALUE self) {
143
+ return INT2NUM(((Response)DATA_PTR(self))->code);
144
+ }
145
+
146
+ /* Document-method: code=
147
+ *
148
+ * call-seq: code=(value)
149
+ *
150
+ * Sets the HTTP status code for the response.
151
+ */
152
+ static VALUE
153
+ code_set(VALUE self, VALUE val) {
154
+ int code = NUM2INT(val);
155
+
156
+ if (100 <= code && code < 600) {
157
+ ((Response)DATA_PTR(self))->code = code;
158
+ } else {
159
+ rb_raise(rb_eArgError, "%d is not a valid HTTP status code.", code);
160
+ }
161
+ return Qnil;
162
+ }
163
+
164
+ /* Document-method: []
165
+ *
166
+ * call-seq: [](key)
167
+ *
168
+ * Gets a header element associated with the _key_.
169
+ */
170
+ static VALUE
171
+ head_get(VALUE self, VALUE key) {
172
+ Response res = (Response)DATA_PTR(self);
173
+ Header h;
174
+ const char *ks = StringValuePtr(key);
175
+ int klen = RSTRING_LEN(key);
176
+
177
+ for (h = res->headers; NULL != h; h = h->next) {
178
+ if (0 == strncasecmp(h->text, ks, klen) && klen + 1 < h->len && ':' == h->text[klen]) {
179
+ return rb_str_new(h->text + klen + 2, h->len - klen - 4);
180
+ }
181
+ }
182
+ return Qnil;
183
+ }
184
+
185
+ /* Document-method: []=
186
+ *
187
+ * call-seq: []=(key, value)
188
+ *
189
+ * Sets a header element with the _key_ and _value_ provided.
190
+ */
191
+ static VALUE
192
+ head_set(VALUE self, VALUE key, VALUE val) {
193
+ Response res = (Response)DATA_PTR(self);
194
+ Header h;
195
+ Header prev = NULL;
196
+ const char *ks = StringValuePtr(key);
197
+ const char *vs;
198
+ int klen = RSTRING_LEN(key);
199
+ int vlen;
200
+ int hlen;
201
+
202
+ for (h = res->headers; NULL != h; h = h->next) {
203
+ if (0 == strncasecmp(h->text, ks, klen) && klen + 1 < h->len && ':' == h->text[klen]) {
204
+ if (NULL == prev) {
205
+ res->headers = h->next;
206
+ } else {
207
+ prev->next = h->next;
208
+ }
209
+ xfree(h);
210
+ break;
211
+ }
212
+ prev = h;
213
+ }
214
+ if (T_STRING != rb_type(val)) {
215
+ val = rb_funcall(val, rb_intern("to_s"), 0);
216
+ }
217
+ vs = StringValuePtr(val);
218
+ vlen = RSTRING_LEN(val);
219
+
220
+ if (res->server->pedantic) {
221
+ http_header_ok(ks, klen, vs, vlen);
222
+ }
223
+ hlen = klen + vlen + 4;
224
+ h = (Header)ALLOC_N(char, sizeof(struct _Header) - 8 + hlen + 1);
225
+ h->next = NULL;
226
+ h->len = hlen;
227
+ strncpy(h->text, ks, klen);
228
+ strcpy(h->text + klen, ": ");
229
+ strncpy(h->text + klen + 2, vs, vlen);
230
+ strcpy(h->text + klen + 2 + vlen, "\r\n");
231
+ if (NULL == res->headers) {
232
+ res->headers = h;
233
+ } else {
234
+ for (prev = res->headers; NULL != prev->next; prev = prev->next) {
235
+ }
236
+ prev->next = h;
237
+ }
238
+ return Qnil;
239
+ }
240
+
241
+ Text
242
+ response_text(VALUE self) {
243
+ Response res = (Response)DATA_PTR(self);
244
+ int len = response_len(res);
245
+ Text t = text_allocate(len);
246
+
247
+ response_fill(res, t->text);
248
+ t->len = len;
249
+
250
+ return t;
251
+ }
252
+
253
+ /* Document-class: Agoo::Response
254
+ *
255
+ * A response passed to a handler that responds to the _on_request_
256
+ * method. The expected response is modified by the handler before returning.
257
+ */
258
+ void
259
+ response_init(VALUE mod) {
260
+ res_class = rb_define_class_under(mod, "Response", rb_cObject);
261
+
262
+ rb_define_method(res_class, "to_s", to_s, 0);
263
+ rb_define_method(res_class, "body", body_get, 0);
264
+ rb_define_method(res_class, "body=", body_set, 1);
265
+ rb_define_method(res_class, "content", body_get, 0);
266
+ rb_define_method(res_class, "content=", body_set, 1);
267
+ rb_define_method(res_class, "code", code_get, 0);
268
+ rb_define_method(res_class, "code=", code_set, 1);
269
+ rb_define_method(res_class, "[]", head_get, 1);
270
+ rb_define_method(res_class, "[]=", head_set, 2);
271
+ }
@@ -0,0 +1,33 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_RESPONSE_H__
4
+ #define __AGOO_RESPONSE_H__
5
+
6
+ #include <stdatomic.h>
7
+ #include <stdbool.h>
8
+
9
+ #include <ruby.h>
10
+
11
+ #include "server.h"
12
+ #include "text.h"
13
+
14
+ typedef struct _Header {
15
+ struct _Header *next;
16
+ int len;
17
+ char text[8];
18
+ } *Header;
19
+
20
+ typedef struct _Response {
21
+ int code;
22
+ Header headers;
23
+ int blen;
24
+ char *body;
25
+ Server server;
26
+ } *Response;
27
+
28
+ extern void response_init(VALUE mod);
29
+
30
+ extern VALUE response_new(Server server);
31
+ extern Text response_text(VALUE self);
32
+
33
+ #endif // __AGOO_RESPONSE_H__
data/ext/agoo/server.c ADDED
@@ -0,0 +1,891 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <errno.h>
4
+ #include <fcntl.h>
5
+ #include <limits.h>
6
+ #include <netdb.h>
7
+ #include <netinet/tcp.h>
8
+ #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
+ #include <sys/socket.h>
16
+ #include <sys/time.h>
17
+ #include <unistd.h>
18
+ #include <stdatomic.h>
19
+
20
+ #include <ruby.h>
21
+ #include <ruby/thread.h>
22
+
23
+ #include "con.h"
24
+ #include "dtime.h"
25
+ #include "err.h"
26
+ #include "http.h"
27
+ #include "response.h"
28
+ #include "request.h"
29
+ #include "server.h"
30
+
31
+ static VALUE server_class = Qundef;
32
+
33
+ static VALUE connect_sym;
34
+ static VALUE delete_sym;
35
+ static VALUE get_sym;
36
+ static VALUE head_sym;
37
+ static VALUE options_sym;
38
+ static VALUE post_sym;
39
+ static VALUE put_sym;
40
+
41
+ static ID call_id;
42
+ static ID each_id;
43
+ static ID on_request_id;
44
+ static ID to_i_id;
45
+
46
+ static Server the_server = NULL;
47
+
48
+ static void
49
+ shutdown_server(Server server) {
50
+ if (NULL != server && server->active) {
51
+ the_server = NULL;
52
+
53
+ server->active = false;
54
+ if (0 != server->listen_thread) {
55
+ pthread_join(server->listen_thread, NULL);
56
+ server->listen_thread = 0;
57
+ }
58
+ if (0 != server->con_thread) {
59
+ pthread_join(server->con_thread, NULL);
60
+ server->con_thread = 0;
61
+ }
62
+ queue_cleanup(&server->con_queue);
63
+ // The preferred method to of waiting for the ruby threads would be
64
+ // either a join or even a kill but since we don't have the gvi here
65
+ // that would cause a segfault. Instead we set a timeout and wait for
66
+ // the running counter to drop to zero.
67
+ if (NULL != server->eval_threads) {
68
+ double timeout = dtime() + 2.0;
69
+
70
+ while (dtime() < timeout) {
71
+ if (0 >= atomic_load(&server->running)) {
72
+ break;
73
+ }
74
+ dsleep(0.02);
75
+ }
76
+ xfree(server->eval_threads);
77
+ server->eval_threads = NULL;
78
+ }
79
+ while (NULL != server->hooks) {
80
+ Hook h = server->hooks;
81
+
82
+ server->hooks = h->next;
83
+ hook_destroy(h);
84
+ }
85
+ queue_cleanup(&server->eval_queue);
86
+ log_close(&server->log);
87
+ }
88
+ }
89
+
90
+ static void
91
+ sig_handler(int sig) {
92
+ if (NULL != the_server) {
93
+ shutdown_server(the_server);
94
+ }
95
+ // Use exit instead of rb_exit as rb_exit segfaults most of the time.
96
+ //rb_exit(0);
97
+ exit(0);
98
+ }
99
+
100
+ static void
101
+ server_free(void *ptr) {
102
+ shutdown_server((Server)ptr);
103
+ // Commented out for now as it causes a segfault later. Some thread seems
104
+ //to be pointing at it even though they have exited so live with a memory
105
+ //leak that only shows up when the program exits.
106
+ //xfree(ptr);
107
+ the_server = NULL;
108
+ }
109
+
110
+ static int
111
+ configure(Err err, Server s, int port, const char *root, VALUE options) {
112
+ s->port = port;
113
+ s->root = strdup(root);
114
+ s->thread_cnt = 1;
115
+ s->running = 0;
116
+ s->listen_thread = 0;
117
+ s->con_thread = 0;
118
+ s->log.cats = NULL;
119
+ log_cat_reg(&s->log, &s->error_cat, "ERROR", ERROR, RED, true);
120
+ log_cat_reg(&s->log, &s->warn_cat, "WARN", WARN, YELLOW, true);
121
+ log_cat_reg(&s->log, &s->info_cat, "INFO", INFO, GREEN, false);
122
+ log_cat_reg(&s->log, &s->debug_cat, "DEBUG", DEBUG, GRAY, false);
123
+ log_cat_reg(&s->log, &s->con_cat, "connect", INFO, GREEN, false);
124
+ log_cat_reg(&s->log, &s->req_cat, "request", INFO, CYAN, false);
125
+ log_cat_reg(&s->log, &s->resp_cat, "response", INFO, DARK_CYAN, false);
126
+ log_cat_reg(&s->log, &s->eval_cat, "eval", INFO, BLUE, false);
127
+
128
+ if (ERR_OK != log_init(err, &s->log, options)) {
129
+ return err->code;
130
+ }
131
+ if (Qnil != options) {
132
+ VALUE v;
133
+
134
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("thread_count"))))) {
135
+ int tc = FIX2INT(v);
136
+
137
+ if (1 <= tc || tc < 1000) {
138
+ s->thread_cnt = tc;
139
+ } else {
140
+ rb_raise(rb_eArgError, "thread_count must be between 1 and 1000.");
141
+ }
142
+ }
143
+ if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("pedantic"))))) {
144
+ s->pedantic = (Qtrue == v);
145
+ }
146
+ }
147
+ return ERR_OK;
148
+ }
149
+
150
+ /* Document-method: new
151
+ *
152
+ * call-seq: new(port, root, options)
153
+ *
154
+ * Creates a new server that will listen on the designated _port_ and using
155
+ * the _root_ as the root of the static resources. Logging is feature based
156
+ * and not level based and the options reflect that approach.
157
+ *
158
+ * - *options* [_Hash_] server options
159
+ *
160
+ * - *:pedantic* [_true_|_false_] if true response header and status codes are check and an exception raised if they violate the rack spec at https://github.com/rack/rack/blob/master/SPEC, https://tools.ietf.org/html/rfc3875#section-4.1.18, or https://tools.ietf.org/html/rfc7230.
161
+ *
162
+ *
163
+ * - *: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.
164
+ *
165
+ * - *:log_dir* [_String_] directory to place log files in. If nil or empty then no log files are written.
166
+ *
167
+
168
+ * - *:log_console* [_true_|_false_] if true log entry are display on the console.
169
+ *
170
+ * - *:log_classic* [_true_|_false_] if true log entry follow a classic format. If false log entries are JSON.
171
+ *
172
+ * - *:log_colorize* [_true_|_false_] if true log entries are colorized.
173
+ *
174
+ * - *:log_states* [_Hash_] a map of logging categories and whether they should be on or off. Categories are:
175
+ * - *:ERROR* errors
176
+ * - *:WARN* warnings
177
+ * - *:INFO* infomational
178
+ * - *:DEBUG* debugging
179
+ * - *:connect* openning and closing of connections
180
+ * - *:request* requests
181
+ * - *:response* responses
182
+ * - *:eval* handler evaluationss
183
+ */
184
+ static VALUE
185
+ server_new(int argc, VALUE *argv, VALUE self) {
186
+ Server s;
187
+ struct _Err err = ERR_INIT;
188
+ int port;
189
+ const char *root;
190
+ VALUE options = Qnil;
191
+
192
+ if (argc < 2 || 3 < argc) {
193
+ rb_raise(rb_eArgError, "Wrong number of arguments to Agoo::Server.new.");
194
+ }
195
+ port = FIX2INT(argv[0]);
196
+ rb_check_type(argv[1], T_STRING);
197
+ root = StringValuePtr(argv[1]);
198
+ if (3 <= argc) {
199
+ options = argv[2];
200
+ }
201
+ s = ALLOC(struct _Server);
202
+ memset(s, 0, sizeof(struct _Server));
203
+ if (ERR_OK != configure(&err, s, port, root, options)) {
204
+ xfree(s);
205
+ // TBD raise Agoo specific exception
206
+ rb_raise(rb_eArgError, "%s", err.msg);
207
+ }
208
+ queue_multi_init(&s->con_queue, 256, false, false);
209
+ queue_multi_init(&s->eval_queue, 1024, false, true);
210
+ cache_init(&s->pages);
211
+ the_server = s;
212
+
213
+ return Data_Wrap_Struct(server_class, NULL, server_free, s);
214
+ }
215
+
216
+ static void*
217
+ listen_loop(void *x) {
218
+ Server server = (Server)x;
219
+ struct sockaddr_in addr;
220
+ int optval = 1;
221
+ struct pollfd pa[1];
222
+ struct pollfd *fp = pa;
223
+ struct _Err err = ERR_INIT;
224
+ struct sockaddr_in client_addr;
225
+ int client_sock;
226
+ socklen_t alen = 0;
227
+ Con con;
228
+ int i;
229
+ uint64_t cnt = 0;
230
+
231
+ atomic_fetch_add(&server->running, 1);
232
+ if (0 >= (pa->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) {
233
+ log_cat(&server->error_cat, "Server failed to open server socket. %s.", strerror(errno));
234
+ atomic_fetch_sub(&server->running, 1);
235
+ return NULL;
236
+ }
237
+ #ifdef OSX_OS
238
+ setsockopt(pa->fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
239
+ #endif
240
+ memset(&addr, 0, sizeof(addr));
241
+ addr.sin_family = AF_INET;
242
+ addr.sin_addr.s_addr = INADDR_ANY;
243
+ addr.sin_port = htons(server->port);
244
+ setsockopt(pa->fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
245
+ setsockopt(pa->fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
246
+ if (0 > bind(fp->fd, (struct sockaddr*)&addr, sizeof(addr))) {
247
+ log_cat(&server->error_cat, "Server failed to bind server socket. %s.", strerror(errno));
248
+ atomic_fetch_sub(&server->running, 1);
249
+ return NULL;
250
+ }
251
+ listen(pa->fd, 1000);
252
+ pa->events = POLLIN;
253
+ pa->revents = 0;
254
+
255
+ memset(&client_addr, 0, sizeof(client_addr));
256
+ while (server->active) {
257
+ if (0 > (i = poll(pa, 1, 100))) {
258
+ if (EAGAIN == errno) {
259
+ continue;
260
+ }
261
+ log_cat(&server->error_cat, "Server polling error. %s.", strerror(errno));
262
+ // Either a signal or something bad like out of memory. Might as well exit.
263
+ break;
264
+ }
265
+ if (0 == i) { // nothing to read
266
+ continue;
267
+ }
268
+ if (0 != (pa->revents & POLLIN)) {
269
+ if (0 > (client_sock = accept(pa->fd, (struct sockaddr*)&client_addr, &alen))) {
270
+ log_cat(&server->error_cat, "Server accept connection failed. %s.", strerror(errno));
271
+ } else if (NULL == (con = con_create(&err, server, client_sock, ++cnt))) {
272
+ log_cat(&server->error_cat, "Server accept connection failed. %s.", err.msg);
273
+ close(client_sock);
274
+ cnt--;
275
+ err_clear(&err);
276
+ } else {
277
+ #ifdef OSX_OS
278
+ setsockopt(client_sock, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
279
+ #endif
280
+ fcntl(client_sock, F_SETFL, O_NONBLOCK);
281
+ setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
282
+ setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
283
+ log_cat(&server->con_cat, "Server accepted connection %llu on port %d [%d]", (unsigned long long)cnt, server->port, con->sock);
284
+ queue_push(&server->con_queue, (void*)con);
285
+ }
286
+ }
287
+ if (0 != (pa->revents & (POLLERR | POLLHUP | POLLNVAL))) {
288
+ if (0 != (pa->revents & (POLLHUP | POLLNVAL))) {
289
+ log_cat(&server->error_cat, "Agoo server socket on port %d closed.", server->port);
290
+ } else {
291
+ log_cat(&server->error_cat, "Agoo server socket on port %d error.", server->port);
292
+ }
293
+ server->active = false;
294
+ }
295
+ pa->revents = 0;
296
+ }
297
+ close(pa->fd);
298
+ atomic_fetch_sub(&server->running, 1);
299
+
300
+ return NULL;
301
+ }
302
+
303
+ static const char bad500[] = "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: ";
304
+
305
+ static VALUE
306
+ rescue_error(VALUE x) {
307
+ Req req = (Req)x;
308
+ volatile VALUE info = rb_errinfo();
309
+ volatile VALUE msg = rb_funcall(info, rb_intern("message"), 0);
310
+ const char *classname = rb_obj_classname(info);
311
+ const char *ms = rb_string_value_ptr(&msg);
312
+ char buf[1024];
313
+ int len = (int)(strlen(classname) + 2 + strlen(ms));
314
+ int cnt;
315
+ Text message;
316
+
317
+ if ((int)(sizeof(buf) - sizeof(bad500) + 7) <= len) {
318
+ len = sizeof(buf) - sizeof(bad500) + 7;
319
+ }
320
+ cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: %d\r\n\r\n%s: %s", len, classname, ms);
321
+ message = text_create(buf, cnt);
322
+
323
+ req->res->close = true;
324
+ res_set_message(req->res, message);
325
+ queue_wakeup(&req->server->con_queue);
326
+
327
+ return Qfalse;
328
+ }
329
+
330
+ static VALUE
331
+ handle_base_inner(void *x) {
332
+ Req req = (Req)x;
333
+ volatile VALUE rr = request_wrap(req);
334
+ volatile VALUE rres = response_new(req->server);
335
+
336
+ rb_funcall(req->handler, on_request_id, 2, rr, rres);
337
+ DATA_PTR(rr) = NULL;
338
+
339
+ res_set_message(req->res, response_text(rres));
340
+ queue_wakeup(&req->server->con_queue);
341
+
342
+ return Qfalse;
343
+ }
344
+
345
+ static void*
346
+ handle_base(void *x) {
347
+ rb_rescue2(handle_base_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
348
+
349
+ return NULL;
350
+ }
351
+
352
+ static int
353
+ header_cb(VALUE key, VALUE value, Text *tp) {
354
+ const char *ks = StringValuePtr(key);
355
+ int klen = (int)RSTRING_LEN(key);
356
+ const char *vs = StringValuePtr(value);
357
+ int vlen = (int)RSTRING_LEN(value);
358
+
359
+ http_header_ok(ks, klen, vs, vlen);
360
+ if (0 != strcasecmp("Content-Length", ks)) {
361
+ *tp = text_append(*tp, ks, klen);
362
+ *tp = text_append(*tp, ": ", 2);
363
+ *tp = text_append(*tp, vs, vlen);
364
+ *tp = text_append(*tp, "\r\n", 2);
365
+ }
366
+ return ST_CONTINUE;
367
+ }
368
+
369
+ static VALUE
370
+ header_each_cb(VALUE kv, Text *tp) {
371
+ header_cb(rb_ary_entry(kv, 0), rb_ary_entry(kv, 1), tp);
372
+
373
+ return Qnil;
374
+ }
375
+
376
+ static VALUE
377
+ body_len_cb(VALUE v, int *sizep) {
378
+ *sizep += (int)RSTRING_LEN(v);
379
+
380
+ return Qnil;
381
+ }
382
+
383
+ static VALUE
384
+ body_append_cb(VALUE v, Text *tp) {
385
+ *tp = text_append(*tp, StringValuePtr(v), (int)RSTRING_LEN(v));
386
+
387
+ return Qnil;
388
+ }
389
+
390
+ static VALUE
391
+ handle_rack_inner(void *x) {
392
+ Req req = (Req)x;
393
+ Text t;
394
+ volatile VALUE env = request_env(req);
395
+ volatile VALUE res = rb_funcall(req->handler, call_id, 1, env);
396
+ volatile VALUE hv;
397
+ volatile VALUE bv;
398
+ int code;
399
+ const char *status_msg;
400
+ int bsize = 0;
401
+
402
+ rb_check_type(res, T_ARRAY);
403
+ if (3 != RARRAY_LEN(res)) {
404
+ rb_raise(rb_eArgError, "a rack call() response must be an array of 3 members.");
405
+ }
406
+ code = NUM2INT(rb_funcall(rb_ary_entry(res, 0), to_i_id, 0));
407
+ status_msg = http_code_message(code);
408
+ if ('\0' == *status_msg) {
409
+ rb_raise(rb_eArgError, "invalid rack call() response status code (%d).", code);
410
+ }
411
+ hv = rb_ary_entry(res, 1);
412
+ if (!rb_respond_to(hv, each_id)) {
413
+ rb_raise(rb_eArgError, "invalid rack call() response headers does not respond to each.");
414
+ }
415
+ bv = rb_ary_entry(res, 2);
416
+ if (!rb_respond_to(bv, each_id)) {
417
+ rb_raise(rb_eArgError, "invalid rack call() response body does not respond to each.");
418
+ }
419
+ if (NULL == (t = text_allocate(1024))) {
420
+ rb_raise(rb_eArgError, "failed to allocate response.");
421
+ }
422
+ if (T_ARRAY != rb_type(bv)) {
423
+ int i;
424
+ int bcnt = RARRAY_LEN(bv);
425
+
426
+ for (i = 0; i < bcnt; i++) {
427
+ bsize += (int)RSTRING_LEN(rb_ary_entry(bv, i));
428
+ }
429
+ } else {
430
+ rb_iterate (rb_each, bv, body_len_cb, (VALUE)&bsize);
431
+ }
432
+ switch (code) {
433
+ case 100:
434
+ case 101:
435
+ case 102:
436
+ case 204:
437
+ case 205:
438
+ case 304:
439
+ // TBD Content-Type and Content-Length can not be present
440
+ t->len = sprintf(t->text, "HTTP/1.1 %d %s\r\n", code, status_msg);
441
+ break;
442
+ default:
443
+ t->len = sprintf(t->text, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n", code, status_msg, bsize);
444
+ break;
445
+ }
446
+ if (T_HASH == rb_type(hv)) {
447
+ rb_hash_foreach(hv, header_cb, (VALUE)&t);
448
+ } else {
449
+ rb_iterate (rb_each, hv, header_each_cb, (VALUE)&t);
450
+ }
451
+ t = text_append(t, "\r\n", 2);
452
+ if (0 < bsize) {
453
+ if (T_ARRAY == rb_type(bv)) {
454
+ VALUE v;
455
+ int i;
456
+ int bcnt = RARRAY_LEN(bv);
457
+
458
+ for (i = 0; i < bcnt; i++) {
459
+ v = rb_ary_entry(bv, i);
460
+ t = text_append(t, StringValuePtr(v), (int)RSTRING_LEN(v));
461
+ }
462
+ } else {
463
+ rb_iterate (rb_each, bv, body_append_cb, (VALUE)&t);
464
+ }
465
+ }
466
+ res_set_message(req->res, t);
467
+ queue_wakeup(&req->server->con_queue);
468
+
469
+ return Qfalse;
470
+ }
471
+
472
+ static void*
473
+ handle_rack(void *x) {
474
+ // Disable GC. The handle_rack function or rather the env seems to get
475
+ // collected even though it is volatile so for now turn off GC
476
+ // temporarily.
477
+ rb_gc_disable();
478
+ rb_rescue2(handle_rack_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
479
+ rb_gc_enable();
480
+
481
+ return NULL;
482
+ }
483
+
484
+ static VALUE
485
+ handle_wab_inner(void *x) {
486
+ Req req = (Req)x;
487
+ volatile VALUE rr = request_wrap(req);
488
+ volatile VALUE rres = response_new(req->server);
489
+
490
+ rb_funcall(req->handler, on_request_id, 2, rr, rres);
491
+ DATA_PTR(rr) = NULL;
492
+
493
+ res_set_message(req->res, response_text(rres));
494
+ queue_wakeup(&req->server->con_queue);
495
+
496
+ return Qfalse;
497
+ }
498
+
499
+ static void*
500
+ handle_wab(void *x) {
501
+ rb_rescue2(handle_wab_inner, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
502
+
503
+ return NULL;
504
+ }
505
+ static void
506
+ handle_protected(Req req) {
507
+
508
+ switch (req->handler_type) {
509
+ case BASE_HOOK:
510
+ rb_thread_call_with_gvl(handle_base, req);
511
+ break;
512
+ case RACK_HOOK:
513
+ rb_thread_call_with_gvl(handle_rack, req);
514
+ break;
515
+ case WAB_HOOK:
516
+ rb_thread_call_with_gvl(handle_wab, req);
517
+ break;
518
+ default: {
519
+ char buf[256];
520
+ int cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n");
521
+ Text message = text_create(buf, cnt);
522
+
523
+ req->res->close = true;
524
+ res_set_message(req->res, message);
525
+ queue_wakeup(&req->server->con_queue);
526
+ break;
527
+ }
528
+ }
529
+ }
530
+
531
+ static void*
532
+ process_loop(void *ptr) {
533
+ Server server = (Server)ptr;
534
+ Req req;
535
+
536
+ atomic_fetch_add(&server->running, 1);
537
+ while (server->active) {
538
+ if (NULL != (req = (Req)queue_pop(&server->eval_queue, 0.1))) {
539
+ handle_protected(req);
540
+ }
541
+ }
542
+ atomic_fetch_sub(&server->running, 1);
543
+
544
+ return NULL;
545
+ }
546
+
547
+ static VALUE
548
+ wrap_process_loop(void *ptr) {
549
+ rb_thread_call_without_gvl(process_loop, ptr, RUBY_UBF_IO, NULL);
550
+ return Qnil;
551
+ }
552
+
553
+ /* Document-method: start
554
+ *
555
+ * call-seq: start()
556
+ *
557
+ * Start the server.
558
+ */
559
+ static VALUE
560
+ start(VALUE self) {
561
+ Server server = (Server)DATA_PTR(self);
562
+ VALUE *vp;
563
+ int i;
564
+
565
+ server->active = true;
566
+
567
+ pthread_create(&server->listen_thread, NULL, listen_loop, server);
568
+ pthread_create(&server->con_thread, NULL, con_loop, server);
569
+
570
+ if (0 >= server->thread_cnt) {
571
+ Req req;
572
+
573
+ while (server->active) {
574
+ if (NULL != (req = (Req)queue_pop(&server->eval_queue, 0.1))) {
575
+ switch (req->handler_type) {
576
+ case BASE_HOOK:
577
+ handle_base(req);
578
+ break;
579
+ case RACK_HOOK:
580
+ handle_rack(req);
581
+ break;
582
+ case WAB_HOOK:
583
+ handle_wab(req);
584
+ break;
585
+ default: {
586
+ char buf[256];
587
+ int cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 500 Internal Error\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n");
588
+ Text message = text_create(buf, cnt);
589
+
590
+ req->res->close = true;
591
+ res_set_message(req->res, message);
592
+ queue_wakeup(&req->server->con_queue);
593
+ break;
594
+ }
595
+ }
596
+ }
597
+ }
598
+ } else {
599
+ server->eval_threads = ALLOC_N(VALUE, server->thread_cnt + 1);
600
+ for (i = server->thread_cnt, vp = server->eval_threads; 0 < i; i--, vp++) {
601
+ *vp = rb_thread_create(wrap_process_loop, server);
602
+ }
603
+ *vp = Qnil;
604
+ }
605
+ return Qnil;
606
+ }
607
+
608
+ /* Document-method: shutdown
609
+ *
610
+ * call-seq: shutdown()
611
+ *
612
+ * Shutdown the server. Logs and queues are flushed before shutting down.
613
+ */
614
+ static VALUE
615
+ server_shutdown(VALUE self) {
616
+ shutdown_server((Server)DATA_PTR(self));
617
+ return Qnil;
618
+ }
619
+
620
+ /* Document-method: error?
621
+ *
622
+ * call-seq: error?()
623
+ *
624
+ * Returns true is errors are being logged.
625
+ */
626
+ static VALUE
627
+ log_errorp(VALUE self) {
628
+ return ((Server)DATA_PTR(self))->error_cat.on ? Qtrue : Qfalse;
629
+ }
630
+
631
+ /* Document-method: warn?
632
+ *
633
+ * call-seq: warn?()
634
+ *
635
+ * Returns true is warnings are being logged.
636
+ */
637
+ static VALUE
638
+ log_warnp(VALUE self) {
639
+ return ((Server)DATA_PTR(self))->warn_cat.on ? Qtrue : Qfalse;
640
+ }
641
+
642
+ /* Document-method: info?
643
+ *
644
+ * call-seq: info?()
645
+ *
646
+ * Returns true is info entries are being logged.
647
+ */
648
+ static VALUE
649
+ log_infop(VALUE self) {
650
+ return ((Server)DATA_PTR(self))->info_cat.on ? Qtrue : Qfalse;
651
+ }
652
+
653
+ /* Document-method: debug?
654
+ *
655
+ * call-seq: debug?()
656
+ *
657
+ * Returns true is debug entries are being logged.
658
+ */
659
+ static VALUE
660
+ log_debugp(VALUE self) {
661
+ return ((Server)DATA_PTR(self))->debug_cat.on ? Qtrue : Qfalse;
662
+ }
663
+
664
+ /* Document-method: eval?
665
+ *
666
+ * call-seq: eval?()
667
+ *
668
+ * Returns true is handler evaluation entries are being logged.
669
+ */
670
+ static VALUE
671
+ log_evalp(VALUE self) {
672
+ return ((Server)DATA_PTR(self))->eval_cat.on ? Qtrue : Qfalse;
673
+ }
674
+
675
+ /* Document-method: error
676
+ *
677
+ * call-seq: error(msg)
678
+ *
679
+ * Log an error message.
680
+ */
681
+ static VALUE
682
+ log_error(VALUE self, VALUE msg) {
683
+ log_cat(&((Server)DATA_PTR(self))->error_cat, "%s", StringValuePtr(msg));
684
+ return Qnil;
685
+ }
686
+
687
+ /* Document-method: warn
688
+ *
689
+ * call-seq: warn(msg)
690
+ *
691
+ * Log a warn message.
692
+ */
693
+ static VALUE
694
+ log_warn(VALUE self, VALUE msg) {
695
+ log_cat(&((Server)DATA_PTR(self))->warn_cat, "%s", StringValuePtr(msg));
696
+ return Qnil;
697
+ }
698
+
699
+ /* Document-method: info
700
+ *
701
+ * call-seq: info(msg)
702
+ *
703
+ * Log an info message.
704
+ */
705
+ static VALUE
706
+ log_info(VALUE self, VALUE msg) {
707
+ log_cat(&((Server)DATA_PTR(self))->info_cat, "%s", StringValuePtr(msg));
708
+ return Qnil;
709
+ }
710
+
711
+ /* Document-method: debug
712
+ *
713
+ * call-seq: debug(msg)
714
+ *
715
+ * Log a debug message.
716
+ */
717
+ static VALUE
718
+ log_debug(VALUE self, VALUE msg) {
719
+ log_cat(&((Server)DATA_PTR(self))->debug_cat, "%s", StringValuePtr(msg));
720
+ return Qnil;
721
+ }
722
+
723
+ /* Document-method: log_eval
724
+ *
725
+ * call-seq: log_eval(msg)
726
+ *
727
+ * Log an eval message.
728
+ */
729
+ static VALUE
730
+ log_eval(VALUE self, VALUE msg) {
731
+ log_cat(&((Server)DATA_PTR(self))->eval_cat, "%s", StringValuePtr(msg));
732
+ return Qnil;
733
+ }
734
+
735
+ /* Document-method: log_state
736
+ *
737
+ * call-seq: log_state(label)
738
+ *
739
+ * Return the logging state of the category identified by the _label_.
740
+ */
741
+ static VALUE
742
+ log_state(VALUE self, VALUE label) {
743
+ Server server = (Server)DATA_PTR(self);
744
+ LogCat cat = log_cat_find(&server->log, StringValuePtr(label));
745
+
746
+ if (NULL == cat) {
747
+ rb_raise(rb_eArgError, "%s is not a valid log category.", StringValuePtr(label));
748
+ }
749
+ return cat->on ? Qtrue : Qfalse;
750
+ }
751
+
752
+ /* Document-method: set_log_state
753
+ *
754
+ * call-seq: set_log_state(label, state)
755
+ *
756
+ * Set the logging state of the category identified by the _label_.
757
+ */
758
+ static VALUE
759
+ set_log_state(VALUE self, VALUE label, VALUE on) {
760
+ Server server = (Server)DATA_PTR(self);
761
+ LogCat cat = log_cat_find(&server->log, StringValuePtr(label));
762
+
763
+ if (NULL == cat) {
764
+ rb_raise(rb_eArgError, "%s is not a valid log category.", StringValuePtr(label));
765
+ }
766
+ cat->on = (Qtrue == on);
767
+
768
+ return Qnil;
769
+ }
770
+
771
+ /* Document-method: log_flush
772
+ *
773
+ * call-seq: log_flush()
774
+ *
775
+ * Flush the log queue and write all entries to disk or the console. The call
776
+ * waits for the flush to complete.
777
+ */
778
+ static VALUE
779
+ server_log_flush(VALUE self, VALUE to) {
780
+ double timeout = NUM2DBL(to);
781
+
782
+ if (!log_flush(&((Server)DATA_PTR(self))->log, timeout)) {
783
+ rb_raise(rb_eStandardError, "timed out waiting for log flush.");
784
+ }
785
+ return Qnil;
786
+ }
787
+
788
+ /* Document-method: handle
789
+ *
790
+ * call-seq: handle(method, pattern, handler)
791
+ *
792
+ * Registers a handler for the HTTP method and path pattern specified. The
793
+ * path pattern follows glob like rules in that a single * matches a single
794
+ * token bounded by the `/` character and a double ** matches all remaining.
795
+ */
796
+ static VALUE
797
+ handle(VALUE self, VALUE method, VALUE pattern, VALUE handler) {
798
+ Server server = (Server)DATA_PTR(self);
799
+ Hook hook;
800
+ Method meth = ALL;
801
+ const char *pat;
802
+
803
+ rb_check_type(pattern, T_STRING);
804
+ pat = StringValuePtr(pattern);
805
+
806
+ if (connect_sym == method) {
807
+ meth = CONNECT;
808
+ } else if (delete_sym == method) {
809
+ meth = DELETE;
810
+ } else if (get_sym == method) {
811
+ meth = GET;
812
+ } else if (head_sym == method) {
813
+ meth = HEAD;
814
+ } else if (options_sym == method) {
815
+ meth = OPTIONS;
816
+ } else if (post_sym == method) {
817
+ meth = POST;
818
+ } else if (put_sym == method) {
819
+ meth = PUT;
820
+ } else if (Qnil == method) {
821
+ meth = ALL;
822
+ } else {
823
+ rb_raise(rb_eArgError, "invalid method");
824
+ }
825
+ if (NULL == (hook = hook_create(meth, pat, handler))) {
826
+ rb_raise(rb_eStandardError, "out of memory.");
827
+ } else {
828
+ Hook h;
829
+ Hook prev = NULL;
830
+
831
+ for (h = server->hooks; NULL != h; h = h->next) {
832
+ prev = h;
833
+ }
834
+ if (NULL != prev) {
835
+ prev->next = hook;
836
+ } else {
837
+ server->hooks = hook;
838
+ }
839
+ }
840
+ return Qnil;
841
+ }
842
+
843
+ /* Document-class: Agoo::Server
844
+ *
845
+ * An HTTP server that support the rack API as well as some other optimized
846
+ * APIs for handling HTTP requests.
847
+ */
848
+ void
849
+ server_init(VALUE mod) {
850
+ server_class = rb_define_class_under(mod, "Server", rb_cObject);
851
+
852
+ rb_define_module_function(server_class, "new", server_new, -1);
853
+ rb_define_method(server_class, "start", start, 0);
854
+ rb_define_method(server_class, "shutdown", server_shutdown, 0);
855
+
856
+ rb_define_method(server_class, "error?", log_errorp, 0);
857
+ rb_define_method(server_class, "warn?", log_warnp, 0);
858
+ rb_define_method(server_class, "info?", log_infop, 0);
859
+ rb_define_method(server_class, "debug?", log_debugp, 0);
860
+ rb_define_method(server_class, "log_eval?", log_evalp, 0);
861
+ rb_define_method(server_class, "error", log_error, 1);
862
+ rb_define_method(server_class, "warn", log_warn, 1);
863
+ rb_define_method(server_class, "info", log_info, 1);
864
+ rb_define_method(server_class, "debug", log_debug, 1);
865
+ rb_define_method(server_class, "log_eval", log_eval, 1);
866
+
867
+ rb_define_method(server_class, "log_state", log_state, 1);
868
+ rb_define_method(server_class, "set_log_state", set_log_state, 2);
869
+ rb_define_method(server_class, "log_flush", server_log_flush, 1);
870
+
871
+ rb_define_method(server_class, "handle", handle, 3);
872
+
873
+ call_id = rb_intern("call");
874
+ each_id = rb_intern("each");
875
+ on_request_id = rb_intern("on_request");
876
+ to_i_id = rb_intern("to_i");
877
+
878
+ connect_sym = ID2SYM(rb_intern("CONNECT")); rb_gc_register_address(&connect_sym);
879
+ delete_sym = ID2SYM(rb_intern("DELETE")); rb_gc_register_address(&delete_sym);
880
+ get_sym = ID2SYM(rb_intern("GET")); rb_gc_register_address(&get_sym);
881
+ head_sym = ID2SYM(rb_intern("HEAD")); rb_gc_register_address(&head_sym);
882
+ options_sym = ID2SYM(rb_intern("OPTIONS")); rb_gc_register_address(&options_sym);
883
+ post_sym = ID2SYM(rb_intern("POST")); rb_gc_register_address(&post_sym);
884
+ put_sym = ID2SYM(rb_intern("PUT")); rb_gc_register_address(&put_sym);
885
+
886
+ http_init();
887
+
888
+ signal(SIGINT, sig_handler);
889
+ signal(SIGTERM, sig_handler);
890
+ signal(SIGPIPE, SIG_IGN);
891
+ }