macaw_framework 1.3.1 → 1.3.3
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 +10 -0
- data/Gemfile +2 -0
- data/README.md +4 -3
- data/lib/macaw_framework/aspects/cache_aspect.rb +3 -3
- data/lib/macaw_framework/aspects/logging_aspect.rb +2 -5
- data/lib/macaw_framework/aspects/prometheus_aspect.rb +2 -2
- data/lib/macaw_framework/cache.rb +91 -0
- data/lib/macaw_framework/core/common/server_base.rb +1 -2
- data/lib/macaw_framework/core/thread_server.rb +11 -15
- data/lib/macaw_framework/data_filters/request_data_filtering.rb +18 -16
- 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: f5ce341c22fbc784508cd3678e12b800d9313bb3966ca933d4970798fc5d8d14
|
|
4
|
+
data.tar.gz: 7a4d3d9926365062786b919e7114c16d09e14ae9e1a6828f49322985fb9d8f9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92d923dfa4e841177186a4b6c35c7a41a543c19b2d0036fc7caa7fd9194f39479d602b8083966ad19930d9992aba88b9f678d78f1cc63b20be715df07e79f848
|
|
7
|
+
data.tar.gz: 65221f77161d6c3903c4d71c7dd2146371f4f846aa49419cedd457377c955805dbbda9de1f8e554966c36e44f974c236a395f6bb8e338b78891a433dc1b2fb56
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -147,3 +147,13 @@
|
|
|
147
147
|
- Fixing bug where missing session configuration on `application.json` break the application
|
|
148
148
|
- Including a Cache module for manual caching.
|
|
149
149
|
|
|
150
|
+
## [1.3.21]
|
|
151
|
+
- Refactoring shutdown method
|
|
152
|
+
- Fixing a bug where a HTTP call without client data broke the parser
|
|
153
|
+
- Removing logs registering new HTTP connections to reduce log bloat
|
|
154
|
+
|
|
155
|
+
## [1.3.22]
|
|
156
|
+
- Fixing error with tests on Ruby 3.4.x due to splash operator
|
|
157
|
+
|
|
158
|
+
## [1.3.3]
|
|
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'
|
data/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# MacawFramework
|
|
3
3
|
|
|
4
4
|
MacawFramework is a lightweight, easy-to-use web framework for Ruby designed to simplify the development of small to
|
|
5
|
-
medium-sized web applications.
|
|
6
|
-
provides developers with the essential tools to quickly build and deploy their applications.
|
|
5
|
+
medium-sized web applications. Weighting less than 26Kb with support for various HTTP methods, caching, and session management,
|
|
6
|
+
MacawFramework provides developers with the essential tools to quickly build and deploy their applications.
|
|
7
7
|
|
|
8
8
|
- [MacawFramework](#macawframework)
|
|
9
9
|
* [Features](#features)
|
|
@@ -31,7 +31,8 @@ provides developers with the essential tools to quickly build and deploy their a
|
|
|
31
31
|
- Session management with server-side in-memory storage
|
|
32
32
|
- Basic rate limiting and SSL support
|
|
33
33
|
- Prometheus integration for monitoring and metrics
|
|
34
|
-
-
|
|
34
|
+
- Less than 26Kb
|
|
35
|
+
- Easy to learn
|
|
35
36
|
|
|
36
37
|
## Installation
|
|
37
38
|
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
##
|
|
4
4
|
# Aspect that provide cache for the endpoints.
|
|
5
5
|
module CacheAspect
|
|
6
|
-
def call_endpoint(cache, *args)
|
|
7
|
-
return super(*args) unless !cache[:cache].nil? && cache[:endpoints_to_cache]&.include?(args[0])
|
|
6
|
+
def call_endpoint(cache, *args, **kwargs)
|
|
7
|
+
return super(*args, **kwargs) unless !cache[:cache].nil? && cache[:endpoints_to_cache]&.include?(args[0])
|
|
8
8
|
|
|
9
9
|
cache_filtered_name = cache_name_filter(args[1], cache[:cached_methods][args[0]])
|
|
10
10
|
|
|
11
11
|
cache[:cache].mutex.synchronize do
|
|
12
12
|
return cache[:cache].cache[cache_filtered_name][0] unless cache[:cache].cache[cache_filtered_name].nil?
|
|
13
13
|
|
|
14
|
-
response = super(*args)
|
|
14
|
+
response = super(*args, **kwargs)
|
|
15
15
|
cache[:cache].cache[cache_filtered_name] = [response, Time.now] if should_cache_response?(response[1])
|
|
16
16
|
response
|
|
17
17
|
end
|
|
@@ -8,11 +8,8 @@ require_relative '../data_filters/log_data_filter'
|
|
|
8
8
|
# the input and output of every endpoint called
|
|
9
9
|
# in the framework.
|
|
10
10
|
module LoggingAspect
|
|
11
|
-
def call_endpoint(logger, *args)
|
|
12
|
-
return super(*args) if logger.nil?
|
|
13
|
-
|
|
14
|
-
endpoint_name = args[1].split('.')[1..].join('/')
|
|
15
|
-
logger.info("Request received for [#{endpoint_name}] from [#{args[-1]}]")
|
|
11
|
+
def call_endpoint(logger, *args, **kwargs)
|
|
12
|
+
return super(*args, **kwargs) if logger.nil?
|
|
16
13
|
|
|
17
14
|
begin
|
|
18
15
|
response = super(*args)
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
##
|
|
4
4
|
# Aspect that provides application metrics using prometheus.
|
|
5
5
|
module PrometheusAspect
|
|
6
|
-
def call_endpoint(prometheus_middleware, *args)
|
|
7
|
-
return super(*args) if prometheus_middleware.nil?
|
|
6
|
+
def call_endpoint(prometheus_middleware, *args, **kwargs)
|
|
7
|
+
return super(*args, **kwargs) if prometheus_middleware.nil?
|
|
8
8
|
|
|
9
9
|
start_time = Time.now
|
|
10
10
|
|
|
@@ -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
|
|
@@ -39,14 +39,13 @@ module ServerBase
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def handle_client(client)
|
|
42
|
-
|
|
42
|
+
_path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
|
|
43
43
|
raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
|
|
44
44
|
raise TooManyRequestsError unless @rate_limit.nil? || @rate_limit.allow?(client.peeraddr[3])
|
|
45
45
|
|
|
46
46
|
client_data = get_client_data(body, headers, parameters)
|
|
47
47
|
session_id = declare_client_session(client_data[:headers], @macaw.secure_header) if @macaw.session
|
|
48
48
|
|
|
49
|
-
@macaw_log&.info("Running #{path.gsub("\n", '').gsub("\r", '')}")
|
|
50
49
|
message, status, response_headers = call_endpoint(@prometheus_middleware, @macaw_log, @cache,
|
|
51
50
|
method_name, client_data, session_id, client.peeraddr[3])
|
|
52
51
|
response_headers ||= {}
|
|
@@ -74,8 +74,17 @@ class ThreadServer
|
|
|
74
74
|
|
|
75
75
|
##
|
|
76
76
|
# Method Responsible for closing the TCP server.
|
|
77
|
-
def
|
|
78
|
-
|
|
77
|
+
def shutdown
|
|
78
|
+
@is_shutting_down = true
|
|
79
|
+
loop do
|
|
80
|
+
break if @work_queue.empty?
|
|
81
|
+
|
|
82
|
+
sleep 0.1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
@num_threads.times { @work_queue << :shutdown }
|
|
86
|
+
@workers.each(&:join)
|
|
87
|
+
@server.close
|
|
79
88
|
end
|
|
80
89
|
|
|
81
90
|
private
|
|
@@ -107,17 +116,4 @@ class ThreadServer
|
|
|
107
116
|
end
|
|
108
117
|
end
|
|
109
118
|
end
|
|
110
|
-
|
|
111
|
-
def shutdown
|
|
112
|
-
@is_shutting_down = true
|
|
113
|
-
loop do
|
|
114
|
-
break if @work_queue.empty?
|
|
115
|
-
|
|
116
|
-
sleep 0.1
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
@num_threads.times { @work_queue << :shutdown }
|
|
120
|
-
@workers.each(&:join)
|
|
121
|
-
@server.close
|
|
122
|
-
end
|
|
123
119
|
end
|
|
@@ -11,7 +11,7 @@ module RequestDataFiltering
|
|
|
11
11
|
# Method responsible for extracting information
|
|
12
12
|
# provided by the client like Headers and Body
|
|
13
13
|
def self.parse_request_data(client, routes)
|
|
14
|
-
path, parameters = extract_url_parameters(client.gets
|
|
14
|
+
path, parameters = extract_url_parameters(client.gets&.gsub('HTTP/1.1', ''))
|
|
15
15
|
parameters = {} if parameters.nil?
|
|
16
16
|
|
|
17
17
|
method_name = sanitize_method_name(path)
|
|
@@ -26,15 +26,15 @@ module RequestDataFiltering
|
|
|
26
26
|
|
|
27
27
|
selected_route = nil
|
|
28
28
|
routes.each do |route|
|
|
29
|
-
split_route = route
|
|
30
|
-
split_name = method_name
|
|
29
|
+
split_route = route&.split('.')
|
|
30
|
+
split_name = method_name&.split('.')
|
|
31
31
|
|
|
32
|
-
next unless split_route
|
|
32
|
+
next unless split_route&.length == split_name&.length
|
|
33
33
|
next unless match_path_with_route(split_name, split_route)
|
|
34
34
|
|
|
35
35
|
selected_route = route
|
|
36
|
-
split_route
|
|
37
|
-
parameters[var[1..].to_sym] = split_name
|
|
36
|
+
split_route&.each_with_index do |var, index|
|
|
37
|
+
parameters[var[1..].to_sym] = split_name&.dig(index) if var =~ VARIABLE_PATTERN
|
|
38
38
|
end
|
|
39
39
|
break
|
|
40
40
|
end
|
|
@@ -45,7 +45,7 @@ module RequestDataFiltering
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def self.match_path_with_route(split_path, split_route)
|
|
48
|
-
split_route
|
|
48
|
+
split_route&.each_with_index do |var, index|
|
|
49
49
|
return false if var != split_path[index] && !var.match?(VARIABLE_PATTERN)
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -56,26 +56,28 @@ module RequestDataFiltering
|
|
|
56
56
|
# Method responsible for sanitizing the method name
|
|
57
57
|
def self.sanitize_method_name(path)
|
|
58
58
|
path = extract_path(path)
|
|
59
|
-
method_name = path
|
|
60
|
-
method_name
|
|
59
|
+
method_name = path&.gsub('/', '.')&.strip&.downcase
|
|
60
|
+
method_name&.gsub!(' ', '')
|
|
61
61
|
method_name
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
##
|
|
65
65
|
# Method responsible for extracting the path from URI
|
|
66
66
|
def self.extract_path(path)
|
|
67
|
+
return path if path.nil?
|
|
68
|
+
|
|
67
69
|
path[0] == '/' ? path[1..].gsub('/', '.') : path.gsub('/', '.')
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
##
|
|
71
73
|
# Method responsible for extracting the headers from request
|
|
72
74
|
def self.extract_headers(client)
|
|
73
|
-
header = client.gets
|
|
75
|
+
header = client.gets&.delete("\n")&.delete("\r")
|
|
74
76
|
headers = {}
|
|
75
|
-
while header
|
|
77
|
+
while header&.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
|
|
76
78
|
split_header = header.split(':')
|
|
77
79
|
headers[split_header[0].strip] = split_header[1].strip
|
|
78
|
-
header = client.gets
|
|
80
|
+
header = client.gets&.delete("\n")&.delete("\r")
|
|
79
81
|
end
|
|
80
82
|
[header, headers]
|
|
81
83
|
end
|
|
@@ -83,7 +85,7 @@ module RequestDataFiltering
|
|
|
83
85
|
##
|
|
84
86
|
# Method responsible for extracting the body from request
|
|
85
87
|
def self.extract_body(client, body_first_line, content_length)
|
|
86
|
-
body = client
|
|
88
|
+
body = client&.read(content_length)
|
|
87
89
|
body_first_line << body.to_s
|
|
88
90
|
end
|
|
89
91
|
|
|
@@ -107,13 +109,13 @@ module RequestDataFiltering
|
|
|
107
109
|
##
|
|
108
110
|
# Method responsible for sanitizing the parameter name
|
|
109
111
|
def self.sanitize_parameter_name(name)
|
|
110
|
-
name
|
|
112
|
+
name&.gsub(/[^\w\s]/, '')
|
|
111
113
|
end
|
|
112
114
|
|
|
113
115
|
##
|
|
114
116
|
# Method responsible for sanitizing the parameter value
|
|
115
117
|
def self.sanitize_parameter_value(value)
|
|
116
|
-
value
|
|
117
|
-
value
|
|
118
|
+
value&.gsub(/[^\w\s]/, '')
|
|
119
|
+
value&.gsub(/\s/, '')
|
|
118
120
|
end
|
|
119
121
|
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.close
|
|
159
|
-
puts('Macaw stop flying for some seeds...')
|
|
160
|
-
else
|
|
161
|
-
@macaw_log.info('Stopping server')
|
|
162
|
-
@server.close
|
|
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.3.
|
|
4
|
+
version: 1.3.3
|
|
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: []
|