macaw_framework 0.2.0 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2191d627ee06b38338eeef5d88e16791ad96c9ac94fd3b4f8a917cb7a4fd1d6
4
- data.tar.gz: 8572db3e03300d28013c29f2bc5238d2f2a4907b087cfc1fe60ff6a8b2369026
3
+ metadata.gz: 03ac2a8df24de8757381abc1e9a85f7854ac71a49b44d51aa7a23f1d42f95380
4
+ data.tar.gz: 4431ce9ab63887660aaa5cc464c00a1dd3a81b08cf4f99bb53926d0aec440fc2
5
5
  SHA512:
6
- metadata.gz: 58cc6a8c9fe40d4f2fead09cf8188ac440dce1444b387a737283285ba1d823b9604ada962305d7c9279bfe0195215b891ecf417715dc755f1b027f75ffa07c13
7
- data.tar.gz: 6e8b48c28f83a5eb33f5166c37dc1bbacc2831a3fd31f494e75a5ded27410d006475f12b684da06051b4a6ffe27a3067b1c8dfd5ab0acdaf12ecaba8b444ca60
6
+ metadata.gz: 7f981191cafd8504428de742979979a82ce1609c69ab08afc9f0b8e131381d12cc298b9b7447679b1611719605b825fcf409c9b4afee2b0d0f3a4fa8f28b5c79
7
+ data.tar.gz: cda629f9c4c779d11712c18dd5f9b7c8c90805688630d75c14b7fcd7c485b3cdcb2ea2386b5f6bcde62296a7b283337ee45418a08b67e61686f23ef18bf56bb2
data/.rubocop.yml CHANGED
@@ -26,3 +26,6 @@ Metrics/ParameterLists:
26
26
 
27
27
  Metrics/PerceivedComplexity:
28
28
  Enabled: false
29
+
30
+ Metrics/ClassLength:
31
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -34,3 +34,16 @@
34
34
  - Adding middleware for integration with Prometheus to collect metrics
35
35
  - Adding a simple caching mechanism that can be enabled separately for each endpoint
36
36
  - Performance and functional optimizations
37
+
38
+ ## [1.0.0] - 2023-04-28
39
+
40
+ - Adding support to HTTPS/SSL using security certificates
41
+ - Implemented a middleware for rate limiting to prevent DoS attacks
42
+ - Improvement of caching strategy to ignore optional headers
43
+ - First production-ready version
44
+
45
+ ## [1.0.1] - 2023-05-03
46
+
47
+ - Introducing server-side session management
48
+ - Fixing a bug with cache
49
+ - Improving README
data/Gemfile CHANGED
@@ -11,6 +11,10 @@ gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
13
 
14
- gem "simplecov", "~> 0.22.0"
15
-
16
14
  gem "prometheus-client", "~> 4.1"
15
+
16
+ group :test do
17
+ gem "simplecov", "~> 0.21.2"
18
+ gem "simplecov-json"
19
+ gem "simplecov_json_formatter", "~> 0.1.2"
20
+ end
data/README.md CHANGED
@@ -1,10 +1,17 @@
1
1
  # MacawFramework
2
2
 
3
- <img src="macaw_logo.png" alt= “” style="width: 30%;height: 30%;margin-left: 35%">
3
+ MacawFramework is a lightweight, easy-to-use web framework for Ruby designed to simplify the development of small to
4
+ medium-sized web applications. With support for various HTTP methods, caching, and session management, MacawFramework
5
+ provides developers with the essential tools to quickly build and deploy their applications.
4
6
 
5
- This is a framework for developing web applications. Please have in mind that this is still a work in progress and
6
- it is strongly advised to not use it for production purposes for now. Actually it supports only HTTP. and HTTPS/SSL
7
- support will be implemented soon. Anyone who wishes to contribute is welcome.
7
+ ## Features
8
+
9
+ - Simple routing with support for GET, POST, PUT, PATCH, and DELETE HTTP methods
10
+ - Caching middleware for improved performance
11
+ - Session management with server-side in-memory storage
12
+ - Basic rate limiting and SSL support
13
+ - Prometheus integration for monitoring and metrics
14
+ - Lightweight and easy to learn
8
15
 
