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.

data/ext/agoo/server.h ADDED
@@ -0,0 +1,47 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_SERVER_H__
4
+ #define __AGOO_SERVER_H__
5
+
6
+ #include <pthread.h>
7
+ #include <stdbool.h>
8
+ #include <stdatomic.h>
9
+
10
+ #include <ruby.h>
11
+
12
+ #include "hook.h"
13
+ #include "log.h"
14
+ #include "page.h"
15
+ #include "queue.h"
16
+
17
+ typedef struct _Server {
18
+ volatile bool active;
19
+ int thread_cnt;
20
+ int port;
21
+ bool pedantic;
22
+ char *root;
23
+ atomic_int running;
24
+ pthread_t listen_thread;
25
+ pthread_t con_thread;
26
+ struct _Log log;
27
+ struct _LogCat error_cat;
28
+ struct _LogCat warn_cat;
29
+ struct _LogCat info_cat;
30
+ struct _LogCat debug_cat;
31
+ struct _LogCat con_cat;
32
+ struct _LogCat req_cat;
33
+ struct _LogCat resp_cat;
34
+ struct _LogCat eval_cat;
35
+
36
+ struct _Queue con_queue;
37
+ struct _Cache pages;
38
+
39
+ Hook hooks;
40
+ struct _Queue eval_queue;
41
+
42
+ VALUE *eval_threads; // Qnil terminated
43
+ } *Server;
44
+
45
+ extern void server_init(VALUE mod);
46
+
47
+ #endif // __AGOO_SERVER_H__
data/ext/agoo/text.c ADDED
@@ -0,0 +1,66 @@
1
+ // Copyright 2016, 2018 by Peter Ohler, All Rights Reserved
2
+
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+
6
+ #include "text.h"
7
+
8
+ Text
9
+ text_create(const char *str, int len) {
10
+ Text t = (Text)malloc(sizeof(struct _Text) - TEXT_MIN_SIZE + len + 1);
11
+
12
+ if (NULL != t) {
13
+ t->len = len;
14
+ t->alen = len;
15
+ atomic_init(&t->ref_cnt, 0);
16
+ memcpy(t->text, str, len);
17
+ t->text[len] = '\0';
18
+ }
19
+ return t;
20
+ }
21
+
22
+ Text
23
+ text_allocate(int len) {
24
+ Text t = (Text)malloc(sizeof(struct _Text) - TEXT_MIN_SIZE + len + 1);
25
+
26
+ if (NULL != t) {
27
+ t->len = 0;
28
+ t->alen = len;
29
+ atomic_init(&t->ref_cnt, 0);
30
+ *t->text = '\0';
31
+ }
32
+ return t;
33
+ }
34
+
35
+ void
36
+ text_ref(Text t) {
37
+ atomic_fetch_add(&t->ref_cnt, 1);
38
+ }
39
+
40
+ void
41
+ text_release(Text t) {
42
+ if (1 >= atomic_fetch_sub(&t->ref_cnt, 1)) {
43
+ free(t);
44
+ }
45
+ }
46
+
47
+ Text
48
+ text_append(Text t, const char *s, int len) {
49
+ if (0 >= len) {
50
+ len = strlen(s);
51
+ }
52
+ if (t->alen <= t->len + len) {
53
+ long new_len = t->alen + t->alen / 2;
54
+ size_t size = sizeof(struct _Text) - TEXT_MIN_SIZE + new_len + 1;
55
+
56
+ if (NULL == (t = (Text)realloc(t, size))) {
57
+ return NULL;
58
+ }
59
+ t->alen = new_len;
60
+ }
61
+ memcpy(t->text + t->len, s, len);
62
+ t->len += len;
63
+ t->text[t->len] = '\0';
64
+
65
+ return t;
66
+ }
data/ext/agoo/text.h ADDED
@@ -0,0 +1,24 @@
1
+ // Copyright 2016, 2018 by Peter Ohler, All Rights Reserved
2
+
3
+ #ifndef __AGOO_TEXT_H__
4
+ #define __AGOO_TEXT_H__
5
+
6
+ #include <stdatomic.h>
7
+ #include <stdbool.h>
8
+
9
+ #define TEXT_MIN_SIZE 8
10
+
11
+ typedef struct _Text {
12
+ long len; // length of valid text
13
+ long alen; // size of allocated text
14
+ atomic_int ref_cnt;
15
+ char text[TEXT_MIN_SIZE];
16
+ } *Text;
17
+
18
+ extern Text text_create(const char *str, int len);
19
+ extern Text text_allocate(int len);
20
+ extern void text_ref(Text t);
21
+ extern void text_release(Text t);
22
+ extern Text text_append(Text t, const char *s, int len);
23
+
24
+ #endif /* __AGOO_TEXT_H__ */
data/ext/agoo/types.h ADDED
@@ -0,0 +1,18 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_TYPES_H__
4
+ #define __AGOO_TYPES_H__
5
+
6
+ typedef enum {
7
+ CONNECT = 'C',
8
+ DELETE = 'D',
9
+ GET = 'G',
10
+ HEAD = 'H',
11
+ OPTIONS = 'O',
12
+ POST = 'P',
13
+ PUT = 'U',
14
+ ALL = 'A',
15
+ NONE = '\0',
16
+ } Method;
17
+
18
+ #endif // __AGOO_TYPES_H__
data/lib/agoo.rb ADDED
@@ -0,0 +1,9 @@
1
+
2
+ # Agoo is the module that includes an HTTP server. The word agoo is a Japanese
3
+ # word for a type of flying fish.
4
+ module Agoo
5
+ end
6
+
7
+ require 'agoo/version'
8
+
9
+ require 'agoo/agoo' # C extension
@@ -0,0 +1,5 @@
1
+
2
+ module Agoo
3
+ # Agoo version.
4
+ VERSION = '0.9.0'
5
+ end
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.dirname(__FILE__)
4
+ $root_dir = File.dirname(File.expand_path(File.dirname(__FILE__)))
5
+ %w(lib ext).each do |dir|
6
+ $: << File.join($root_dir, dir)
7
+ end
8
+
9
+ require 'minitest'
10
+ require 'minitest/autorun'
11
+ require 'net/http'
12
+
13
+ require 'oj'
14
+
15
+ require 'agoo'
16
+
17
+ class BaseHandlerTest < Minitest::Test
18
+
19
+ class TellMeHandler
20
+ def initialize
21
+ end
22
+
23
+ def on_request(req, res)
24
+ if 'GET' == req.request_method
25
+ res.body = Oj.dump(req.to_h, mode: :null)
26
+ elsif 'POST' == req.request_method
27
+ res.code = 204
28
+ elsif 'PUT' == req.request_method
29
+ res.code = 201
30
+ res.body = req.body
31
+ end
32
+ end
33
+ end
34
+
35
+ class WildHandler
36
+ def initialize(name)
37
+ @name = name
38
+ end
39
+
40
+ def on_request(req, res)
41
+ res.body = "#{@name} - #{req.script_name}"
42
+ end
43
+ end
44
+
45
+ def test_base_handler
46
+ begin
47
+ server = Agoo::Server.new(6464, 'root',
48
+ pedantic: false,
49
+ log_dir: '',
50
+ thread_count: 1,
51
+ log_console: true,
52
+ log_classic: true,
53
+ log_colorize: true,
54
+ log_states: {
55
+ INFO: false,
56
+ DEBUG: false,
57
+ connect: false,
58
+ request: false,
59
+ response: false,
60
+ eval: true,
61
+ })
62
+ handler = TellMeHandler.new
63
+ server.handle(:GET, "/tellme", handler)
64
+ server.handle(:POST, "/makeme", handler)
65
+ server.handle(:PUT, "/makeme", handler)
66
+ server.handle(:GET, "/wild/*/one", WildHandler.new('one'))
67
+ server.handle(:GET, "/wild/all/**", WildHandler.new('all'))
68
+ server.start()
69
+
70
+ #sleep(100)
71
+ eval_test
72
+ post_test
73
+ put_test
74
+ wild_one_test
75
+ wild_all_test
76
+ ensure
77
+ server.shutdown
78
+ end
79
+ end
80
+
81
+ def eval_test
82
+ uri = URI('http://localhost:6464/tellme?a=1')
83
+ req = Net::HTTP::Get.new(uri)
84
+ # Set the headers the way we want them.
85
+ req['Accept-Encoding'] = '*'
86
+ req['Accept'] = 'application/json'
87
+ req['User-Agent'] = 'Ruby'
88
+
89
+ res = Net::HTTP.start(uri.hostname, uri.port) { |h|
90
+ h.request(req)
91
+ }
92
+ content = res.body
93
+ obj = Oj.load(content, mode: :strict)
94
+
95
+ expect = {
96
+ "HTTP_Accept" => "application/json",
97
+ "HTTP_Accept-Encoding" => "*",
98
+ "HTTP_User-Agent" => "Ruby",
99
+ "PATH_INFO" => "",
100
+ "QUERY_STRING" => "a=1",
101
+ "REQUEST_METHOD" => "GET",
102
+ "SCRIPT_NAME" => "/tellme",
103
+ "SERVER_NAME" => "localhost",
104
+ "SERVER_PORT" => "6464",
105
+ "rack.errors" => nil,
106
+ "rack.input" => nil,
107
+ "rack.multiprocess" => false,
108
+ "rack.multithread" => false,
109
+ "rack.run_once" => false,
110
+ "rack.url_scheme" => "http",
111
+ "rack.version" => "2.0.3",
112
+ }
113
+ expect.each_pair { |k,v|
114
+ if v.nil?
115
+ assert_nil(obj[k], k)
116
+ else
117
+ assert_equal(v, obj[k], k)
118
+ end
119
+ }
120
+ end
121
+
122
+ def post_test
123
+ uri = URI('http://localhost:6464/makeme')
124
+ req = Net::HTTP::Post.new(uri)
125
+ # Set the headers the way we want them.
126
+ req['Accept-Encoding'] = '*'
127
+ req['Accept'] = 'application/json'
128
+ req['User-Agent'] = 'Ruby'
129
+
130
+ res = Net::HTTP.start(uri.hostname, uri.port) { |h|
131
+ h.request(req)
132
+ }
133
+ assert_equal(Net::HTTPNoContent, res.class)
134
+ end
135
+
136
+ def put_test
137
+ uri = URI('http://localhost:6464/makeme')
138
+ req = Net::HTTP::Put.new(uri)
139
+ # Set the headers the way we want them.
140
+ req['Accept-Encoding'] = '*'
141
+ req['Accept'] = 'application/json'
142
+ req['User-Agent'] = 'Ruby'
143
+ req.body = 'hello'
144
+
145
+ res = Net::HTTP.start(uri.hostname, uri.port) { |h|
146
+ h.request(req)
147
+ }
148
+ assert_equal(Net::HTTPCreated, res.class)
149
+ assert_equal('hello', res.body)
150
+ end
151
+
152
+ def wild_one_test
153
+ uri = URI('http://localhost:6464/wild/abc/one')
154
+ req = Net::HTTP::Get.new(uri)
155
+ res = Net::HTTP.start(uri.hostname, uri.port) { |h|
156
+ h.request(req)
157
+ }
158
+ assert_equal('one - /wild/abc/one', res.body)
159
+ end
160
+
161
+ def wild_all_test
162
+ uri = URI('http://localhost:6464/wild/all/x/y')
163
+ req = Net::HTTP::Get.new(uri)
164
+ res = Net::HTTP.start(uri.hostname, uri.port) { |h|
165
+ h.request(req)
166
+ }
167
+ assert_equal('all - /wild/all/x/y', res.body)
168
+ end
169
+
170
+ end
data/test/log_test.rb ADDED
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.dirname(__FILE__)
4
+ $root_dir = File.dirname(File.expand_path(File.dirname(__FILE__)))
5
+ %w(lib ext).each do |dir|
6
+ $: << File.join($root_dir, dir)
7
+ end
8
+
9
+ require 'minitest'
10
+ require 'minitest/autorun'
11
+
12
+ require 'agoo'
13
+
14
+ class LogStateTest < Minitest::Test
15
+
16
+ def test_log_state
17
+ begin
18
+ server = Agoo::Server.new(6464, '.')
19
+
20
+ error_state_test(server)
21
+ warn_state_test(server)
22
+ info_state_test(server)
23
+ debug_state_test(server)
24
+ request_state_test(server)
25
+ response_state_test(server)
26
+ eval_state_test(server)
27
+ ensure
28
+ server.shutdown
29
+ end
30
+ end
31
+
32
+ def error_state_test(server)
33
+ assert(server.error?)
34
+ server.set_log_state('ERROR', false)
35
+ refute(server.error?)
36
+ server.set_log_state('ERROR', true)
37
+ end
38
+
39
+ def warn_state_test(server)
40
+ assert(server.warn?)
41
+ server.set_log_state('WARN', false)
42
+ refute(server.warn?)
43
+ server.set_log_state('WARN', true)
44
+ end
45
+
46
+ def info_state_test(server)
47
+ refute(server.info?)
48
+ server.set_log_state('INFO', true)
49
+ assert(server.info?)
50
+ end
51
+
52
+ def debug_state_test(server)
53
+ refute(server.debug?)
54
+ server.set_log_state('DEBUG', true)
55
+ assert(server.debug?)
56
+ server.set_log_state('DEBUG', false)
57
+ end
58
+
59
+ def request_state_test(server)
60
+ refute(server.log_state('request'))
61
+ server.set_log_state('request', true)
62
+ assert(server.log_state('request'))
63
+ server.set_log_state('request', false)
64
+ end
65
+
66
+ def response_state_test(server)
67
+ refute(server.log_state('response'))
68
+ server.set_log_state('response', true)
69
+ assert(server.log_state('response'))
70
+ server.set_log_state('response', false)
71
+ end
72
+
73
+ def eval_state_test(server)
74
+ refute(server.log_state('eval'))
75
+ server.set_log_state('eval', true)
76
+ assert(server.log_state('eval'))
77
+ server.set_log_state('eval', false)
78
+ end
79
+
80
+ def unknown_state_test(server)
81
+ assert_raises(ArgumentError) {
82
+ server.log_state('unknown')
83
+ }
84
+ assert_raises(ArgumentError) {
85
+ server.set_log_state('unknown', true)
86
+ }
87
+ end
88
+
89
+ end
90
+
91
+ class LogClassicTest < Minitest::Test
92
+
93
+ def test_log_classic
94
+ `rm -rf log`
95
+ begin
96
+ server = Agoo::Server.new(6464, '.',
97
+ log_dir: 'log',
98
+ log_console: false,
99
+ log_classic: true,
100
+ log_colorize: false,
101
+ log_states: {
102
+ INFO: true,
103
+ DEBUG: true,
104
+ eval: true,
105
+ })
106
+ error_test(server)
107
+ warn_test(server)
108
+ info_test(server)
109
+ debug_test(server)
110
+ log_eval_test(server)
111
+ ensure
112
+ server.shutdown
113
+ end
114
+ end
115
+
116
+ def error_test(server)
117
+ server.error('my message')
118
+ server.log_flush(1.0)
119
+ content = IO.read('log/agoo.log')
120
+ assert_match(/ERROR: my message/, content)
121
+ end
122
+
123
+ def warn_test(server)
124
+ server.warn('my message')
125
+ server.log_flush(1.0)
126
+ content = IO.read('log/agoo.log')
127
+ assert_match(/WARN: my message/, content)
128
+ end
129
+
130
+ def info_test(server)
131
+ server.info('my message')
132
+ server.log_flush(1.0)
133
+ content = IO.read('log/agoo.log')
134
+ assert_match(/INFO: my message/, content)
135
+ end
136
+
137
+ def debug_test(server)
138
+ server.debug('my message')
139
+ server.log_flush(1.0)
140
+ content = IO.read('log/agoo.log')
141
+ assert_match(/DEBUG: my message/, content)
142
+ end
143
+
144
+ def log_eval_test(server)
145
+ server.log_eval('my message')
146
+ server.log_flush(1.0)
147
+ content = IO.read('log/agoo.log')
148
+ assert_match(/eval: my message/, content)
149
+ end
150
+
151
+ end
152
+
153
+ class LogJsonTest < Minitest::Test
154
+
155
+ def test_log_json
156
+ `rm -rf log`
157
+ begin
158
+ server = Agoo::Server.new(6464, '.',
159
+ log_dir: 'log',
160
+ log_max_files: 2,
161
+ log_max_size: 10,
162
+ log_console: false,
163
+ log_classic: false,
164
+ log_colorize: false,
165
+ log_states: {
166
+ INFO: true,
167
+ DEBUG: true,
168
+ eval: true,
169
+ })
170
+ error_test(server)
171
+ warn_test(server)
172
+ info_test(server)
173
+ debug_test(server)
174
+ eval_test(server)
175
+ ensure
176
+ server.shutdown
177
+ end
178
+ end
179
+
180
+ def error_test(server)
181
+ server.error('my message')
182
+ server.log_flush(1.0)
183
+ content = IO.read('log/agoo.log.1')
184
+ assert_match(/"where":"ERROR"/, content)
185
+ assert_match(/"level":1/, content)
186
+ assert_match(/"what":"my message"/, content)
187
+ end
188
+
189
+ def warn_test(server)
190
+ server.warn('my message')
191
+ server.log_flush(1.0)
192
+ content = IO.read('log/agoo.log.1')
193
+ assert_match(/"where":"WARN"/, content)
194
+ assert_match(/"level":2/, content)
195
+ assert_match(/"what":"my message"/, content)
196
+ end
197
+
198
+ def info_test(server)
199
+ server.info('my message')
200
+ server.log_flush(1.0)
201
+ content = IO.read('log/agoo.log.1')
202
+ assert_match(/"where":"INFO"/, content)
203
+ assert_match(/"level":3/, content)
204
+ assert_match(/"what":"my message"/, content)
205
+ end
206
+
207
+ def debug_test(server)
208
+ server.debug('my message')
209
+ server.log_flush(1.0)
210
+ content = IO.read('log/agoo.log.1')
211
+ assert_match(/"where":"DEBUG"/, content)
212
+ assert_match(/"level":4/, content)
213
+ assert_match(/"what":"my message"/, content)
214
+ end
215
+
216
+ def eval_test(server)
217
+ server.log_eval('my message')
218
+ server.log_flush(1.0)
219
+ content = IO.read('log/agoo.log.1')
220
+ assert_match(/"where":"eval"/, content)
221
+ assert_match(/"level":3/, content)
222
+ assert_match(/"what":"my message"/, content)
223
+ end
224
+
225
+ end
226
+
227
+ class LogRollTest < Minitest::Test
228
+
229
+ def setup
230
+ `rm -rf log`
231
+ begin
232
+ server = Agoo::Server.new(6464, '.',
233
+ log_dir: 'log',
234
+ log_max_files: 2,
235
+ log_max_size: 150,
236
+ log_console: false,
237
+ log_classic: true,
238
+ log_colorize: false,
239
+ log_states: {
240
+ INFO: true,
241
+ DEBUG: true,
242
+ eval: true,
243
+ })
244
+ 10.times { |i|
245
+ @server.info("my #{i} message")
246
+ }
247
+ @server.log_flush(1.0)
248
+ content = IO.read('log/agoo.log')
249
+ assert_match(/INFO: my 9 message/, content)
250
+
251
+ content = IO.read('log/agoo.log.1')
252
+ assert_match(/INFO: my 6 message/, content)
253
+ assert_match(/INFO: my 7 message/, content)
254
+ assert_match(/INFO: my 8 message/, content)
255
+
256
+ content = IO.read('log/agoo.log.2')
257
+ assert_match(/INFO: my 3 message/, content)
258
+ assert_match(/INFO: my 4 message/, content)
259
+ assert_match(/INFO: my 5 message/, content)
260
+
261
+ assert_raises() {
262
+ IO.read('log/agoo.log.3')
263
+ }
264
+ ensure
265
+ @server.shutdown
266
+ end
267
+ end
268
+
269
+ end