macaw_framework 1.3.22 → 1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +4 -1
- data/Gemfile +2 -0
- data/lib/macaw_framework/cache.rb +91 -0
- data/lib/macaw_framework/macaw.rb +264 -0
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +5 -345
- metadata +5 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c18584ede9872a77b3976bd6685f99e4fb015082647e0dde824da268ce44b889
|
|
4
|
+
data.tar.gz: c36bf7deee533993408493517e2d8d46dd1a763276dafd3a8388b4a773c48af4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 315cb13a54c7cdc82444747b1dd32da4a50dce5de756abd52a05a244eb2fe94b77bbe28aa0e56229a7a437d7be308118f67990779ad011fd965dec5001b31703
|
|
7
|
+
data.tar.gz: f8be92b1eb37fb9644f6bf8cb84ad02fa2ad0ba0da3244c888a5fd8b48e5adbfcda5a263f3bda73f29dfec6dd729bc5d72cbaa024e0fbe274aa988e5a025deaf
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -152,5 +152,8 @@
|
|
|
152
152
|
- Fixing a bug where a HTTP call without client data broke the parser
|
|
153
153
|
- Removing logs registering new HTTP connections to reduce log bloat
|
|
154
154
|
|
|
155
|
-
## [1.3.
|
|
155
|
+
## [1.3.22]
|
|
156
156
|
- Fixing error with tests on Ruby 3.4.x due to splash operator
|
|
157
|
+
|
|
158
|
+
## [1.4]
|
|
159
|
+
- Add missing dependencies for Ruby 4.x
|
data/Gemfile
CHANGED
|
@@ -4,11 +4,13 @@ source 'https://rubygems.org'
|
|
|
4
4
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
|
+
gem 'logger', '~> 1.7'
|
|
7
8
|
gem 'openssl'
|
|
8
9
|
gem 'prometheus-client', '~> 4.1'
|
|
9
10
|
|
|
10
11
|
group :test do
|
|
11
12
|
gem 'minitest', '~> 5.0'
|
|
13
|
+
gem 'ostruct', '~> 0.6.3'
|
|
12
14
|
gem 'rake', '~> 13.0'
|
|
13
15
|
gem 'rubocop', '~> 1.21'
|
|
14
16
|
gem 'simplecov', '~> 0.21.2'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Main module for all Macaw classes
|
|
5
|
+
module MacawFramework; end
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# This singleton class allows to manually cache
|
|
9
|
+
# parameters and other data.
|
|
10
|
+
class MacawFramework::Cache
|
|
11
|
+
include Singleton
|
|
12
|
+
|
|
13
|
+
attr_accessor :invalidation_frequency
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Write a value to Cache memory.
|
|
17
|
+
# Can be called statically or from an instance.
|
|
18
|
+
# @param {String} tag
|
|
19
|
+
# @param {Object} value
|
|
20
|
+
# @param {Integer} expires_in Defaults to 3600.
|
|
21
|
+
# @return nil
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# MacawFramework::Cache.write("name", "Maria", expires_in: 7200)
|
|
25
|
+
def self.write(tag, value, expires_in: 3600)
|
|
26
|
+
MacawFramework::Cache.instance.write(tag, value, expires_in: expires_in)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Write a value to Cache memory.
|
|
31
|
+
# Can be called statically or from an instance.
|
|
32
|
+
# @param {String} tag
|
|
33
|
+
# @param {Object} value
|
|
34
|
+
# @param {Integer} expires_in Defaults to 3600.
|
|
35
|
+
# @return nil
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# MacawFramework::Cache.write("name", "Maria", expires_in: 7200)
|
|
39
|
+
def write(tag, value, expires_in: 3600)
|
|
40
|
+
if read(tag).nil?
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
@cache.store(tag, { value: value, expires_in: Time.now + expires_in })
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
@cache[tag][:value] = value
|
|
46
|
+
@cache[tag][:expires_in] = Time.now + expires_in
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Read the value with the specified tag.
|
|
52
|
+
# Can be called statically or from an instance.
|
|
53
|
+
# @param {String} tag
|
|
54
|
+
# @return {String|nil}
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# MacawFramework::Cache.read("name") # Maria
|
|
58
|
+
def self.read(tag) = MacawFramework::Cache.instance.read(tag)
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Read the value with the specified tag.
|
|
62
|
+
# Can be called statically or from an instance.
|
|
63
|
+
# @param {String} tag
|
|
64
|
+
# @return {String|nil}
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# MacawFramework::Cache.read("name") # Maria
|
|
68
|
+
def read(tag) = @cache.dig(tag, :value)
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def initialize
|
|
73
|
+
@cache = {}
|
|
74
|
+
@mutex = Mutex.new
|
|
75
|
+
@invalidation_frequency = 60
|
|
76
|
+
invalidate_cache
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def invalidate_cache
|
|
80
|
+
@invalidator = Thread.new(&method(:invalidation_process))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def invalidation_process
|
|
84
|
+
loop do
|
|
85
|
+
sleep @invalidation_frequency
|
|
86
|
+
@mutex.synchronize do
|
|
87
|
+
@cache.delete_if { |_, v| v[:expires_in] < Time.now }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors/endpoint_not_mapped_error'
|
|
4
|
+
require_relative 'middlewares/prometheus_middleware'
|
|
5
|
+
require_relative 'data_filters/request_data_filtering'
|
|
6
|
+
require_relative 'middlewares/memory_invalidation_middleware'
|
|
7
|
+
require_relative 'core/cron_runner'
|
|
8
|
+
require_relative 'core/thread_server'
|
|
9
|
+
require_relative 'version'
|
|
10
|
+
require 'prometheus/client'
|
|
11
|
+
require 'securerandom'
|
|
12
|
+
require 'singleton'
|
|
13
|
+
require 'pathname'
|
|
14
|
+
require 'logger'
|
|
15
|
+
require 'socket'
|
|
16
|
+
require 'json'
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Main module for all Macaw classes
|
|
20
|
+
module MacawFramework; end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Class responsible for creating endpoints and
|
|
24
|
+
# starting the web server.
|
|
25
|
+
class MacawFramework::Macaw
|
|
26
|
+
attr_reader :routes, :macaw_log, :config, :jobs, :cached_methods, :secure_header, :session
|
|
27
|
+
attr_accessor :port, :bind, :threads
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Initialize Macaw Class
|
|
31
|
+
# @param {Logger} custom_log
|
|
32
|
+
# @param {ThreadServer} server
|
|
33
|
+
# @param {String?} dir
|
|
34
|
+
def initialize(custom_log: Logger.new($stdout), server: ThreadServer, dir: nil)
|
|
35
|
+
apply_options(custom_log)
|
|
36
|
+
create_endpoint_public_files(dir)
|
|
37
|
+
setup_default_configs
|
|
38
|
+
@server_class = server
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Creates a GET endpoint associated
|
|
43
|
+
# with the respective path.
|
|
44
|
+
# @param {String} path
|
|
45
|
+
# @param {Proc} block
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# macaw = MacawFramework::Macaw.new
|
|
49
|
+
# macaw.get("/hello") do |context|
|
|
50
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
51
|
+
# end
|
|
52
|
+
##
|
|
53
|
+
def get(path, cache: [], &block)
|
|
54
|
+
map_new_endpoint('get', cache, path, &block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Creates a POST endpoint associated
|
|
59
|
+
# with the respective path.
|
|
60
|
+
# @param {String} path
|
|
61
|
+
# @param {Boolean} cache
|
|
62
|
+
# @param {Proc} block
|
|
63
|
+
# @example
|
|
64
|
+
#
|
|
65
|
+
# macaw = MacawFramework::Macaw.new
|
|
66
|
+
# macaw.post("/hello") do |context|
|
|
67
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
68
|
+
# end
|
|
69
|
+
##
|
|
70
|
+
def post(path, cache: [], &block)
|
|
71
|
+
map_new_endpoint('post', cache, path, &block)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Creates a PUT endpoint associated
|
|
76
|
+
# with the respective path.
|
|
77
|
+
# @param {String} path
|
|
78
|
+
# @param {Proc} block
|
|
79
|
+
# @example
|
|
80
|
+
#
|
|
81
|
+
# macaw = MacawFramework::Macaw.new
|
|
82
|
+
# macaw.put("/hello") do |context|
|
|
83
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
84
|
+
# end
|
|
85
|
+
##
|
|
86
|
+
def put(path, cache: [], &block)
|
|
87
|
+
map_new_endpoint('put', cache, path, &block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Creates a PATCH endpoint associated
|
|
92
|
+
# with the respective path.
|
|
93
|
+
# @param {String} path
|
|
94
|
+
# @param {Proc} block
|
|
95
|
+
# @example
|
|
96
|
+
#
|
|
97
|
+
# macaw = MacawFramework::Macaw.new
|
|
98
|
+
# macaw.patch("/hello") do |context|
|
|
99
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
100
|
+
# end
|
|
101
|
+
##
|
|
102
|
+
def patch(path, cache: [], &block)
|
|
103
|
+
map_new_endpoint('patch', cache, path, &block)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Creates a DELETE endpoint associated
|
|
108
|
+
# with the respective path.
|
|
109
|
+
# @param {String} path
|
|
110
|
+
# @param {Proc} block
|
|
111
|
+
# @example
|
|
112
|
+
#
|
|
113
|
+
# macaw = MacawFramework::Macaw.new
|
|
114
|
+
# macaw.delete("/hello") do |context|
|
|
115
|
+
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
116
|
+
# end
|
|
117
|
+
##
|
|
118
|
+
def delete(path, cache: [], &block)
|
|
119
|
+
map_new_endpoint('delete', cache, path, &block)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Spawn and start a thread running the defined periodic job.
|
|
124
|
+
# @param {Integer} interval
|
|
125
|
+
# @param {Integer?} start_delay
|
|
126
|
+
# @param {String} job_name
|
|
127
|
+
# @param {Proc} block
|
|
128
|
+
# @example
|
|
129
|
+
#
|
|
130
|
+
# macaw = MacawFramework::Macaw.new
|
|
131
|
+
# macaw.setup_job(interval: 60, start_delay: 60, job_name: "job 1") do
|
|
132
|
+
# puts "I'm a periodic job that runs every minute"
|
|
133
|
+
# end
|
|
134
|
+
##
|
|
135
|
+
def setup_job(interval: 60, start_delay: 0, job_name: "job_#{SecureRandom.uuid}", &block)
|
|
136
|
+
@cron_runner ||= CronRunner.new(self)
|
|
137
|
+
@jobs ||= []
|
|
138
|
+
@cron_runner.start_cron_job_thread(interval, start_delay, job_name, &block)
|
|
139
|
+
@jobs << job_name
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
# Starts the web server
|
|
144
|
+
def start!
|
|
145
|
+
if @macaw_log.nil?
|
|
146
|
+
puts('---------------------------------')
|
|
147
|
+
puts("Starting server at port #{@port}")
|
|
148
|
+
puts("Number of threads: #{@threads}")
|
|
149
|
+
puts('---------------------------------')
|
|
150
|
+
else
|
|
151
|
+
@macaw_log.info('---------------------------------')
|
|
152
|
+
@macaw_log.info("Starting server at port #{@port}")
|
|
153
|
+
@macaw_log.info("Number of threads: #{@threads}")
|
|
154
|
+
@macaw_log.info('---------------------------------')
|
|
155
|
+
end
|
|
156
|
+
@server = @server_class.new(self, @endpoints_to_cache, @cache, @prometheus, @prometheus_middleware)
|
|
157
|
+
server_loop(@server)
|
|
158
|
+
rescue Interrupt
|
|
159
|
+
if @macaw_log.nil?
|
|
160
|
+
puts('Stopping server')
|
|
161
|
+
@server.shutdown
|
|
162
|
+
puts('Macaw stop flying for some seeds...')
|
|
163
|
+
else
|
|
164
|
+
@macaw_log.info('Stopping server')
|
|
165
|
+
@server.shutdown
|
|
166
|
+
@macaw_log.info('Macaw stop flying for some seeds...')
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
##
|
|
171
|
+
# This method is intended to start the framework
|
|
172
|
+
# without an web server. This can be useful when
|
|
173
|
+
# you just want to keep cron jobs running, without
|
|
174
|
+
# mapping any HTTP endpoints.
|
|
175
|
+
def start_without_server!
|
|
176
|
+
@macaw_log.nil? ? puts('Application starting') : @macaw_log.info('Application starting')
|
|
177
|
+
loop { sleep(3600) }
|
|
178
|
+
rescue Interrupt
|
|
179
|
+
@macaw_log.nil? ? puts('Macaw stop flying for some seeds.') : @macaw_log.info('Macaw stop flying for some seeds.')
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def setup_default_configs
|
|
185
|
+
@port ||= 8080
|
|
186
|
+
@bind ||= 'localhost'
|
|
187
|
+
@config ||= nil
|
|
188
|
+
@threads ||= 200
|
|
189
|
+
@endpoints_to_cache = []
|
|
190
|
+
@prometheus ||= nil
|
|
191
|
+
@prometheus_middleware ||= nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def apply_options(custom_log)
|
|
195
|
+
setup_basic_config(custom_log)
|
|
196
|
+
setup_session
|
|
197
|
+
setup_cache
|
|
198
|
+
setup_prometheus
|
|
199
|
+
rescue StandardError => e
|
|
200
|
+
@macaw_log&.warn(e.message)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def setup_cache
|
|
204
|
+
return if @config['macaw']['cache'].nil?
|
|
205
|
+
|
|
206
|
+
@cache = MemoryInvalidationMiddleware.new(@config['macaw']['cache']['cache_invalidation'].to_i || 3_600)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def setup_session
|
|
210
|
+
@session = false
|
|
211
|
+
return if @config['macaw']['session'].nil?
|
|
212
|
+
|
|
213
|
+
@session = true
|
|
214
|
+
@secure_header = @config['macaw']['session']['secure_header'] || 'X-Session-ID'
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def setup_basic_config(custom_log)
|
|
218
|
+
@routes = []
|
|
219
|
+
@cached_methods = {}
|
|
220
|
+
@macaw_log ||= custom_log
|
|
221
|
+
@config = JSON.parse(File.read('application.json'))
|
|
222
|
+
@port = @config['macaw']['port'] || 8080
|
|
223
|
+
@bind = @config['macaw']['bind'] || 'localhost'
|
|
224
|
+
@threads = @config['macaw']['threads'] || 200
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def setup_prometheus
|
|
228
|
+
return unless @config['macaw']['prometheus']
|
|
229
|
+
|
|
230
|
+
@prometheus = Prometheus::Client::Registry.new
|
|
231
|
+
@prometheus_middleware = PrometheusMiddleware.new
|
|
232
|
+
@prometheus_middleware&.configure_prometheus(@prometheus, @config, self)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def server_loop(server)
|
|
236
|
+
server.run
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def map_new_endpoint(prefix, cache, path, &block)
|
|
240
|
+
@endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" unless cache.empty?
|
|
241
|
+
@cached_methods["#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}"] = cache unless cache.empty?
|
|
242
|
+
path_clean = RequestDataFiltering.extract_path(path)
|
|
243
|
+
slash = path[0] == '/' ? '' : '/'
|
|
244
|
+
@macaw_log&.info("Defining #{prefix.upcase} endpoint at #{slash}#{path}")
|
|
245
|
+
define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
|
|
246
|
+
|context = { headers: {}, body: '', params: {} }|
|
|
247
|
+
})
|
|
248
|
+
@routes << "#{prefix}.#{path_clean}"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def get_files_public_folder(dir)
|
|
252
|
+
return [] if dir.nil?
|
|
253
|
+
|
|
254
|
+
folder_path = Pathname.new(File.expand_path('public', dir))
|
|
255
|
+
file_paths = folder_path.glob('**/*').select(&:file?)
|
|
256
|
+
file_paths.map { |path| "public/#{path.relative_path_from(folder_path)}" }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def create_endpoint_public_files(dir)
|
|
260
|
+
get_files_public_folder(dir).each do |file|
|
|
261
|
+
get(file) { |_context| return File.read(file).to_s, 200, {} }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
data/lib/macaw_framework.rb
CHANGED
|
@@ -1,348 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require_relative 'macaw_framework/middlewares/memory_invalidation_middleware'
|
|
7
|
-
require_relative 'macaw_framework/core/cron_runner'
|
|
8
|
-
require_relative 'macaw_framework/core/thread_server'
|
|
9
|
-
require_relative 'macaw_framework/version'
|
|
10
|
-
require 'prometheus/client'
|
|
11
|
-
require 'securerandom'
|
|
12
|
-
require 'singleton'
|
|
13
|
-
require 'pathname'
|
|
14
|
-
require 'logger'
|
|
15
|
-
require 'socket'
|
|
16
|
-
require 'json'
|
|
3
|
+
##
|
|
4
|
+
# Main module for all Macaw classes
|
|
5
|
+
module MacawFramework; end
|
|
17
6
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# Class responsible for creating endpoints and
|
|
21
|
-
# starting the web server.
|
|
22
|
-
class Macaw
|
|
23
|
-
attr_reader :routes, :macaw_log, :config, :jobs, :cached_methods, :secure_header, :session
|
|
24
|
-
attr_accessor :port, :bind, :threads
|
|
25
|
-
|
|
26
|
-
##
|
|
27
|
-
# Initialize Macaw Class
|
|
28
|
-
# @param {Logger} custom_log
|
|
29
|
-
# @param {ThreadServer} server
|
|
30
|
-
# @param {String?} dir
|
|
31
|
-
def initialize(custom_log: Logger.new($stdout), server: ThreadServer, dir: nil)
|
|
32
|
-
apply_options(custom_log)
|
|
33
|
-
create_endpoint_public_files(dir)
|
|
34
|
-
setup_default_configs
|
|
35
|
-
@server_class = server
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
##
|
|
39
|
-
# Creates a GET endpoint associated
|
|
40
|
-
# with the respective path.
|
|
41
|
-
# @param {String} path
|
|
42
|
-
# @param {Proc} block
|
|
43
|
-
#
|
|
44
|
-
# @example
|
|
45
|
-
# macaw = MacawFramework::Macaw.new
|
|
46
|
-
# macaw.get("/hello") do |context|
|
|
47
|
-
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
48
|
-
# end
|
|
49
|
-
##
|
|
50
|
-
def get(path, cache: [], &block)
|
|
51
|
-
map_new_endpoint('get', cache, path, &block)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
##
|
|
55
|
-
# Creates a POST endpoint associated
|
|
56
|
-
# with the respective path.
|
|
57
|
-
# @param {String} path
|
|
58
|
-
# @param {Boolean} cache
|
|
59
|
-
# @param {Proc} block
|
|
60
|
-
# @example
|
|
61
|
-
#
|
|
62
|
-
# macaw = MacawFramework::Macaw.new
|
|
63
|
-
# macaw.post("/hello") do |context|
|
|
64
|
-
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
65
|
-
# end
|
|
66
|
-
##
|
|
67
|
-
def post(path, cache: [], &block)
|
|
68
|
-
map_new_endpoint('post', cache, path, &block)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
##
|
|
72
|
-
# Creates a PUT endpoint associated
|
|
73
|
-
# with the respective path.
|
|
74
|
-
# @param {String} path
|
|
75
|
-
# @param {Proc} block
|
|
76
|
-
# @example
|
|
77
|
-
#
|
|
78
|
-
# macaw = MacawFramework::Macaw.new
|
|
79
|
-
# macaw.put("/hello") do |context|
|
|
80
|
-
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
81
|
-
# end
|
|
82
|
-
##
|
|
83
|
-
def put(path, cache: [], &block)
|
|
84
|
-
map_new_endpoint('put', cache, path, &block)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
##
|
|
88
|
-
# Creates a PATCH endpoint associated
|
|
89
|
-
# with the respective path.
|
|
90
|
-
# @param {String} path
|
|
91
|
-
# @param {Proc} block
|
|
92
|
-
# @example
|
|
93
|
-
#
|
|
94
|
-
# macaw = MacawFramework::Macaw.new
|
|
95
|
-
# macaw.patch("/hello") do |context|
|
|
96
|
-
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
97
|
-
# end
|
|
98
|
-
##
|
|
99
|
-
def patch(path, cache: [], &block)
|
|
100
|
-
map_new_endpoint('patch', cache, path, &block)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
##
|
|
104
|
-
# Creates a DELETE endpoint associated
|
|
105
|
-
# with the respective path.
|
|
106
|
-
# @param {String} path
|
|
107
|
-
# @param {Proc} block
|
|
108
|
-
# @example
|
|
109
|
-
#
|
|
110
|
-
# macaw = MacawFramework::Macaw.new
|
|
111
|
-
# macaw.delete("/hello") do |context|
|
|
112
|
-
# return "Hello World!", 200, { "Content-Type" => "text/plain" }
|
|
113
|
-
# end
|
|
114
|
-
##
|
|
115
|
-
def delete(path, cache: [], &block)
|
|
116
|
-
map_new_endpoint('delete', cache, path, &block)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
##
|
|
120
|
-
# Spawn and start a thread running the defined periodic job.
|
|
121
|
-
# @param {Integer} interval
|
|
122
|
-
# @param {Integer?} start_delay
|
|
123
|
-
# @param {String} job_name
|
|
124
|
-
# @param {Proc} block
|
|
125
|
-
# @example
|
|
126
|
-
#
|
|
127
|
-
# macaw = MacawFramework::Macaw.new
|
|
128
|
-
# macaw.setup_job(interval: 60, start_delay: 60, job_name: "job 1") do
|
|
129
|
-
# puts "I'm a periodic job that runs every minute"
|
|
130
|
-
# end
|
|
131
|
-
##
|
|
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
|
-
|
|
139
|
-
##
|
|
140
|
-
# Starts the web server
|
|
141
|
-
def start!
|
|
142
|
-
if @macaw_log.nil?
|
|
143
|
-
puts('---------------------------------')
|
|
144
|
-
puts("Starting server at port #{@port}")
|
|
145
|
-
puts("Number of threads: #{@threads}")
|
|
146
|
-
puts('---------------------------------')
|
|
147
|
-
else
|
|
148
|
-
@macaw_log.info('---------------------------------')
|
|
149
|
-
@macaw_log.info("Starting server at port #{@port}")
|
|
150
|
-
@macaw_log.info("Number of threads: #{@threads}")
|
|
151
|
-
@macaw_log.info('---------------------------------')
|
|
152
|
-
end
|
|
153
|
-
@server = @server_class.new(self, @endpoints_to_cache, @cache, @prometheus, @prometheus_middleware)
|
|
154
|
-
server_loop(@server)
|
|
155
|
-
rescue Interrupt
|
|
156
|
-
if @macaw_log.nil?
|
|
157
|
-
puts('Stopping server')
|
|
158
|
-
@server.shutdown
|
|
159
|
-
puts('Macaw stop flying for some seeds...')
|
|
160
|
-
else
|
|
161
|
-
@macaw_log.info('Stopping server')
|
|
162
|
-
@server.shutdown
|
|
163
|
-
@macaw_log.info('Macaw stop flying for some seeds...')
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
##
|
|
168
|
-
# This method is intended to start the framework
|
|
169
|
-
# without an web server. This can be useful when
|
|
170
|
-
# you just want to keep cron jobs running, without
|
|
171
|
-
# mapping any HTTP endpoints.
|
|
172
|
-
def start_without_server!
|
|
173
|
-
@macaw_log.nil? ? puts('Application starting') : @macaw_log.info('Application starting')
|
|
174
|
-
loop { sleep(3600) }
|
|
175
|
-
rescue Interrupt
|
|
176
|
-
@macaw_log.nil? ? puts('Macaw stop flying for some seeds.') : @macaw_log.info('Macaw stop flying for some seeds.')
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
private
|
|
180
|
-
|
|
181
|
-
def setup_default_configs
|
|
182
|
-
@port ||= 8080
|
|
183
|
-
@bind ||= 'localhost'
|
|
184
|
-
@config ||= nil
|
|
185
|
-
@threads ||= 200
|
|
186
|
-
@endpoints_to_cache = []
|
|
187
|
-
@prometheus ||= nil
|
|
188
|
-
@prometheus_middleware ||= nil
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def apply_options(custom_log)
|
|
192
|
-
setup_basic_config(custom_log)
|
|
193
|
-
setup_session
|
|
194
|
-
setup_cache
|
|
195
|
-
setup_prometheus
|
|
196
|
-
rescue StandardError => e
|
|
197
|
-
@macaw_log&.warn(e.message)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def setup_cache
|
|
201
|
-
return if @config['macaw']['cache'].nil?
|
|
202
|
-
|
|
203
|
-
@cache = MemoryInvalidationMiddleware.new(@config['macaw']['cache']['cache_invalidation'].to_i || 3_600)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def setup_session
|
|
207
|
-
@session = false
|
|
208
|
-
return if @config['macaw']['session'].nil?
|
|
209
|
-
|
|
210
|
-
@session = true
|
|
211
|
-
@secure_header = @config['macaw']['session']['secure_header'] || 'X-Session-ID'
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def setup_basic_config(custom_log)
|
|
215
|
-
@routes = []
|
|
216
|
-
@cached_methods = {}
|
|
217
|
-
@macaw_log ||= custom_log
|
|
218
|
-
@config = JSON.parse(File.read('application.json'))
|
|
219
|
-
@port = @config['macaw']['port'] || 8080
|
|
220
|
-
@bind = @config['macaw']['bind'] || 'localhost'
|
|
221
|
-
@threads = @config['macaw']['threads'] || 200
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def setup_prometheus
|
|
225
|
-
return unless @config['macaw']['prometheus']
|
|
226
|
-
|
|
227
|
-
@prometheus = Prometheus::Client::Registry.new
|
|
228
|
-
@prometheus_middleware = PrometheusMiddleware.new
|
|
229
|
-
@prometheus_middleware&.configure_prometheus(@prometheus, @config, self)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def server_loop(server)
|
|
233
|
-
server.run
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def map_new_endpoint(prefix, cache, path, &block)
|
|
237
|
-
@endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" unless cache.empty?
|
|
238
|
-
@cached_methods["#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}"] = cache unless cache.empty?
|
|
239
|
-
path_clean = RequestDataFiltering.extract_path(path)
|
|
240
|
-
slash = path[0] == '/' ? '' : '/'
|
|
241
|
-
@macaw_log&.info("Defining #{prefix.upcase} endpoint at #{slash}#{path}")
|
|
242
|
-
define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
|
|
243
|
-
|context = { headers: {}, body: '', params: {} }|
|
|
244
|
-
})
|
|
245
|
-
@routes << "#{prefix}.#{path_clean}"
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def get_files_public_folder(dir)
|
|
249
|
-
return [] if dir.nil?
|
|
250
|
-
|
|
251
|
-
folder_path = Pathname.new(File.expand_path('public', dir))
|
|
252
|
-
file_paths = folder_path.glob('**/*').select(&:file?)
|
|
253
|
-
file_paths.map { |path| "public/#{path.relative_path_from(folder_path)}" }
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def create_endpoint_public_files(dir)
|
|
257
|
-
get_files_public_folder(dir).each do |file|
|
|
258
|
-
get(file) { |_context| return File.read(file).to_s, 200, {} }
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
##
|
|
264
|
-
# This singleton class allows to manually cache
|
|
265
|
-
# parameters and other data.
|
|
266
|
-
class Cache
|
|
267
|
-
include Singleton
|
|
268
|
-
|
|
269
|
-
attr_accessor :invalidation_frequency
|
|
270
|
-
|
|
271
|
-
##
|
|
272
|
-
# Write a value to Cache memory.
|
|
273
|
-
# Can be called statically or from an instance.
|
|
274
|
-
# @param {String} tag
|
|
275
|
-
# @param {Object} value
|
|
276
|
-
# @param {Integer} expires_in Defaults to 3600.
|
|
277
|
-
# @return nil
|
|
278
|
-
#
|
|
279
|
-
# @example
|
|
280
|
-
# MacawFramework::Cache.write("name", "Maria", expires_in: 7200)
|
|
281
|
-
def self.write(tag, value, expires_in: 3600)
|
|
282
|
-
MacawFramework::Cache.instance.write(tag, value, expires_in: expires_in)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
##
|
|
286
|
-
# Write a value to Cache memory.
|
|
287
|
-
# Can be called statically or from an instance.
|
|
288
|
-
# @param {String} tag
|
|
289
|
-
# @param {Object} value
|
|
290
|
-
# @param {Integer} expires_in Defaults to 3600.
|
|
291
|
-
# @return nil
|
|
292
|
-
#
|
|
293
|
-
# @example
|
|
294
|
-
# MacawFramework::Cache.write("name", "Maria", expires_in: 7200)
|
|
295
|
-
def write(tag, value, expires_in: 3600)
|
|
296
|
-
if read(tag).nil?
|
|
297
|
-
@mutex.synchronize do
|
|
298
|
-
@cache.store(tag, { value: value, expires_in: Time.now + expires_in })
|
|
299
|
-
end
|
|
300
|
-
else
|
|
301
|
-
@cache[tag][:value] = value
|
|
302
|
-
@cache[tag][:expires_in] = Time.now + expires_in
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
##
|
|
307
|
-
# Read the value with the specified tag.
|
|
308
|
-
# Can be called statically or from an instance.
|
|
309
|
-
# @param {String} tag
|
|
310
|
-
# @return {String|nil}
|
|
311
|
-
#
|
|
312
|
-
# @example
|
|
313
|
-
# MacawFramework::Cache.read("name") # Maria
|
|
314
|
-
def self.read(tag) = MacawFramework::Cache.instance.read(tag)
|
|
315
|
-
|
|
316
|
-
##
|
|
317
|
-
# Read the value with the specified tag.
|
|
318
|
-
# Can be called statically or from an instance.
|
|
319
|
-
# @param {String} tag
|
|
320
|
-
# @return {String|nil}
|
|
321
|
-
#
|
|
322
|
-
# @example
|
|
323
|
-
# MacawFramework::Cache.read("name") # Maria
|
|
324
|
-
def read(tag) = @cache.dig(tag, :value)
|
|
325
|
-
|
|
326
|
-
private
|
|
327
|
-
|
|
328
|
-
def initialize
|
|
329
|
-
@cache = {}
|
|
330
|
-
@mutex = Mutex.new
|
|
331
|
-
@invalidation_frequency = 60
|
|
332
|
-
invalidate_cache
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def invalidate_cache
|
|
336
|
-
@invalidator = Thread.new(&method(:invalidation_process))
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
def invalidation_process
|
|
340
|
-
loop do
|
|
341
|
-
sleep @invalidation_frequency
|
|
342
|
-
@mutex.synchronize do
|
|
343
|
-
@cache.delete_if { |_, v| v[:expires_in] < Time.now }
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
end
|
|
348
|
-
end
|
|
7
|
+
require_relative 'macaw_framework/macaw'
|
|
8
|
+
require_relative 'macaw_framework/cache'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: macaw_framework
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: '1.4'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aria Diniz
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: prometheus-client
|
|
@@ -46,6 +45,7 @@ files:
|
|
|
46
45
|
- lib/macaw_framework/aspects/cache_aspect.rb
|
|
47
46
|
- lib/macaw_framework/aspects/logging_aspect.rb
|
|
48
47
|
- lib/macaw_framework/aspects/prometheus_aspect.rb
|
|
48
|
+
- lib/macaw_framework/cache.rb
|
|
49
49
|
- lib/macaw_framework/core/common/server_base.rb
|
|
50
50
|
- lib/macaw_framework/core/cron_runner.rb
|
|
51
51
|
- lib/macaw_framework/core/thread_server.rb
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- lib/macaw_framework/data_filters/response_data_filter.rb
|
|
55
55
|
- lib/macaw_framework/errors/endpoint_not_mapped_error.rb
|
|
56
56
|
- lib/macaw_framework/errors/too_many_requests_error.rb
|
|
57
|
+
- lib/macaw_framework/macaw.rb
|
|
57
58
|
- lib/macaw_framework/middlewares/memory_invalidation_middleware.rb
|
|
58
59
|
- lib/macaw_framework/middlewares/prometheus_middleware.rb
|
|
59
60
|
- lib/macaw_framework/middlewares/rate_limiter_middleware.rb
|
|
@@ -76,7 +77,6 @@ metadata:
|
|
|
76
77
|
documentation_uri: https://rubydoc.info/gems/macaw_framework
|
|
77
78
|
homepage_uri: https://github.com/ariasdiniz/macaw_framework
|
|
78
79
|
source_code_uri: https://github.com/ariasdiniz/macaw_framework
|
|
79
|
-
post_install_message:
|
|
80
80
|
rdoc_options: []
|
|
81
81
|
require_paths:
|
|
82
82
|
- lib
|
|
@@ -91,8 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
91
91
|
- !ruby/object:Gem::Version
|
|
92
92
|
version: '0'
|
|
93
93
|
requirements: []
|
|
94
|
-
rubygems_version:
|
|
95
|
-
signing_key:
|
|
94
|
+
rubygems_version: 4.0.3
|
|
96
95
|
specification_version: 4
|
|
97
96
|
summary: A lightweight back-end web framework
|
|
98
97
|
test_files: []
|