9
16
  ## Installation
10
17
 
@@ -18,10 +25,56 @@ If bundler is not being used to manage dependencies, install the gem by executin
18
25
 
19
26
  ## Usage
20
27
 
21
- The usage of the framework still very simple. Actually it support 5 HTTP verbs: GET, POST, PUT, PATCH and DELETE.
28
+ ### Basic routing: Define routes with support for GET, POST, PUT, PATCH, and DELETE HTTP methods
29
+
30
+ ```ruby
31
+ require 'macaw_framework'
32
+
33
+ m = MacawFramework::Macaw.new
34
+
35
+ m.get('/hello_world') do |_context|
36
+ return "Hello, World!", 200, {"Content-Type" => "text/plain"}
37
+ end
38
+
39
+ m.post('/submit_data/:path_variable') do |context|
40
+ context[:body] # Client body data
41
+ context[:params] # Client params, like url parameters or variables
42
+ context[:headers] # Client headers
43
+ context[:params][:path_variable] # The defined path variable can be found in :params
44
+ context[:client] # Client session
45
+ end
46
+
47
+ m.start!
48
+
49
+ ```
50
+
51
+ ### Caching: Improve performance by caching responses and configuring cache invalidation
52
+
53
+ ```ruby
54
+ m.get('/cached_data', cache: true) do |context|
55
+ # Retrieve data
56
+ end
57
+ ```
22
58
 
23
- The default server port is 8080. To choose a different port, create a file with the name `application.json`
24
- in the same directory of the script that will start the application with the following content:
59
+ ### Session management: Handle user sessions securely with server-side in-memory storage
60
+
61
+ ```ruby
62
+ m.get('/login') do |context|
63
+ # Authenticate user
64
+ context[:session][:user_id] = user_id
65
+ end
66
+
67
+ m.get('/dashboard') do |context|
68
+ # Check if the user is logged in
69
+ if context[:session][:user_id]
70
+ # Show dashboard
71
+ else
72
+ # Redirect to login
73
+ end
74
+ end
75
+ ```
76
+
77
+ ### Configuration: Customize various aspects of the framework through the application.json configuration file, such as rate limiting, SSL support, and Prometheus integration
25
78
 
26
79
  ```json
27
80
  {
@@ -30,49 +83,61 @@ in the same directory of the script that will start the application with the fol
30
83
  "bind": "localhost",
31
84
  "threads": 10,
32
85
  "cache": {
33
- "cache_invalidation": 3600
86
+ "cache_invalidation": 3600,
87
+ "ignore_headers": [
88
+ "header-to-be-ignored-from-caching-strategy",
89
+ "another-header-to-be-ignored-from-caching-strategy"
90
+ ]
91
+ },
92
+ "prometheus": {
93
+ "endpoint": "/metrics"
94
+ },
95
+ "rate_limiting": {
96
+ "window": 10,
97
+ "max_requests": 3
98
+ },
99
+ "ssl": {
100
+ "cert_file_name": "path/to/cert/file/file.crt",
101
+ "key_file_name": "path/to/cert/key/file.key"
34
102
  }
35
103
  }
36
104
  }
37
105
  ```
38
106
 
39
- Cache invalidation time should be specified in seconds. In order to enable caching, The application.json file
40
- should exist in the app main directory and it need the `cache_invalidation` config set.
107
+ ### Monitoring: Easily monitor your application performance and metrics with built-in Prometheus support
41
108
 
42
- Example of usage:
109
+ ```shell
110
+ curl http://localhost:8080/metrics
111
+ ```
43
112
 
