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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +61 -0
- data/ext/agoo/agoo.c +19 -0
- data/ext/agoo/con.c +515 -0
- data/ext/agoo/con.h +46 -0
- data/ext/agoo/dtime.c +52 -0
- data/ext/agoo/dtime.h +10 -0
- data/ext/agoo/err.c +78 -0
- data/ext/agoo/err.h +48 -0
- data/ext/agoo/error_stream.c +92 -0
- data/ext/agoo/error_stream.h +13 -0
- data/ext/agoo/extconf.rb +13 -0
- data/ext/agoo/hook.c +74 -0
- data/ext/agoo/hook.h +33 -0
- data/ext/agoo/http.c +617 -0
- data/ext/agoo/http.h +13 -0
- data/ext/agoo/log.c +497 -0
- data/ext/agoo/log.h +106 -0
- data/ext/agoo/log_queue.h +30 -0
- data/ext/agoo/page.c +342 -0
- data/ext/agoo/page.h +39 -0
- data/ext/agoo/queue.c +191 -0
- data/ext/agoo/queue.h +39 -0
- data/ext/agoo/request.c +563 -0
- data/ext/agoo/request.h +36 -0
- data/ext/agoo/res.c +38 -0
- data/ext/agoo/res.h +28 -0
- data/ext/agoo/response.c +271 -0
- data/ext/agoo/response.h +33 -0
- data/ext/agoo/server.c +891 -0
- data/ext/agoo/server.h +47 -0
- data/ext/agoo/text.c +66 -0
- data/ext/agoo/text.h +24 -0
- data/ext/agoo/types.h +18 -0
- data/lib/agoo.rb +9 -0
- data/lib/agoo/version.rb +5 -0
- data/test/base_handler_test.rb +170 -0
- data/test/log_test.rb +269 -0
- data/test/rack_handler_test.rb +147 -0
- data/test/static_test.rb +81 -0
- data/test/tests.rb +8 -0
- metadata +159 -0
data/ext/agoo/request.h
ADDED
@@ -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__
|
data/ext/agoo/response.c
ADDED
@@ -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
|
+
}
|
data/ext/agoo/response.h
ADDED
@@ -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
|
+
}
|