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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 875dd13c45030ea940c3afe58571a260f6220ff8f09ed3fd1084957e0efa3586
4
+ data.tar.gz: d8efe96974e9299d1627373cc31be42f93b7bed3eb00c7593c32b8aa9fe954cb
5
+ SHA512:
6
+ metadata.gz: b66d7a3f8265fab7f84c9a78894401950adcbd7a55df412ab7ff92bc497ff825777498cf58ff1a0e3ba0b168aecec597da823ac61455f57b4f9d26ef32e1264b
7
+ data.tar.gz: d82ea3f18c07459186e64a8db4fda82e07bdb55e7506a031eebeb628c05e458eb4507fb531144fd1559f39c5543d0569a9fdc9738df47110ed4edf68045c1c83
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Peter Ohler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # agoo
2
+
3
+ [![Build Status](https://img.shields.io/travis/ohler55/agoo/master.svg)](http://travis-ci.org/ohler55/agoo?branch=master)
4
+
5
+ A High Performance HTTP Server for Ruby
6
+
7
+ ## Usage
8
+
9
+ ```ruby
10
+ require 'agoo'
11
+
12
+ server = Agoo::Server.new(6464, 'root')
13
+
14
+ class MyHandler
15
+ def initialize
16
+ end
17
+
18
+ def call(req)
19
+ [ 200, { }, [ "hello world" ] ]
20
+ end
21
+ end
22
+
23
+ handler = TellMeHandler.new
24
+ server.handle(:GET, "/hello", handler)
25
+ server.start()
26
+ ```
27
+
28
+ ## Installation
29
+ ```
30
+ gem install agoo
31
+ ```
32
+
33
+ ## What Is This?
34
+
35
+ Agoo is Japanese for a type of flying fish. This gem flies. It is a high
36
+ performance HTTP server that serves static resource at hundreds of thousands
37
+ of fetchs per second. A a simple hello world Ruby handler at over 100,000
38
+ requests per second on a desktop computer. That places Agoo at about 80 times
39
+ faster than Sinatra and 1000 times faster than Rails. In both cases the
40
+ latency was an order of magnitude lower or more. Checkout the benchmarks on <a
41
+ href="http://opo.technology/benchmarks.html#web_benchmarks">OpO
42
+ benchmarks</a>. Note that the benchmarks had to use a C program called _hose_
43
+ from the <a href="http://opo.technology/index.html">OpO</a> downloads to hit
44
+ the Agoo limits. Ruby benchmarks driver could not push Agoo hard enough.
45
+
46
+ Agoo supports the [Ruby rack API](https://rack.github.io) which allows for the
47
+ use of rack compatible gems.
48
+
49
+ ## Releases
50
+
51
+ See [{file:CHANGELOG.md}](CHANGELOG.md)
52
+
53
+ ## Links
54
+
55
+ - *Documentation*: http://rubydoc.info/gems/agoo
56
+
57
+ - *GitHub* *repo*: https://github.com/ohler55/agoo
58
+
59
+ - *RubyGems* *repo*: https://rubygems.org/gems/agoo
60
+
61
+ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announcements and news about the Agoo gem.
data/ext/agoo/agoo.c ADDED
@@ -0,0 +1,19 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdio.h>
4
+ #include <ruby.h>
5
+
6
+ #include "error_stream.h"
7
+ #include "request.h"
8
+ #include "response.h"
9
+ #include "server.h"
10
+
11
+ void
12
+ Init_agoo() {
13
+ VALUE mod = rb_define_module("Agoo");
14
+
15
+ error_stream_init(mod);
16
+ request_init(mod);
17
+ response_init(mod);
18
+ server_init(mod);
19
+ }
data/ext/agoo/con.c ADDED
@@ -0,0 +1,515 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <ctype.h>
4
+ #include <string.h>
5
+
6
+ #include "con.h"
7
+ #include "dtime.h"
8
+ #include "hook.h"
9
+ #include "http.h"
10
+ #include "res.h"
11
+ #include "server.h"
12
+
13
+ #define MAX_SOCK 4096
14
+ #define CON_TIMEOUT 5.0
15
+
16
+ Con
17
+ con_create(Err err, Server server, int sock, uint64_t id) {
18
+ Con c;
19
+
20
+ if (MAX_SOCK <= sock) {
21
+ err_set(err, ERR_TOO_MANY, "Too many connections.");
22
+ return NULL;
23
+ }
24
+ if (NULL == (c = (Con)malloc(sizeof(struct _Con)))) {
25
+ err_set(err, ERR_MEMORY, "Failed to allocate memory for a connection.");
26
+ } else {
27
+ memset(c, 0, sizeof(struct _Con));
28
+ c->sock = sock;
29
+ c->iid = id;
30
+ sprintf(c->id, "%llu", (unsigned long long)id);
31
+ c->server = server;
32
+ }
33
+ return c;
34
+ }
35
+
36
+ void
37
+ con_destroy(Con c) {
38
+ if (0 < c->sock) {
39
+ close(c->sock);
40
+ c->sock = 0;
41
+ }
42
+ if (NULL != c->req) {
43
+ free(c->req);
44
+ }
45
+ free(c);
46
+ }
47
+
48
+ const char*
49
+ con_header_value(const char *header, int hlen, const char *key, int *vlen) {
50
+ // Search for \r then check for \n and then the key followed by a :. Keep
51
+ // trying until the end of the header.
52
+ const char *h = header;
53
+ const char *hend = header + hlen;
54
+ const char *value;
55
+ int klen = strlen(key);
56
+
57
+ while (h < hend) {
58
+ if (0 == strncmp(key, h, klen) && ':' == h[klen]) {
59
+ h += klen + 1;
60
+ for (; ' ' == *h; h++) {
61
+ }
62
+ value = h;
63
+ for (; '\r' != *h && '\0' != *h; h++) {
64
+ }
65
+ *vlen = h - value;
66
+
67
+ return value;
68
+ }
69
+ for (; h < hend; h++) {
70
+ if ('\r' == *h && '\n' == *(h + 1)) {
71
+ h += 2;
72
+ break;
73
+ }
74
+ }
75
+ }
76
+ return NULL;
77
+ }
78
+
79
+ static bool
80
+ bad_request(Con c, int status, int line) {
81
+ Res res;
82
+ const char *msg = http_code_message(status);
83
+
84
+ if (NULL == (res = res_create())) {
85
+ log_cat(&c->server->error_cat, "memory allocation of response failed on connection %llu @ %d.", c->iid, line);
86
+ } else {
87
+ char buf[256];
88
+ int cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 %d %s\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n", status, msg);
89
+ Text message = text_create(buf, cnt);
90
+
91
+ if (NULL == c->res_tail) {
92
+ c->res_head = res;
93
+ } else {
94
+ c->res_tail->next = res;
95
+ }
96
+ c->res_tail = res;
97
+ res->close = true;
98
+ res_set_message(res, message);
99
+ }
100
+ return true;
101
+ }
102
+
103
+ static bool
104
+ should_close(const char *header, int hlen) {
105
+ const char *v;
106
+ int vlen = 0;
107
+
108
+ if (NULL != (v = con_header_value(header, hlen, "Connection", &vlen))) {
109
+ return (5 == vlen && 0 == strncasecmp("Close", v, 5));
110
+ }
111
+ return false;
112
+ }
113
+
114
+ // Returns true if request has handled.
115
+ static bool
116
+ con_header_read(Con c) {
117
+ Server server = c->server;
118
+ char *hend = strstr(c->buf, "\r\n\r\n");
119
+ Method method;
120
+ char *path;
121
+ char *pend;
122
+ char *query = NULL;
123
+ char *qend;
124
+ char *b;
125
+ size_t clen = 0;
126
+ size_t mlen;
127
+ Hook hook = NULL;
128
+
129
+ if (NULL == hend) {
130
+ if (sizeof(c->buf) - 1 <= c->bcnt) {
131
+ return bad_request(c, 431, __LINE__);
132
+ }
133
+ return false;
134
+ }
135
+ if (server->req_cat.on) {
136
+ *hend = '\0';
137
+ log_cat(&server->req_cat, "%llu: %s", c->iid, c->buf);
138
+ *hend = '\r';
139
+ }
140
+ for (b = c->buf; ' ' != *b; b++) {
141
+ if ('\0' == *b) {
142
+ return bad_request(c, 400, __LINE__);
143
+ }
144
+ }
145
+ switch (toupper(*c->buf)) {
146
+ case 'G':
147
+ if (3 != b - c->buf || 0 != strncmp("GET", c->buf, 3)) {
148
+ return bad_request(c, 400, __LINE__);
149
+ }
150
+ method = GET;
151
+ break;
152
+ case 'P': {
153
+ const char *v;
154
+ int vlen = 0;
155
+ char *vend;
156
+
157
+ if (3 == b - c->buf && 0 == strncmp("PUT", c->buf, 3)) {
158
+ method = PUT;
159
+ } else if (4 == b - c->buf && 0 == strncmp("POST", c->buf, 4)) {
160
+ method = POST;
161
+ } else {
162
+ return bad_request(c, 400, __LINE__);
163
+ }
164
+ if (NULL == (v = con_header_value(c->buf, hend - c->buf, "Content-Length", &vlen))) {
165
+ return bad_request(c, 411, __LINE__);
166
+ }
167
+ clen = (size_t)strtoul(v, &vend, 10);
168
+ if (vend != v + vlen) {
169
+ return bad_request(c, 411, __LINE__);
170
+ }
171
+ break;
172
+ }
173
+ case 'D':
174
+ if (6 != b - c->buf || 0 != strncmp("DELETE", c->buf, 6)) {
175
+ return bad_request(c, 400, __LINE__);
176
+ }
177
+ method = DELETE;
178
+ break;
179
+ case 'H':
180
+ if (4 != b - c->buf || 0 != strncmp("HEAD", c->buf, 4)) {
181
+ return bad_request(c, 400, __LINE__);
182
+ }
183
+ method = HEAD;
184
+ break;
185
+ case 'O':
186
+ if (7 != b - c->buf || 0 != strncmp("OPTIONS", c->buf, 7)) {
187
+ return bad_request(c, 400, __LINE__);
188
+ }
189
+ method = OPTIONS;
190
+ break;
191
+ case 'C':
192
+ if (7 != b - c->buf || 0 != strncmp("CONNECT", c->buf, 7)) {
193
+ return bad_request(c, 400, __LINE__);
194
+ }
195
+ method = CONNECT;
196
+ break;
197
+ default:
198
+ return bad_request(c, 400, __LINE__);
199
+ }
200
+ for (; ' ' == *b; b++) {
201
+ if ('\0' == *b) {
202
+ return bad_request(c, 400, __LINE__);
203
+ }
204
+ }
205
+ path = b;
206
+ for (; ' ' != *b; b++) {
207
+ switch (*b) {
208
+ case '?':
209
+ pend = b;
210
+ query = b + 1;
211
+ break;
212
+ case '\0':
213
+ return bad_request(c, 400, __LINE__);
214
+ default:
215
+ break;
216
+ }
217
+ }
218
+ if (NULL == query) {
219
+ pend = b;
220
+ query = b;
221
+ qend = b;
222
+ } else {
223
+ qend = b;
224
+ }
225
+ if (NULL == (hook = hook_find(server->hooks, method, path, pend))) {
226
+ if (GET == method) {
227
+ struct _Err err = ERR_INIT;
228
+ Page p = page_get(&err, &server->pages, server->root, path, pend - path);
229
+ Res res;
230
+
231
+ if (NULL == p) {
232
+ return bad_request(c, 404, __LINE__);
233
+ }
234
+ if (NULL == (res = res_create())) {
235
+ return bad_request(c, 500, __LINE__);
236
+ }
237
+ if (NULL == c->res_tail) {
238
+ c->res_head = res;
239
+ } else {
240
+ c->res_tail->next = res;
241
+ }
242
+ c->res_tail = res;
243
+
244
+ b = strstr(c->buf, "\r\n");
245
+ res->close = should_close(b, hend - b);
246
+
247
+ text_ref(p->resp);
248
+ res_set_message(res, p->resp);
249
+
250
+ return true;
251
+ }
252
+ }
253
+ // Create request and populate.
254
+ mlen = hend - c->buf + 4 + clen;
255
+ if (NULL == (c->req = (Req)malloc(mlen + sizeof(struct _Req) - 8 + 1))) {
256
+ return bad_request(c, 413, __LINE__);
257
+ }
258
+ memcpy(c->req->msg, c->buf, c->bcnt);
259
+ if (c->bcnt < mlen) {
260
+ memset(c->req->msg + c->bcnt, 0, mlen - c->bcnt);
261
+ }
262
+ c->req->server = server;
263
+ c->req->method = method;
264
+ c->req->path.start = c->req->msg + (path - c->buf);
265
+ c->req->path.len = pend - path;
266
+ c->req->query.start = c->req->msg + (query - c->buf);
267
+ c->req->query.len = qend - query;
268
+ c->req->mlen = mlen;
269
+ c->req->body.start = c->req->msg + (hend - c->buf + 4);
270
+ c->req->body.len = clen;
271
+ b = strstr(b, "\r\n");
272
+ c->req->header.start = c->req->msg + (b + 2 - c->buf);
273
+ c->req->header.len = hend - b - 2;
274
+ c->req->res = NULL;
275
+ if (NULL != hook) {
276
+ c->req->handler = hook->handler;
277
+ c->req->handler_type = hook->type;
278
+ } else {
279
+ c->req->handler = Qnil;
280
+ c->req->handler_type = NO_HOOK;
281
+ }
282
+ return false;
283
+ }
284
+
285
+ // return true to remove/close connection
286
+ static bool
287
+ con_read(Con c) {
288
+ ssize_t cnt;
289
+
290
+ if (NULL != c->req) {
291
+ cnt = recv(c->sock, c->req->msg + c->bcnt, c->req->mlen - c->bcnt, 0);
292
+ } else {
293
+ cnt = recv(c->sock, c->buf + c->bcnt, sizeof(c->buf) - c->bcnt - 1, 0);
294
+ }
295
+ c->timeout = dtime() + CON_TIMEOUT;
296
+ if (0 >= cnt) {
297
+ // If nothing read then no need to complain. Just close.
298
+ if (0 < c->bcnt) {
299
+ if (0 == cnt) {
300
+ log_cat(&c->server->warn_cat, "Nothing to read. Client closed socket %s.", c->id);
301
+ } else {
302
+ log_cat(&c->server->warn_cat, "Failed to read request. %s.", strerror(errno));
303
+ }
304
+ }
305
+ return true;
306
+ }
307
+ c->bcnt += cnt;
308
+ // Terminate with \0 for debug and strstr() check
309
+ if (NULL == c->req) {
310
+ c->buf[c->bcnt] = '\0';
311
+ if (con_header_read(c)) { // already handled
312
+ c->bcnt = 0;
313
+ *c->buf = '\0';
314
+ // TBD handle extra data in buf
315
+
316
+ return false;
317
+ }
318
+ }
319
+ if (NULL != c->req) {
320
+ c->req->msg[c->bcnt] = '\0';
321
+ if (c->req->mlen <= c->bcnt) {
322
+ Res res;
323
+
324
+ if (c->server->debug_cat.on && NULL != c->req && NULL != c->req->body.start) {
325
+ log_cat(&c->server->debug_cat, "request on %llu: %s", c->iid, c->req->body.start);
326
+ }
327
+ if (NULL == (res = res_create())) {
328
+ c->req = NULL;
329
+ log_cat(&c->server->error_cat, "memory allocation of response failed on connection %llu.", c->iid);
330
+ return bad_request(c, 500, __LINE__);
331
+ } else {
332
+ if (NULL == c->res_tail) {
333
+ c->res_head = res;
334
+ } else {
335
+ c->res_tail->next = res;
336
+ }
337
+ c->res_tail = res;
338
+ res->close = should_close(c->req->header.start, c->req->header.len);
339
+ }
340
+ c->req->res = res;
341
+ queue_push(&c->server->eval_queue, (void*)c->req);
342
+ c->req = NULL;
343
+ c->bcnt = 0;
344
+ *c->buf = '\0';
345
+ // TBD handle extra data in buf
346
+
347
+ return false;
348
+ }
349
+ }
350
+ return false;
351
+ }
352
+
353
+ // return true to remove/close connection
354
+ static bool
355
+ con_write(Con c) {
356
+ Text message = res_message(c->res_head);
357
+ ssize_t cnt;
358
+
359
+ c->timeout = dtime() + CON_TIMEOUT;
360
+ if (0 == c->wcnt) {
361
+ if (c->server->resp_cat.on) {
362
+ char buf[4096];
363
+ char *hend = strstr(message->text, "\r\n\r\n");
364
+
365
+ if (NULL == hend) {
366
+ hend = message->text + message->len;
367
+ }
368
+ memcpy(buf, message->text, hend - message->text);
369
+ log_cat(&c->server->resp_cat, "%llu: %s", c->iid, buf);
370
+ }
371
+ if (c->server->debug_cat.on) {
372
+ log_cat(&c->server->debug_cat, "response on %llu: %s", c->iid, message->text);
373
+ }
374
+ }
375
+ if (0 > (cnt = send(c->sock, message->text + c->wcnt, message->len - c->wcnt, 0))) {
376
+ if (EAGAIN == errno) {
377
+ return false;
378
+ }
379
+ log_cat(&c->server->error_cat, "Socket error @ %llu.", c->iid);
380
+
381
+ return true;
382
+ }
383
+ c->wcnt += cnt;
384
+ if (c->wcnt == message->len) { // finished
385
+ Res res = c->res_head;
386
+ bool done = res->close;
387
+
388
+ c->res_head = res->next;
389
+ if (res == c->res_tail) {
390
+ c->res_tail = NULL;
391
+ }
392
+ c->wcnt = 0;
393
+ res_destroy(res);
394
+
395
+ return done;
396
+ }
397
+ return false;
398
+ }
399
+
400
+ void*
401
+ con_loop(void *x) {
402
+ Server server = (Server)x;
403
+ Con c;
404
+ Con ca[MAX_SOCK];
405
+ struct pollfd pa[MAX_SOCK];
406
+ struct pollfd *pp;
407
+ Con *end = ca + MAX_SOCK;
408
+ Con *cp;
409
+ int ccnt = 0;
410
+ int i;
411
+ long mcnt = 0;
412
+ double now;
413
+
414
+ atomic_fetch_add(&server->running, 1);
415
+ memset(ca, 0, sizeof(ca));
416
+ memset(pa, 0, sizeof(pa));
417
+ while (server->active) {
418
+ while (NULL != (c = (Con)queue_pop(&server->con_queue, 0.0))) {
419
+ mcnt++;
420
+ ca[c->sock] = c;
421
+ ccnt++;
422
+ }
423
+ pp = pa;
424
+ pp->fd = queue_listen(&server->con_queue);
425
+ pp->events = POLLIN;
426
+ pp->revents = 0;
427
+ pp++;
428
+ for (i = ccnt, cp = ca; 0 < i && cp < end; cp++) {
429
+ if (NULL == *cp) {
430
+ continue;
431
+ }
432
+ c = *cp;
433
+ c->pp = pp;
434
+ pp->fd = c->sock;
435
+ if (NULL != c->res_head && NULL != res_message(c->res_head)) {
436
+ pp->events = POLLIN | POLLOUT;
437
+ } else {
438
+ pp->events = POLLIN;
439
+ }
440
+ pp->revents = 0;
441
+ i--;
442
+ pp++;
443
+ }
444
+ if (0 > (i = poll(pa, pp - pa, 100))) {
445
+ if (EAGAIN == errno) {
446
+ continue;
447
+ }
448
+ log_cat(&server->error_cat, "Polling error. %s.", strerror(errno));
449
+ // Either a signal or something bad like out of memory. Might as well exit.
450
+ break;
451
+ }
452
+ now = dtime();
453
+
454
+ if (0 == i) { // nothing to read or write
455
+ // TBD check for cons to close
456
+ continue;
457
+ }
458
+ if (0 != (pa->revents & POLLIN)) {
459
+ queue_release(&server->con_queue);
460
+ while (NULL != (c = (Con)queue_pop(&server->con_queue, 0.0))) {
461
+ mcnt++;
462
+ ca[c->sock] = c;
463
+ ccnt++;
464
+ }
465
+ }
466
+ for (i = ccnt, cp = ca; 0 < i && cp < end; cp++) {
467
+ if (NULL == *cp || 0 == (*cp)->sock || NULL == (*cp)->pp) {
468
+ continue;
469
+ }
470
+ c = *cp;
471
+ i--;
472
+ pp = c->pp;
473
+ if (0 != (pp->revents & POLLIN)) {
474
+ if (con_read(c)) {
475
+ goto CON_RM;
476
+ }
477
+ }
478
+ if (0 != (pp->revents & POLLOUT)) {
479
+ if (con_write(c)) {
480
+ goto CON_RM;
481
+ }
482
+ }
483
+ if (0 != (pp->revents & (POLLERR | POLLHUP | POLLNVAL))) {
484
+ if (0 < c->bcnt) {
485
+ if (0 != (pp->revents & (POLLHUP | POLLNVAL))) {
486
+ log_cat(&server->error_cat, "Socket %llu closed.", c->iid);
487
+ } else if (!c->closing) {
488
+ log_cat(&server->error_cat, "Socket %llu error. %s", c->iid, strerror(errno));
489
+ }
490
+ }
491
+ goto CON_RM;
492
+ }
493
+ if (0.0 == c->timeout || now < c->timeout) {
494
+ continue;
495
+ } else if (c->closing) {
496
+ goto CON_RM;
497
+ } else {
498
+ c->closing = true;
499
+ c->timeout = now + 0.5;
500
+ //wush_text_set(&c->resp, (char*)close_resp, sizeof(close_resp) - 1, false);
501
+ continue;
502
+ }
503
+ continue;
504
+ CON_RM:
505
+ ca[c->sock] = NULL;
506
+ ccnt--;
507
+ log_cat(&server->con_cat, "Connection %llu closed.", c->iid);
508
+ con_destroy(c);
509
+ }
510
+ }
511
+ atomic_fetch_sub(&server->running, 1);
512
+
513
+ return NULL;
514
+ }
515
+