44
- ```ruby
45
- require 'macaw_framework'
46
- require 'json'
113
+ ### Tips
47
114
 
48
- m = MacawFramework::Macaw.new
115
+ Cache invalidation time should be specified in seconds. In order to enable caching, The application.json file
116
+ should exist in the app main directory and it need the `cache_invalidation` config set. It is possible to
117
+ provide a list of strings in the property `ignore_headers`. All the client headers with the same name of any
118
+ of the strings provided will be ignored from caching strategy. This is useful to exclude headers like
119
+ correlation IDs from the caching strategy.
49
120
 
50
- m.get('/hello_world', cache: true) do |context|
51
- context[:body] # Returns the request body as string
52
- context[:params] # Returns query parameters and path variables as a hash
53
- context[:headers] # Returns headers as a hash
54
- return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
55
- end
121
+ URL parameters like `...endOfUrl?key1=value1&key2=value2` can be find in the `context[:params]`
56
122
 
57
- m.post('/hello_world/:path_variable') do |context|
58
- context[:body] # Returns the request body as string
59
- context[:params] # Returns query parameters and path variables as a hash
60
- context[:headers] # Returns headers as a hash
61
- context[:params][:path_variable] # The defined path variable can be found in :params
62
- return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
123
+ ```ruby
124
+ m.get('/test_params') do |context|
125
+ context[:params]["key1"] # returns: value1
63
126
  end
