macaw_framework 1.1.0 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -2
- data/README.md +55 -8
- data/SECURITY.md +3 -4
- data/lib/macaw_framework/core/{server.rb → common/server_base.rb} +26 -130
- data/lib/macaw_framework/core/cron_runner.rb +50 -0
- data/lib/macaw_framework/core/thread_server.rb +122 -0
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +44 -8
- data/sig/cron_runner.rbs +6 -0
- data/sig/macaw_framework/macaw.rbs +4 -0
- metadata +6 -4
- data/macaw_logo.png +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 684896888c97d23fc4763ddbf106a7248ca22acedb7550371cdc912a34e4e9ef
|
4
|
+
data.tar.gz: 8188e16ee9193d334360de46c66e7fadbb054e1ffd10183d8967cbc2fb1b763f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3de5c64a50e8cb95eff2d9d13cbae3a5d24d60f42c4be2ccea7459bf28aaa0ed80d2aaa2a47a64e69fb4eacc8d354d78b38505c9e31b176481ba96aca3b4af7
|
7
|
+
data.tar.gz: 148d541c0ba4d2c6790e939da810e02dd405dd8bc056e8af38ef90bb8f7832b9a366933b3d5fce14df03bc9516d52b41f6a4294f47d8133d2dba1740b32ae0d9
|
data/CHANGELOG.md
CHANGED
@@ -68,9 +68,20 @@
|
|
68
68
|
|
69
69
|
- Fixing critical bug where threads were being killed and not respawning after abrupt client connection shutdown
|
70
70
|
|
71
|
-
## [1.1.0] - 2023-
|
71
|
+
## [1.1.0] - 2023-05-20
|
72
72
|
|
73
73
|
- Adding support for other SSL/TSL keys other than RSA
|
74
74
|
- New mechanism to handle server shutdown properly
|
75
75
|
- Improving log readability
|
76
|
-
- Automatic logging is now optional
|
76
|
+
- Automatic logging is now optional
|
77
|
+
|
78
|
+
## [1.1.1] - 2023-05-28
|
79
|
+
|
80
|
+
- Adding native cron jobs
|
81
|
+
- Documentation improvement
|
82
|
+
|
83
|
+
## [1.1.2] - 2023-05-31
|
84
|
+
|
85
|
+
- Fixing retry bug in cron jobs, where retries were made after an exception without waiting for interval
|
86
|
+
- Fixing another bug in cron jobs where an exception were thrown when start_delay were not set
|
87
|
+
- Documentation improvement
|
data/README.md
CHANGED
@@ -4,6 +4,22 @@ MacawFramework is a lightweight, easy-to-use web framework for Ruby designed to
|
|
4
4
|
medium-sized web applications. With support for various HTTP methods, caching, and session management, MacawFramework
|
5
5
|
provides developers with the essential tools to quickly build and deploy their applications.
|
6
6
|
|
7
|
+
- [MacawFramework](#macawframework)
|
8
|
+
* [Features](#features)
|
9
|
+
* [Installation](#installation)
|
10
|
+
* [Compatibility](#compatibility)
|
11
|
+
* [Usage](#usage)
|
12
|
+
+ [Basic routing: Define routes with support for GET, POST, PUT, PATCH, and DELETE HTTP methods](#basic-routing-define-routes-with-support-for-get-post-put-patch-and-delete-http-methods)
|
13
|
+
+ [Caching: Improve performance by caching responses and configuring cache invalidation](#caching-improve-performance-by-caching-responses-and-configuring-cache-invalidation)
|
14
|
+
+ [Session management: Handle user sessions securely with server-side in-memory storage](#session-management-handle-user-sessions-securely-with-server-side-in-memory-storage)
|
15
|
+
+ [Configuration: Customize various aspects of the framework through the application.json configuration file, such as rate limiting, SSL support, and Prometheus integration](#configuration-customize-various-aspects-of-the-framework-through-the-applicationjson-configuration-file-such-as-rate-limiting-ssl-support-and-prometheus-integration)
|
16
|
+
+ [Monitoring: Easily monitor your application performance and metrics with built-in Prometheus support](#monitoring-easily-monitor-your-application-performance-and-metrics-with-built-in-prometheus-support)
|
17
|
+
+ [Cron Jobs](#cron-jobs)
|
18
|
+
+ [Tips](#tips)
|
19
|
+
* [Contributing](#contributing)
|
20
|
+
* [License](#license)
|
21
|
+
* [Code of Conduct](#code-of-conduct)
|
22
|
+
|
7
23
|
## Features
|
8
24
|
|
9
25
|
- Simple routing with support for GET, POST, PUT, PATCH, and DELETE HTTP methods
|
@@ -23,6 +39,16 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
23
39
|
|
24
40
|
$ gem install macaw_framework
|
25
41
|
|
42
|
+
## Compatibility
|
43
|
+
|
44
|
+
MacawFramework is built to be highly compatible, since it uses only native Ruby code:
|
45
|
+
|
46
|
+
- **MRI**: MacawFramework is compatible with Matz's Ruby Interpreter (MRI), version 2.7.0 and onwards. If you are using this version or a more recent one, you should not encounter any compatibility issues.
|
47
|
+
|
48
|
+
- **TruffleRuby**: TruffleRuby is another Ruby interpreter that is fully compatible with MacawFramework. This provides developers with more flexibility in their choice of Ruby interpreter.
|
49
|
+
|
50
|
+
- **JRuby**: MacawFramework is also compatible with JRuby, a version of Ruby that runs on the Java Virtual Machine (JVM).
|
51
|
+
|
26
52
|
## Usage
|
27
53
|
|
28
54
|
### Basic routing: Define routes with support for GET, POST, PUT, PATCH, and DELETE HTTP methods
|
@@ -122,21 +148,39 @@ end
|
|
122
148
|
curl http://localhost:8080/metrics
|
123
149
|
```
|
124
150
|
|
151
|
+
### Cron Jobs
|
152
|
+
|
153
|
+
Macaw Framework supports the declaration of cron jobs right in your application code. This feature allows developers to
|
154
|
+
define tasks that run at set intervals, starting after an optional delay. Each job runs in a separate thread, meaning
|
155
|
+
your cron jobs can execute in parallel without blocking the rest of your application.
|
156
|
+
|
157
|
+
Here's an example of how to declare a cron job:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
m.setup_job(interval: 5, start_delay: 5, job_name: "cron job 1") do
|
161
|
+
puts "i'm a cron job that runs every 5 secs!"
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
Values for interval and start_delay are in seconds.
|
166
|
+
|
167
|
+
**Caution: Defining a lot of jobs with low interval can severely degrade performance.**
|
168
|
+
|
125
169
|
### Tips
|
126
170
|
|
127
|
-
The automatic logging and log aspect are now optional. To disable them, simply start Macaw with `custom_log` set to nil.
|
171
|
+
- The automatic logging and log aspect are now optional. To disable them, simply start Macaw with `custom_log` set to nil.
|
128
172
|
|
129
173
|
```ruby
|
130
174
|
MacawFramework::Macaw.new(custom_log: nil)
|
131
175
|
```
|
132
176
|
|
133
|
-
Cache invalidation time should be specified in seconds. In order to enable caching, The application.json file
|
177
|
+
- Cache invalidation time should be specified in seconds. In order to enable caching, The application.json file
|
134
178
|
should exist in the app main directory and it need the `cache_invalidation` config set. It is possible to
|
135
179
|
provide a list of strings in the property `ignore_headers`. All the client headers with the same name of any
|
136
180
|
of the strings provided will be ignored from caching strategy. This is useful to exclude headers like
|
137
181
|
correlation IDs from the caching strategy.
|
138
182
|
|
139
|
-
URL parameters like `...endOfUrl?key1=value1&key2=value2` can be find in the `context[:params]`
|
183
|
+
- URL parameters like `...endOfUrl?key1=value1&key2=value2` can be find in the `context[:params]`
|
140
184
|
|
141
185
|
```ruby
|
142
186
|
m.get('/test_params') do |context|
|
@@ -144,22 +188,25 @@ m.get('/test_params') do |context|
|
|
144
188
|
end
|
145
189
|
```
|
146
190
|
|
147
|
-
Rate Limit window should also be specified in seconds. Rate limit will be activated only if the `rate_limiting` config
|
191
|
+
- Rate Limit window should also be specified in seconds. Rate limit will be activated only if the `rate_limiting` config
|
148
192
|
exists inside `application.json`.
|
149
193
|
|
150
|
-
If the SSL configuration is provided in the `application.json` file with valid certificate and key files, the TCP server
|
194
|
+
- If the SSL configuration is provided in the `application.json` file with valid certificate and key files, the TCP server
|
151
195
|
will be wrapped with HTTPS security using the provided certificate.
|
152
196
|
|
153
|
-
The supported values for `min` and `max` in the SSL configuration are: `SSL2`, `SSL3`, `TLS1.1`, `TLS1.2` and `TLS1.3`,
|
197
|
+
- The supported values for `min` and `max` in the SSL configuration are: `SSL2`, `SSL3`, `TLS1.1`, `TLS1.2` and `TLS1.3`,
|
154
198
|
and the supported values for `key_type` are `RSA` and `EC`.
|
155
199
|
|
156
|
-
If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
|
200
|
+
- If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
|
157
201
|
is configurable via the `application.json` file.
|
158
202
|
|
159
|
-
The verb methods must always return a string or nil (used as the response), a number corresponding to the HTTP status
|
203
|
+
- The verb methods must always return a string or nil (used as the response), a number corresponding to the HTTP status
|
160
204
|
code to be returned to the client and the response headers as a Hash or nil. If an endpoint doesn't return a value or
|
161
205
|
returns nil for body, status code and headers, a default 200 OK status will be sent as the response.
|
162
206
|
|
207
|
+
- For cron jobs without a start_delay, a value of 0 will be used. For a job without name, a unique name will be generated
|
208
|
+
for it.
|
209
|
+
|
163
210
|
## Contributing
|
164
211
|
|
165
212
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ariasdiniz/macaw_framework. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ariasdiniz/macaw_framework/blob/main/CODE_OF_CONDUCT.md).
|
data/SECURITY.md
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
We are committed to addressing security issues in a timely manner. The following versions of MacawFramework are currently supported with security updates:
|
6
6
|
|
7
7
|
| Version | Supported |
|
8
|
-
|
9
|
-
| 1.
|
10
|
-
| < 1.
|
8
|
+
|---------| ------------------ |
|
9
|
+
| 1.x.x | :white_check_mark: |
|
10
|
+
| < 1.0 | :x: |
|
11
11
|
|
12
12
|
## Reporting a Vulnerability
|
13
13
|
|
@@ -24,4 +24,3 @@ Alternatively, you can send an email to aria.diniz.dev@gmail.com with the same i
|
|
24
24
|
4. Once the issue is resolved, we will release a new version of MacawFramework with the necessary security fixes.
|
25
25
|
|
26
26
|
Please remember to follow the project's [Code of Conduct](https://github.com/ariasdiniz/macaw_framework/blob/main/CODE_OF_CONDUCT.md) when reporting security vulnerabilities.
|
27
|
-
|
@@ -1,89 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
5
|
-
require_relative "
|
6
|
-
require_relative "
|
7
|
-
require_relative "
|
8
|
-
require_relative "
|
9
|
-
require_relative "
|
10
|
-
require_relative "
|
11
|
-
require "openssl"
|
3
|
+
require_relative "../../middlewares/memory_invalidation_middleware"
|
4
|
+
require_relative "../../middlewares/rate_limiter_middleware"
|
5
|
+
require_relative "../../data_filters/response_data_filter"
|
6
|
+
require_relative "../../errors/too_many_requests_error"
|
7
|
+
require_relative "../../utils/supported_ssl_versions"
|
8
|
+
require_relative "../../aspects/prometheus_aspect"
|
9
|
+
require_relative "../../aspects/logging_aspect"
|
10
|
+
require_relative "../../aspects/cache_aspect"
|
12
11
|
|
13
12
|
##
|
14
|
-
#
|
15
|
-
#
|
16
|
-
|
13
|
+
# Base module for Server classes. It contains
|
14
|
+
# methods for client handling, error handling,
|
15
|
+
# set features and every method that is common
|
16
|
+
# for the implementations of Servers.
|
17
|
+
module ServerBase
|
17
18
|
prepend CacheAspect
|
18
19
|
prepend LoggingAspect
|
19
20
|
prepend PrometheusAspect
|
20
|
-
# rubocop:disable Metrics/ParameterLists
|
21
|
-
|
22
|
-
attr_reader :context
|
23
|
-
|
24
|
-
##
|
25
|
-
# Create a new instance of Server.
|
26
|
-
# @param {Macaw} macaw
|
27
|
-
# @param {Logger} logger
|
28
|
-
# @param {Integer} port
|
29
|
-
# @param {String} bind
|
30
|
-
# @param {Integer} num_threads
|
31
|
-
# @param {MemoryInvalidationMiddleware} cache
|
32
|
-
# @param {Prometheus::Client:Registry} prometheus
|
33
|
-
# @return {Server}
|
34
|
-
def initialize(macaw, endpoints_to_cache = nil, cache = nil, prometheus = nil, prometheus_mw = nil)
|
35
|
-
@port = macaw.port
|
36
|
-
@bind = macaw.bind
|
37
|
-
@macaw = macaw
|
38
|
-
@macaw_log = macaw.macaw_log
|
39
|
-
@num_threads = macaw.threads
|
40
|
-
@work_queue = Queue.new
|
41
|
-
ignored_headers = set_cache_ignored_h
|
42
|
-
set_features
|
43
|
-
@rate_limit ||= nil
|
44
|
-
ignored_headers ||= nil
|
45
|
-
@cache = { cache: cache, endpoints_to_cache: endpoints_to_cache || [], ignored_headers: ignored_headers }
|
46
|
-
@prometheus = prometheus
|
47
|
-
@prometheus_middleware = prometheus_mw
|
48
|
-
@workers = []
|
49
|
-
end
|
50
|
-
|
51
|
-
# rubocop:enable Metrics/ParameterLists
|
52
|
-
|
53
|
-
##
|
54
|
-
# Start running the webserver.
|
55
|
-
def run
|
56
|
-
@server = TCPServer.new(@bind, @port)
|
57
|
-
@server = OpenSSL::SSL::SSLServer.new(@server, @context) if @context
|
58
|
-
@workers_mutex = Mutex.new
|
59
|
-
@num_threads.times do
|
60
|
-
spawn_worker
|
61
|
-
end
|
62
21
|
|
63
|
-
|
64
|
-
loop do
|
65
|
-
sleep 10
|
66
|
-
maintain_worker_pool
|
67
|
-
end
|
68
|
-
end
|
22
|
+
private
|
69
23
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
24
|
+
def call_endpoint(name, client_data, client_ip)
|
25
|
+
@macaw.send(
|
26
|
+
name.to_sym,
|
27
|
+
{
|
28
|
+
headers: client_data[:headers],
|
29
|
+
body: client_data[:body],
|
30
|
+
params: client_data[:params],
|
31
|
+
client: @session[client_ip][0]
|
32
|
+
}
|
33
|
+
)
|
77
34
|
end
|
78
35
|
|
79
|
-
|
80
|
-
|
81
|
-
def close
|
82
|
-
shutdown
|
36
|
+
def get_client_data(body, headers, parameters)
|
37
|
+
{ body: body, headers: headers, params: parameters }
|
83
38
|
end
|
84
39
|
|
85
|
-
private
|
86
|
-
|
87
40
|
def handle_client(client)
|
88
41
|
path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
|
89
42
|
raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
|
@@ -178,61 +131,4 @@ class Server
|
|
178
131
|
set_session
|
179
132
|
set_ssl
|
180
133
|
end
|
181
|
-
|
182
|
-
def call_endpoint(name, client_data, client_ip)
|
183
|
-
@macaw.send(
|
184
|
-
name.to_sym,
|
185
|
-
{
|
186
|
-
headers: client_data[:headers],
|
187
|
-
body: client_data[:body],
|
188
|
-
params: client_data[:params],
|
189
|
-
client: @session[client_ip][0]
|
190
|
-
}
|
191
|
-
)
|
192
|
-
end
|
193
|
-
|
194
|
-
def get_client_data(body, headers, parameters)
|
195
|
-
{ body: body, headers: headers, params: parameters }
|
196
|
-
end
|
197
|
-
|
198
|
-
def spawn_worker
|
199
|
-
@workers_mutex.synchronize do
|
200
|
-
@workers << Thread.new do
|
201
|
-
loop do
|
202
|
-
client = @work_queue.pop
|
203
|
-
break if client == :shutdown
|
204
|
-
|
205
|
-
handle_client(client)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
def maintain_worker_pool
|
212
|
-
@workers_mutex.synchronize do
|
213
|
-
@workers.each_with_index do |worker, index|
|
214
|
-
unless worker.alive?
|
215
|
-
if @is_shutting_down
|
216
|
-
@macaw_log&.info("Worker thread #{index} finished, not respawning due to server shutdown.")
|
217
|
-
else
|
218
|
-
@macaw_log&.error("Worker thread #{index} died, respawning...")
|
219
|
-
@workers[index] = spawn_worker
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def shutdown
|
227
|
-
@is_shutting_down = true
|
228
|
-
loop do
|
229
|
-
break if @work_queue.empty?
|
230
|
-
|
231
|
-
sleep 0.1
|
232
|
-
end
|
233
|
-
|
234
|
-
@num_threads.times { @work_queue << :shutdown }
|
235
|
-
@workers.each(&:join)
|
236
|
-
@server.close
|
237
|
-
end
|
238
134
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# This module is responsible to set up a new thread
|
5
|
+
# for each cron job defined
|
6
|
+
class CronRunner
|
7
|
+
def initialize(macaw)
|
8
|
+
@logger = macaw.macaw_log
|
9
|
+
@macaw = macaw
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Will start a thread for the defined cron job
|
14
|
+
# @param {Integer} interval
|
15
|
+
# @param {Integer?} start_delay
|
16
|
+
# @param {String} job_name
|
17
|
+
# @param {Proc} block
|
18
|
+
def start_cron_job_thread(interval, start_delay, job_name, &block)
|
19
|
+
start_delay ||= 0
|
20
|
+
raise "interval can't be <= 0 and start_delay can't be < 0!" if interval <= 0 || start_delay.negative?
|
21
|
+
|
22
|
+
@logger&.info("Starting thread for job #{job_name}")
|
23
|
+
start_delay ||= 0
|
24
|
+
thread = Thread.new do
|
25
|
+
name = job_name
|
26
|
+
interval_thread = interval
|
27
|
+
unless start_delay.nil?
|
28
|
+
@logger&.info("Job #{name} scheduled with delay. Will start running in #{start_delay} seconds.")
|
29
|
+
sleep(start_delay)
|
30
|
+
end
|
31
|
+
|
32
|
+
loop do
|
33
|
+
start_time = Time.now
|
34
|
+
@logger&.info("Running job #{name}")
|
35
|
+
block.call
|
36
|
+
@logger&.info("Job #{name} executed with success. New execution in #{interval_thread} seconds.")
|
37
|
+
|
38
|
+
execution_time = Time.now - start_time
|
39
|
+
sleep_time = [interval_thread - execution_time, 0].max
|
40
|
+
sleep(sleep_time)
|
41
|
+
rescue StandardError => e
|
42
|
+
@logger&.error("Error executing cron job with name #{name}: #{e.message}")
|
43
|
+
sleep(interval)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
sleep(1)
|
47
|
+
@logger&.info("Thread for job #{job_name} started")
|
48
|
+
thread
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "common/server_base"
|
4
|
+
require "openssl"
|
5
|
+
|
6
|
+
##
|
7
|
+
# Class responsible for providing a default
|
8
|
+
# webserver with Ruby Threads. This Server is subject
|
9
|
+
# to the MRI Global Interpreter Lock, thus it will use
|
10
|
+
# only a single physical Thread. For a true multi threaded
|
11
|
+
# server, check the RactorServer.
|
12
|
+
class ThreadServer
|
13
|
+
include ServerBase
|
14
|
+
|
15
|
+
attr_reader :context
|
16
|
+
|
17
|
+
# rubocop:disable Metrics/ParameterLists
|
18
|
+
|
19
|
+
##
|
20
|
+
# Create a new instance of ThreadServer.
|
21
|
+
# @param {Macaw} macaw
|
22
|
+
# @param {Logger} logger
|
23
|
+
# @param {Integer} port
|
24
|
+
# @param {String} bind
|
25
|
+
# @param {Integer} num_threads
|
26
|
+
# @param {MemoryInvalidationMiddleware} cache
|
27
|
+
# @param {Prometheus::Client:Registry} prometheus
|
28
|
+
# @return {Server}
|
29
|
+
def initialize(macaw, endpoints_to_cache = nil, cache = nil, prometheus = nil, prometheus_mw = nil)
|
30
|
+
@port = macaw.port
|
31
|
+
@bind = macaw.bind
|
32
|
+
@macaw = macaw
|
33
|
+
@macaw_log = macaw.macaw_log
|
34
|
+
@num_threads = macaw.threads
|
35
|
+
@work_queue = Queue.new
|
36
|
+
ignored_headers = set_cache_ignored_h
|
37
|
+
set_features
|
38
|
+
@rate_limit ||= nil
|
39
|
+
ignored_headers ||= nil
|
40
|
+
@cache = { cache: cache, endpoints_to_cache: endpoints_to_cache || [], ignored_headers: ignored_headers }
|
41
|
+
@prometheus = prometheus
|
42
|
+
@prometheus_middleware = prometheus_mw
|
43
|
+
@workers = []
|
44
|
+
end
|
45
|
+
|
46
|
+
# rubocop:enable Metrics/ParameterLists
|
47
|
+
|
48
|
+
##
|
49
|
+
# Start running the webserver.
|
50
|
+
def run
|
51
|
+
@server = TCPServer.new(@bind, @port)
|
52
|
+
@server = OpenSSL::SSL::SSLServer.new(@server, @context) if @context
|
53
|
+
@workers_mutex = Mutex.new
|
54
|
+
@num_threads.times do
|
55
|
+
spawn_worker
|
56
|
+
end
|
57
|
+
|
58
|
+
Thread.new do
|
59
|
+
loop do
|
60
|
+
sleep 10
|
61
|
+
maintain_worker_pool
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
loop do
|
66
|
+
@work_queue << @server.accept
|
67
|
+
rescue OpenSSL::SSL::SSLError => e
|
68
|
+
@macaw_log&.error("SSL error: #{e.message}")
|
69
|
+
rescue IOError, Errno::EBADF
|
70
|
+
break
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Method Responsible for closing the TCP server.
|
76
|
+
def close
|
77
|
+
shutdown
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def spawn_worker
|
83
|
+
@workers_mutex.synchronize do
|
84
|
+
@workers << Thread.new do
|
85
|
+
loop do
|
86
|
+
client = @work_queue.pop
|
87
|
+
break if client == :shutdown
|
88
|
+
|
89
|
+
handle_client(client)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def maintain_worker_pool
|
96
|
+
@workers_mutex.synchronize do
|
97
|
+
@workers.each_with_index do |worker, index|
|
98
|
+
unless worker.alive?
|
99
|
+
if @is_shutting_down
|
100
|
+
@macaw_log&.info("Worker thread #{index} finished, not respawning due to server shutdown.")
|
101
|
+
else
|
102
|
+
@macaw_log&.error("Worker thread #{index} died, respawning...")
|
103
|
+
@workers[index] = spawn_worker
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def shutdown
|
111
|
+
@is_shutting_down = true
|
112
|
+
loop do
|
113
|
+
break if @work_queue.empty?
|
114
|
+
|
115
|
+
sleep 0.1
|
116
|
+
end
|
117
|
+
|
118
|
+
@num_threads.times { @work_queue << :shutdown }
|
119
|
+
@workers.each(&:join)
|
120
|
+
@server.close
|
121
|
+
end
|
122
|
+
end
|
data/lib/macaw_framework.rb
CHANGED
@@ -4,9 +4,11 @@ require_relative "macaw_framework/errors/endpoint_not_mapped_error"
|
|
4
4
|
require_relative "macaw_framework/middlewares/prometheus_middleware"
|
5
5
|
require_relative "macaw_framework/data_filters/request_data_filtering"
|
6
6
|
require_relative "macaw_framework/middlewares/memory_invalidation_middleware"
|
7
|
-
require_relative "macaw_framework/core/
|
7
|
+
require_relative "macaw_framework/core/cron_runner"
|
8
|
+
require_relative "macaw_framework/core/thread_server"
|
8
9
|
require_relative "macaw_framework/version"
|
9
10
|
require "prometheus/client"
|
11
|
+
require "securerandom"
|
10
12
|
require "logger"
|
11
13
|
require "socket"
|
12
14
|
require "json"
|
@@ -18,11 +20,11 @@ module MacawFramework
|
|
18
20
|
class Macaw
|
19
21
|
##
|
20
22
|
# Array containing the routes defined in the application
|
21
|
-
attr_reader :routes, :port, :bind, :threads, :macaw_log, :config
|
23
|
+
attr_reader :routes, :port, :bind, :threads, :macaw_log, :config, :jobs
|
22
24
|
|
23
25
|
##
|
24
26
|
# @param {Logger} custom_log
|
25
|
-
def initialize(custom_log: Logger.new($stdout), server:
|
27
|
+
def initialize(custom_log: Logger.new($stdout), server: ThreadServer)
|
26
28
|
begin
|
27
29
|
@routes = []
|
28
30
|
@macaw_log ||= custom_log
|
@@ -54,7 +56,11 @@ module MacawFramework
|
|
54
56
|
# with the respective path.
|
55
57
|
# @param {String} path
|
56
58
|
# @param {Proc} block
|
57
|
-
# @
|
59
|
+
# @example
|
60
|
+
# macaw = MacawFramework::Macaw.new
|
61
|
+
# macaw.get("/hello") do |context|
|
62
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
63
|
+
# end
|
58
64
|
def get(path, cache: false, &block)
|
59
65
|
map_new_endpoint("get", cache, path, &block)
|
60
66
|
end
|
@@ -65,7 +71,10 @@ module MacawFramework
|
|
65
71
|
# @param {String} path
|
66
72
|
# @param {Boolean} cache
|
67
73
|
# @param {Proc} block
|
68
|
-
#
|
74
|
+
# macaw = MacawFramework::Macaw.new
|
75
|
+
# macaw.post("/hello") do |context|
|
76
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
77
|
+
# end
|
69
78
|
def post(path, cache: false, &block)
|
70
79
|
map_new_endpoint("post", cache, path, &block)
|
71
80
|
end
|
@@ -75,7 +84,10 @@ module MacawFramework
|
|
75
84
|
# with the respective path.
|
76
85
|
# @param {String} path
|
77
86
|
# @param {Proc} block
|
78
|
-
#
|
87
|
+
# macaw = MacawFramework::Macaw.new
|
88
|
+
# macaw.put("/hello") do |context|
|
89
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
90
|
+
# end
|
79
91
|
def put(path, cache: false, &block)
|
80
92
|
map_new_endpoint("put", cache, path, &block)
|
81
93
|
end
|
@@ -85,7 +97,10 @@ module MacawFramework
|
|
85
97
|
# with the respective path.
|
86
98
|
# @param {String} path
|
87
99
|
# @param {Proc} block
|
88
|
-
#
|
100
|
+
# macaw = MacawFramework::Macaw.new
|
101
|
+
# macaw.patch("/hello") do |context|
|
102
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
103
|
+
# end
|
89
104
|
def patch(path, cache: false, &block)
|
90
105
|
map_new_endpoint("patch", cache, path, &block)
|
91
106
|
end
|
@@ -95,11 +110,32 @@ module MacawFramework
|
|
95
110
|
# with the respective path.
|
96
111
|
# @param {String} path
|
97
112
|
# @param {Proc} block
|
98
|
-
#
|
113
|
+
# macaw = MacawFramework::Macaw.new
|
114
|
+
# macaw.delete("/hello") do |context|
|
115
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
116
|
+
# end
|
99
117
|
def delete(path, cache: false, &block)
|
100
118
|
map_new_endpoint("delete", cache, path, &block)
|
101
119
|
end
|
102
120
|
|
121
|
+
##
|
122
|
+
# Spawn and start a thread running the defined cron job.
|
123
|
+
# @param {Integer} interval
|
124
|
+
# @param {Integer?} start_delay
|
125
|
+
# @param {String} job_name
|
126
|
+
# @param {Proc} block
|
127
|
+
# @example
|
128
|
+
# macaw = MacawFramework::Macaw.new
|
129
|
+
# macaw.setup_job(interval: 60, start_delay: 60, job_name: "job 1") do
|
130
|
+
# puts "I'm a cron job that runs every minute"
|
131
|
+
# end
|
132
|
+
def setup_job(interval: 60, start_delay: 0, job_name: "job_#{SecureRandom.uuid}", &block)
|
133
|
+
@cron_runner ||= CronRunner.new(self)
|
134
|
+
@jobs ||= []
|
135
|
+
@cron_runner.start_cron_job_thread(interval, start_delay, job_name, &block)
|
136
|
+
@jobs << job_name
|
137
|
+
end
|
138
|
+
|
103
139
|
##
|
104
140
|
# Starts the web server
|
105
141
|
def start!
|
data/sig/cron_runner.rbs
ADDED
@@ -3,6 +3,7 @@ module MacawFramework
|
|
3
3
|
@bind: String
|
4
4
|
@cache: untyped
|
5
5
|
@config: Hash[String, untyped]
|
6
|
+
@cron_runner: CronRunner
|
6
7
|
@endpoints_to_cache: Array[String]
|
7
8
|
@macaw_log: Logger?
|
8
9
|
|
@@ -14,6 +15,7 @@ module MacawFramework
|
|
14
15
|
|
15
16
|
attr_reader bind: String
|
16
17
|
attr_reader config: Hash[String, untyped]
|
18
|
+
attr_reader jobs: Array[String]
|
17
19
|
attr_reader macaw_log: Logger?
|
18
20
|
attr_reader port: Integer
|
19
21
|
attr_reader routes: Array[String]
|
@@ -30,6 +32,8 @@ module MacawFramework
|
|
30
32
|
|
31
33
|
def put: -> nil
|
32
34
|
|
35
|
+
def setup_job: -> nil
|
36
|
+
|
33
37
|
def start!: -> nil
|
34
38
|
|
35
39
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: macaw_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aria Diniz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prometheus-client
|
@@ -46,7 +46,9 @@ files:
|
|
46
46
|
- lib/macaw_framework/aspects/cache_aspect.rb
|
47
47
|
- lib/macaw_framework/aspects/logging_aspect.rb
|
48
48
|
- lib/macaw_framework/aspects/prometheus_aspect.rb
|
49
|
-
- lib/macaw_framework/core/
|
49
|
+
- lib/macaw_framework/core/common/server_base.rb
|
50
|
+
- lib/macaw_framework/core/cron_runner.rb
|
51
|
+
- lib/macaw_framework/core/thread_server.rb
|
50
52
|
- lib/macaw_framework/data_filters/log_data_filter.rb
|
51
53
|
- lib/macaw_framework/data_filters/request_data_filtering.rb
|
52
54
|
- lib/macaw_framework/data_filters/response_data_filter.rb
|
@@ -58,8 +60,8 @@ files:
|
|
58
60
|
- lib/macaw_framework/utils/http_status_code.rb
|
59
61
|
- lib/macaw_framework/utils/supported_ssl_versions.rb
|
60
62
|
- lib/macaw_framework/version.rb
|
61
|
-
- macaw_logo.png
|
62
63
|
- main/CODEOWNERS
|
64
|
+
- sig/cron_runner.rbs
|
63
65
|
- sig/http_status_code.rbs
|
64
66
|
- sig/logging_aspect.rbs
|
65
67
|
- sig/macaw_framework.rbs
|
data/macaw_logo.png
DELETED
Binary file
|