64
-
65
- m.start!
66
127
  ```
67
128
 
68
- The example above starts a server and creates a GET endpoint at localhost/hello_world.
129
+ Rate Limit window should also be specified in seconds. Rate limit will be activated only if the `rate_limiting` config
130
+ exists inside `application.json`.
131
+
132
+ If the SSL configuration is provided in the `application.json` file with valid certificate and key files, the TCP server
133
+ will be wrapped with HTTPS security using the provided certificate.
69
134
 
70
135
  If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
71
136
  is configurable via the `application.json` file.
72
137
 
73
- The verb methods must always return a string or nil (used as the response) and a number corresponding to the HTTP status
74
- code to be returned to the client. If an endpoint doesn't return a value or returns nil for both the string and the
75
- code, a default 200 OK status will be sent as the response.
138
+ The verb methods must always return a string or nil (used as the response), a number corresponding to the HTTP status
139
+ 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
140
+ returns nil for body, status code and headers, a default 200 OK status will be sent as the response.
76
141
 
77
142
  ## Contributing
78
143
 
data/SECURITY.md ADDED
@@ -0,0 +1,13 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.0.x | :white_check_mark: |
8
+ | < 1.x | :x: |
9
+
10
+
11
+ ## Reporting a Vulnerability
12
+
13
+ If you find a vulnerability, please open an issue or send an e-mail to aria.diniz.dev@gmail.com
@@ -3,12 +3,24 @@
3
3
  ##
4
4
  # Aspect that provide cache for the endpoints.
5
5
  module CacheAspect
6
- def call_endpoint(cache, endpoints_to_cache, *args)
7
- return super(*args) unless endpoints_to_cache.include?(args[0]) && !cache.nil?
8
- return cache.cache[args[2..].to_s.to_sym][0] unless cache.cache[args[2..].to_s.to_sym].nil?
6
+ def call_endpoint(cache, *args)
7
+ return super(*args) unless !cache[:cache].nil? && cache[:endpoints_to_cache]&.include?(args[0])
9
8
 
10
- response = super(*args)
11
- cache.cache[args[2..].to_s.to_sym] = [response, Time.now]
12
- response
9
+ cache_filtered_name = cache_name_filter(args[1], cache[:ignored_headers])
10
+
11
+ cache[:cache].mutex.synchronize do
12
+ return cache[:cache].cache[cache_filtered_name][0] unless cache[:cache].cache[cache_filtered_name].nil?
13
+
14
+ response = super(*args)
15
+ cache[:cache].cache[cache_filtered_name] = [response, Time.now]
16
+ response
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def cache_name_filter(client_data, ignored_headers)
23
+ filtered_headers = client_data[:headers].filter { |key, _value| !ignored_headers&.include?(key) }
24
+ [{ body: client_data[:body], params: client_data[:params], headers: filtered_headers }].to_s.to_sym
13
25
  end
14
26
  end
@@ -8,8 +8,8 @@ require "logger"
8
8
  # in the framework.
9
9
  module LoggingAspect
10
10
  def call_endpoint(logger, *args)
11
- endpoint_name = args[2].split(".")[1..].join("/")
12
- logger.info("Request received for #{endpoint_name} with arguments: #{args[3..]}")
11
+ endpoint_name = args[1].split(".")[1..].join("/")
12
+ logger.info("Request received for #{endpoint_name} with arguments: #{args[2..]}")
13
13
 
14
14
  begin
15
15
  response = super(*args)
@@ -13,7 +13,7 @@ module PrometheusAspect
13
13
  ensure
14
14
  duration = (Time.now - start_time) * 1_000
15
15
 
16
- endpoint_name = args[3].split(".").join("/")
16
+ endpoint_name = args[2].split(".").join("/")
17
17
 
18
18
  prometheus_middleware.request_duration_milliseconds.with_labels(endpoint: endpoint_name).observe(duration)
19
19
  prometheus_middleware.request_count.with_labels(endpoint: endpoint_name).increment
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../middlewares/rate_limiter_middleware"
4
+ require_relative "../data_filters/response_data_filter"
5
+ require_relative "../middlewares/memory_invalidation_middleware"
6
+ require_relative "../errors/too_many_requests_error"
3
7
  require_relative "../aspects/prometheus_aspect"
4
8
  require_relative "../aspects/logging_aspect"
5
- require_relative "../utils/http_status_code"
6
9
  require_relative "../aspects/cache_aspect"
10
+ require "openssl"
7
11
 
8
12
  ##
9
13
  # Class responsible for providing a default
@@ -12,7 +16,6 @@ class Server
12
16
  prepend CacheAspect
13
17
  prepend LoggingAspect
14
18
  prepend PrometheusAspect
15
- include HttpStatusCode
16
19
  # rubocop:disable Metrics/ParameterLists
17
20
 
18
21
  ##
@@ -22,21 +25,23 @@ class Server
22
25
  # @param {Integer} port
23
26
  # @param {String} bind
24
27
  # @param {Integer} num_threads
25
- # @param {CachingMiddleware} cache
28
+ # @param {MemoryInvalidationMiddleware} cache
26
29
  # @param {Prometheus::Client:Registry} prometheus
27
30
  # @return {Server}
28
- def initialize(macaw, logger, port, bind, num_threads, endpoints_to_cache = nil, cache = nil, prometheus = nil,
29
- prometheus_middleware = nil)
30
- @port = port
31
- @bind = bind
31
+ def initialize(macaw, endpoints_to_cache = nil, cache = nil, prometheus = nil, prometheus_mw = nil)
32
+ @port = macaw.port
33
+ @bind = macaw.bind
32
34
  @macaw = macaw
33
- @macaw_log = logger
34
- @num_threads = num_threads
35
+ @macaw_log = macaw.macaw_log
36
+ @num_threads = macaw.threads
35
37
  @work_queue = Queue.new
36
- @endpoints_to_cache = endpoints_to_cache || []
37
- @cache = cache
38
+ ignored_headers = set_cache_ignored_h
39
+ set_features
40
+ @rate_limit ||= nil
41
+ ignored_headers ||= nil
42
+ @cache = { cache: cache, endpoints_to_cache: endpoints_to_cache || [], ignored_headers: ignored_headers }
38
43
  @prometheus = prometheus
39
- @prometheus_middleware = prometheus_middleware
44
+ @prometheus_middleware = prometheus_mw
40
45
  @workers = []
41
46
  end
42
47
 
@@ -46,6 +51,7 @@ class Server
46
51
  # Start running the webserver.
47
52
  def run
48
53
  @server = TCPServer.new(@bind, @port)
54
+ @server = OpenSSL::SSL::SSLServer.new(@server, @context) if @context
49
55
  @num_threads.times do
50
56
  @workers << Thread.new do
51
57
  loop do
@@ -59,6 +65,8 @@ class Server
59
65
 
60
66
  loop do
61
67
  @work_queue << @server.accept
68
+ rescue OpenSSL::SSL::SSLError => e
69
+ @macaw_log.error("SSL error: #{e.message}")
62
70
  rescue IOError, Errno::EBADF
63
71
  break
64
72
  end
@@ -77,13 +85,20 @@ class Server
77
85
  def handle_client(client)
78
86
  path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
79
87
  raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
88
+ raise TooManyRequestsError unless @rate_limit.nil? || @rate_limit.allow?(client.peeraddr[3])
89
+
90
+ declare_client_session(client)
91
+ client_data = get_client_data(body, headers, parameters)
80
92
 
81
93
  @macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
82
- message, status = call_endpoint(@prometheus_middleware, @macaw_log, @cache, @endpoints_to_cache,
83
- method_name, headers, body, parameters)
94
+ message, status, response_headers = call_endpoint(@prometheus_middleware, @macaw_log, @cache,
95
+ method_name, client_data, client.peeraddr[3])
84
96
  status ||= 200
85
97
  message ||= nil
86
- client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
98
+ response_headers ||= nil
99
+ client.puts ResponseDataFilter.mount_response(status, response_headers, message)
100
+ rescue TooManyRequestsError
101
+ client.print "HTTP/1.1 429 Too Many Requests\r\n\r\n"
87
102
  rescue EndpointNotMappedError
88
103
  client.print "HTTP/1.1 404 Not Found\r\n\r\n"
89
104
  rescue StandardError => e
@@ -93,7 +108,71 @@ class Server
93
108
  client.close
94
109
  end
95
110
 
96
- def call_endpoint(name, headers, body, parameters)
97
- @macaw.send(name.to_sym, { headers: headers, body: body, params: parameters })
111
+ def declare_client_session(client)
112
+ @session[client.peeraddr[3]] ||= [{}, Time.now]
113
+ @session[client.peeraddr[3]] = [{}, Time.now] if @session[client.peeraddr[3]][0].nil?
114
+ end
115
+
116
+ def set_rate_limiting
117
+ return unless @macaw.config&.dig("macaw", "rate_limiting")
118
+
119
+ @rate_limit = RateLimiterMiddleware.new(
120
+ @macaw.config["macaw"]["rate_limiting"]["window"].to_i || 1,
121
+ @macaw.config["macaw"]["rate_limiting"]["max_requests"].to_i || 60
122
+ )
123
+ end
124
+
125
+ def set_cache_ignored_h
126
+ ignored_headers = []
127
+ if @macaw.config&.dig("macaw", "cache", "ignored_headers")
128
+ ignored_headers = @macaw.config["macaw"]["cache"]["ignore_headers"] || []
129
+ end
130
+ ignored_headers
131
+ end
132
+
133
+ def set_ssl
134
+ if @macaw.config&.dig("macaw", "ssl")
135
+ @context = OpenSSL::SSL::SSLContext.new
136
+ @context.cert = OpenSSL::X509::Certificate.new(File.read(@macaw.config["macaw"]["ssl"]["cert_file_name"]))
137
+ @context.key = OpenSSL::PKey::RSA.new(File.read(@macaw.config["macaw"]["ssl"]["key_file_name"]))
138
+ end
139
+ @context ||= nil
140
+ rescue IOError => e
141
+ @macaw_log.error("It was not possible to read files #{@macaw.config["macaw"]["ssl"]["cert_file_name"]} and
142
+ #{@macaw.config["macaw"]["ssl"]["key_file_name"]}. Please assure the files exists and their names are correct.")
143
+ @macaw_log.error(e.backtrace)
144
+ raise e
145
+ end
146
+
147
+ def set_session
148
+ @session = {}
149
+ inv = if @macaw.config&.dig("macaw", "session", "invalidation_time")
150
+ MemoryInvalidationMiddleware.new(@macaw.config["macaw"]["session"]["invalidation_time"])
151
+ else
152
+ MemoryInvalidationMiddleware.new
153
+ end
154
+ inv.cache = @session
155
+ end
156
+
157
+ def set_features
158
+ set_rate_limiting
159
+ set_session
160
+ set_ssl
161
+ end
162
+
163
+ def call_endpoint(name, client_data, client_ip)
164
+ @macaw.send(
165
+ name.to_sym,
166
+ {
167
+ headers: client_data[:headers],
168
+ body: client_data[:body],
169
+ params: client_data[:parameters],
170
+ client: @session[client_ip][0]
171
+ }
172
+ )
173
+ end
174
+
175
+ def get_client_data(body, headers, parameters)
176
+ { body: body, headers: headers, parameters: parameters }
98
177
  end
99
178
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../utils/http_status_code"
4
+
5
+ ##
6
+ # Module responsible to filter and mount HTTP responses
7
+ module ResponseDataFilter
8
+ include HttpStatusCode
9
+
10
+ def self.mount_response(status, headers, body)
11
+ "#{mount_first_response_line(status, headers)}#{mount_response_headers(headers)}#{body}"
12
+ end
13
+
14
+ def self.mount_first_response_line(status, headers)
15
+ separator = " \r\n\r\n"
16
+ separator = " \r\n" unless headers.nil?
17
+
18
+ "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]}#{separator}"
19
+ end
20
+
21
+ def self.mount_response_headers(headers)
22
+ return nil if headers.nil?
23
+
24
+ response = ""
25
+ headers.each do |key, value|
26
+ response += "#{key}: #{value}\r\n"
27
+ end
28
+ response += "\r\n\r\n"
29
+ response
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TooManyRequestsError < StandardError
4
+ end
@@ -3,16 +3,19 @@
3
3
  ##
4
4
  # Middleware responsible for storing and
5
5
  # invalidating cache.
6
- class CachingMiddleware
7
- attr_accessor :cache
6
+ class MemoryInvalidationMiddleware
7
+ attr_accessor :cache, :mutex
8
8
 
9
9
  def initialize(inv_time_seconds = 3_600)
10
10
  @cache = {}
11
+ @mutex = Mutex.new
11
12
  Thread.new do
12
13
  loop do
13
14
  sleep(1)
14
- @cache.each_pair do |key, value|
15
- @cache.delete(key) if Time.now - value[1] >= inv_time_seconds
15
+ @mutex.synchronize do
16
+ @cache.each_pair do |key, value|
17
+ @cache.delete(key) if Time.now - value[1] >= inv_time_seconds
18
+ end
16
19
  end
17
20
  end
18
21
  end
@@ -42,7 +42,7 @@ class PrometheusMiddleware
42
42
  def prometheus_endpoint(prometheus_registry, configurations, macaw)
43
43
  endpoint = configurations["macaw"]["prometheus"]["endpoint"] || "/metrics"
44
44
  macaw.get(endpoint) do |_context|
45
- [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200]
45
+ [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200, { "Content-Type" => "plaintext" }]
46
46
  end
47
47
  end
48
48
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Middleware responsible for implementing
5
+ # rate limiting
6
+ class RateLimiterMiddleware
7
+ attr_reader :window_size, :max_requests
8
+
9
+ def initialize(window_size, max_requests)
10
+ @window_size = window_size
11
+ @max_requests = max_requests
12
+ @client_timestamps = Hash.new { |key, value| key[value] = [] }
13
+ @mutex = Mutex.new
14
+ end
15
+
16
+ def allow?(client_id)
17
+ @mutex.synchronize do
18
+ now = Time.now.to_i
19
+ timestamps = @client_timestamps[client_id]
20
+
21
+ timestamps.reject! { |timestamp| timestamp <= now - window_size }
22
+
23
+ if timestamps.length < max_requests
24
+ timestamps << now
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "0.2.0"
4
+ VERSION = "1.0.1"
5
5
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  require_relative "macaw_framework/errors/endpoint_not_mapped_error"
4
4
  require_relative "macaw_framework/middlewares/prometheus_middleware"
5
- require_relative "macaw_framework/middlewares/request_data_filtering"
6
- require_relative "macaw_framework/middlewares/caching_middleware"
5
+ require_relative "macaw_framework/data_filters/request_data_filtering"
6
+ require_relative "macaw_framework/middlewares/memory_invalidation_middleware"
7
7
  require_relative "macaw_framework/core/server"
8
8
  require_relative "macaw_framework/version"
9
9
  require "prometheus/client"
@@ -18,7 +18,7 @@ module MacawFramework
18
18
  class Macaw
19
19
  ##
20
20
  # Array containing the routes defined in the application
21
- attr_reader :routes
21
+ attr_reader :routes, :port, :bind, :threads, :macaw_log, :config
22
22
 
23
23
  ##
24
24
  # @param {Logger} custom_log
@@ -26,27 +26,27 @@ module MacawFramework
26
26
  begin
27
27
  @routes = []
28
28
  @macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
29
- config = JSON.parse(File.read("application.json"))
30
- @port = config["macaw"]["port"] || 8080
31
- @bind = config["macaw"]["bind"] || "localhost"
32
- @threads = config["macaw"]["threads"] || 5
33
- unless config["macaw"]["cache"].nil?
34
- @cache = CachingMiddleware.new(config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
29
+ @config = JSON.parse(File.read("application.json"))
30
+ @port = @config["macaw"]["port"] || 8080
31
+ @bind = @config["macaw"]["bind"] || "localhost"
32
+ @threads = @config["macaw"]["threads"] || 5
33
+ unless @config["macaw"]["cache"].nil?
34
+ @cache = MemoryInvalidationMiddleware.new(@config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
35
35
  end
36
- @prometheus = Prometheus::Client::Registry.new if config["macaw"]["prometheus"]
37
- @prometheus_middleware = PrometheusMiddleware.new if config["macaw"]["prometheus"]
38
- @prometheus_middleware.configure_prometheus(@prometheus, config, self) if config["macaw"]["prometheus"]
36
+ @prometheus = Prometheus::Client::Registry.new if @config["macaw"]["prometheus"]
37
+ @prometheus_middleware = PrometheusMiddleware.new if @config["macaw"]["prometheus"]
38
+ @prometheus_middleware.configure_prometheus(@prometheus, @config, self) if @config["macaw"]["prometheus"]
39
39
  rescue StandardError => e
40
40
  @macaw_log.error(e.message)
41
41
  end
42
42
  @port ||= 8080
43
43
  @bind ||= "localhost"
44
+ @config ||= nil
44
45
  @threads ||= 5
45
46
  @endpoints_to_cache = []
46
47
  @prometheus ||= nil
47
48
  @prometheus_middleware ||= nil
48
- @server = server.new(self, @macaw_log, @port, @bind, @threads, @endpoints_to_cache, @cache, @prometheus,
49
- @prometheus_middleware)
49
+ @server = server.new(self, @endpoints_to_cache, @cache, @prometheus, @prometheus_middleware)
50
50
  end
51
51
 
52
52
  ##
@@ -1,18 +1,25 @@
1
1
  module MacawFramework
2
2
  class Macaw
3
- @bind: string
3
+ @bind: String
4
4
  @cache: untyped
5
+ @config: Hash[String, untyped]
5
6
  @endpoints_to_cache: Array[String]
6
7
  @macaw_log: Logger
7
- @port: int
8
8
 
9
9
  @prometheus: untyped
10
+ @prometheus_middleware: untyped
10
11
  @server: Server
11
12
 
12
13
  @threads: Integer
13
14
 
15
+ attr_reader bind: String
16
+ attr_reader config: Hash[String, untyped]
17
+ attr_reader macaw_log: Logger
18
+ attr_reader port: Integer
14
19
  attr_reader routes: Array[String]
15
20
 
21
+ attr_reader threads: Integer
22
+
16
23
  def delete: -> nil
17
24
 
18
25
  def get: -> nil
@@ -1,4 +1,4 @@
1
- class CachingMiddleware
1
+ class MemoryInvalidationMiddleware
2
2
  @cache: Hash[String, Array[string]]
3
3
 
4
4
  attr_accessor cache: Hash[String, Array[string]]
data/sig/server.rbs CHANGED
@@ -1,6 +1,7 @@
1
1
  class Server
2
2
  @bind: String
3
- @cache: CachingMiddleware
3
+ @cache: Hash[Symbol, Array]
4
+ @context: OpenSSL::SSL::SSLContext
4
5
  @endpoints_to_cache: Array[String]
5
6
  @macaw: MacawFramework::Macaw
6
7
  @macaw_log: Logger
@@ -9,7 +10,7 @@ class Server
9
10
 
10
11
  @prometheus: untyped
11
12
  @prometheus_middleware: untyped
12
- @server: TCPServer
13
+ @server: TCPServer|OpenSSL::SSL::SSLServer
13
14
 
14
15
  @threads: Integer
15
16
 
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: 0.2.0
4
+ version: 1.0.1
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-04-22 00:00:00.000000000 Z
11
+ date: 2023-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prometheus-client
@@ -24,7 +24,9 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.1'
27
- description: A project started for study purpose that I intend to keep working on.
27
+ description: |-
28
+ A lightweight web framework designed for building efficient backend applications. Initially
29
+ created for study purposes, now production-ready and open for contributions.
28
30
  email:
29
31
  - aria.diniz.dev@gmail.com
30
32
  executables: []
@@ -38,23 +40,27 @@ files:
38
40
  - LICENSE.txt
39
41
  - README.md
40
42
  - Rakefile
43
+ - SECURITY.md
41
44
  - lib/macaw_framework.rb
42
45
  - lib/macaw_framework/aspects/cache_aspect.rb
43
46
  - lib/macaw_framework/aspects/logging_aspect.rb
44
47
  - lib/macaw_framework/aspects/prometheus_aspect.rb
45
48
  - lib/macaw_framework/core/server.rb
49
+ - lib/macaw_framework/data_filters/request_data_filtering.rb
50
+ - lib/macaw_framework/data_filters/response_data_filter.rb
46
51
  - lib/macaw_framework/errors/endpoint_not_mapped_error.rb
47
- - lib/macaw_framework/middlewares/caching_middleware.rb
52
+ - lib/macaw_framework/errors/too_many_requests_error.rb
53
+ - lib/macaw_framework/middlewares/memory_invalidation_middleware.rb
48
54
  - lib/macaw_framework/middlewares/prometheus_middleware.rb
49
- - lib/macaw_framework/middlewares/request_data_filtering.rb
55
+ - lib/macaw_framework/middlewares/rate_limiter_middleware.rb
50
56
  - lib/macaw_framework/utils/http_status_code.rb
51
57
  - lib/macaw_framework/version.rb
52
58
  - macaw_logo.png
53
- - sig/caching_middleware.rbs
54
59
  - sig/http_status_code.rbs
55
60
  - sig/logging_aspect.rbs
56
61
  - sig/macaw_framework.rbs
57
62
  - sig/macaw_framework/macaw.rbs
63
+ - sig/memory_invalidation_middleware.rbs
58
64
  - sig/request_data_filtering.rbs
59
65
  - sig/server.rbs
60
66
  homepage: https://github.com/ariasdiniz/macaw_framework
@@ -79,8 +85,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
85
  - !ruby/object:Gem::Version
80
86
  version: '0'
81
87
  requirements: []
82
- rubygems_version: 3.4.10
88
+ rubygems_version: 3.4.12
83
89
  signing_key:
84
90
  specification_version: 4
85
- summary: A web framework still in development.
91
+ summary: A lightweight back-end web framework
86
92
  test_files: []