macaw_framework 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2191d627ee06b38338eeef5d88e16791ad96c9ac94fd3b4f8a917cb7a4fd1d6
4
- data.tar.gz: 8572db3e03300d28013c29f2bc5238d2f2a4907b087cfc1fe60ff6a8b2369026
3
+ metadata.gz: 3b35cc5b2fba0adc8ded0f76db41699323cc3957d54382d19e554d0e3dc77f51
4
+ data.tar.gz: bcd983f6afe8b8864b5d2c012c120ead8290b521ed66f49477f8defb7eed2ede
5
5
  SHA512:
6
- metadata.gz: 58cc6a8c9fe40d4f2fead09cf8188ac440dce1444b387a737283285ba1d823b9604ada962305d7c9279bfe0195215b891ecf417715dc755f1b027f75ffa07c13
7
- data.tar.gz: 6e8b48c28f83a5eb33f5166c37dc1bbacc2831a3fd31f494e75a5ded27410d006475f12b684da06051b4a6ffe27a3067b1c8dfd5ab0acdaf12ecaba8b444ca60
6
+ metadata.gz: 9a7f7ef95d76e906b706a036d8cb299ba1b5e3be607ba8cdc13da216b11665b019635bca062ac83fc3067a18ab36148df12d3af2bdbc0fbe42fa3a8e0cb2c018
7
+ data.tar.gz: 56a97dff9ddac5f4142b82d441de3264d4044ace0f8f9496a2e0f49a8e384c6cf57ed705bcf68dd051c536b0dc4e4b9a643161f6563a7bcfe095f2b6f691e5ff
data/CHANGELOG.md CHANGED
@@ -34,3 +34,10 @@
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
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,7 +1,5 @@
1
1
  # MacawFramework
2
2
 
3
- <img src="macaw_logo.png" alt= “” style="width: 30%;height: 30%;margin-left: 35%">
4
-
5
3
  This is a framework for developing web applications. Please have in mind that this is still a work in progress and
6
4
  it is strongly advised to not use it for production purposes for now. Actually it supports only HTTP. and HTTPS/SSL
7
5
  support will be implemented soon. Anyone who wishes to contribute is welcome.
@@ -31,13 +29,39 @@ in the same directory of the script that will start the application with the fol
31
29
  "threads": 10,
32
30
  "cache": {
33
31
  "cache_invalidation": 3600
32
+ },
33
+ "prometheus": {
34
+ "endpoint": "/metrics"
35
+ },
36
+ "rate_limiting": {
37
+ "window": 10,
38
+ "max_requests": 3,
39
+ "ignore_headers": [
40
+ "header-to-be-ignored-from-caching-strategy",
41
+ "another-header-to-be-ignored-from-caching-strategy"
42
+ ]
43
+ },
44
+ "ssl": {
45
+ "ssl": {
46
+ "cert_file_name": "path/to/cert/file/file.crt",
47
+ "key_file_name": "path/to/cert/key/file.key"
48
+ }
34
49
  }
35
50
  }
36
51
  }
37
52
  ```
38
53
 
39
54
  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.
55
+ should exist in the app main directory and it need the `cache_invalidation` config set. It is possible to
56
+ provide a list of strings in the property `ignore_headers`. All the client headers with the same name of any
57
+ of the strings provided will be ignored from caching strategy. This is useful to exclude headers like
58
+ correlation IDs from the caching strategy.
59
+
60
+ Rate Limit window should also be specified in seconds. Rate limit will be activated only if the `rate_limiting` config
61
+ exists inside `application.json`.
62
+
63
+ If the SSL configuration is provided in the `application.json` file with valid certificate and key files, the TCP server
64
+ will be wrapped with HTTPS security using the provided certificate.
41
65
 
42
66
  Example of usage:
43
67
 
@@ -51,7 +75,7 @@ m.get('/hello_world', cache: true) do |context|
51
75
  context[:body] # Returns the request body as string
52
76
  context[:params] # Returns query parameters and path variables as a hash
53
77
  context[:headers] # Returns headers as a hash
54
- return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
78
+ return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200, {"Content-Type" => "application/json"}
55
79
  end
56
80
 
57
81
  m.post('/hello_world/:path_variable') do |context|
@@ -70,9 +94,9 @@ The example above starts a server and creates a GET endpoint at localhost/hello_
70
94
  If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
71
95
  is configurable via the `application.json` file.
72
96
 
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.
97
+ The verb methods must always return a string or nil (used as the response), a number corresponding to the HTTP status
98
+ 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
99
+ returns nil for body, status code and headers, a default 200 OK status will be sent as the response.
76
100
 
77
101
  ## Contributing
78
102
 
@@ -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,12 @@
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 "../errors/too_many_requests_error"
3
6
  require_relative "../aspects/prometheus_aspect"
4
7
  require_relative "../aspects/logging_aspect"
5
- require_relative "../utils/http_status_code"
6
8
  require_relative "../aspects/cache_aspect"
9
+ require "openssl"
7
10
 
8
11
  ##
9
12
  # Class responsible for providing a default
@@ -12,7 +15,6 @@ class Server
12
15
  prepend CacheAspect
13
16
  prepend LoggingAspect
14
17
  prepend PrometheusAspect
15
- include HttpStatusCode
16
18
  # rubocop:disable Metrics/ParameterLists
17
19
 
18
20
  ##
@@ -25,18 +27,20 @@ class Server
25
27
  # @param {CachingMiddleware} cache
26
28
  # @param {Prometheus::Client:Registry} prometheus
27
29
  # @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
30
+ def initialize(macaw, endpoints_to_cache = nil, cache = nil, prometheus = nil, prometheus_mw = nil)
31
+ @port = macaw.port
32
+ @bind = macaw.bind
32
33
  @macaw = macaw
33
- @macaw_log = logger
34
- @num_threads = num_threads
34
+ @macaw_log = macaw.macaw_log
35
+ @num_threads = macaw.threads
35
36
  @work_queue = Queue.new
36
- @endpoints_to_cache = endpoints_to_cache || []
37
- @cache = cache
37
+ ignored_headers = set_rate_limiting
38
+ set_ssl
39
+ @rate_limit ||= nil
40
+ ignored_headers ||= nil
41
+ @cache = { cache: cache, endpoints_to_cache: endpoints_to_cache || [], ignored_headers: ignored_headers }
38
42
  @prometheus = prometheus
39
- @prometheus_middleware = prometheus_middleware
43
+ @prometheus_middleware = prometheus_mw
40
44
  @workers = []
41
45
  end
42
46
 
@@ -46,6 +50,7 @@ class Server
46
50
  # Start running the webserver.
47
51
  def run
48
52
  @server = TCPServer.new(@bind, @port)
53
+ @server = OpenSSL::SSL::SSLServer.new(@server, @context) if @context
49
54
  @num_threads.times do
50
55
  @workers << Thread.new do
51
56
  loop do
@@ -59,6 +64,8 @@ class Server
59
64
 
60
65
  loop do
61
66
  @work_queue << @server.accept
67
+ rescue OpenSSL::SSL::SSLError => e
68
+ @macaw_log.error("SSL error: #{e.message}")
62
69
  rescue IOError, Errno::EBADF
63
70
  break
64
71
  end
@@ -77,13 +84,19 @@ class Server
77
84
  def handle_client(client)
78
85
  path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
79
86
  raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
87
+ raise TooManyRequestsError unless @rate_limit.nil? || @rate_limit.allow?(client.peeraddr[3])
88
+
89
+ client_data = get_client_data(body, headers, parameters)
80
90
 
81
91
  @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)
92
+ message, status, response_headers = call_endpoint(@prometheus_middleware, @macaw_log, @cache,
93
+ method_name, client_data)
84
94
  status ||= 200
85
95
  message ||= nil
86
- client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
96
+ response_headers ||= nil
97
+ client.puts ResponseDataFilter.mount_response(status, response_headers, message)
98
+ rescue TooManyRequestsError
99
+ client.print "HTTP/1.1 429 Too Many Requests\r\n\r\n"
87
100
  rescue EndpointNotMappedError
88
101
  client.print "HTTP/1.1 404 Not Found\r\n\r\n"
89
102
  rescue StandardError => e
@@ -93,7 +106,39 @@ class Server
93
106
  client.close
94
107
  end
95
108
 
96
- def call_endpoint(name, headers, body, parameters)
97
- @macaw.send(name.to_sym, { headers: headers, body: body, params: parameters })
109
+ def set_rate_limiting
110
+ if @macaw.config&.dig("macaw", "rate_limiting")
111
+ ignored_headers = @macaw.config["macaw"]["rate_limiting"]["ignore_headers"] || []
112
+ @rate_limit = RateLimiterMiddleware.new(
113
+ @macaw.config["macaw"]["rate_limiting"]["window"].to_i || 1,
114
+ @macaw.config["macaw"]["rate_limiting"]["max_requests"].to_i || 60
115
+ )
116
+ end
117
+ ignored_headers
118
+ end
119
+
120
+ def set_ssl
121
+ if @macaw.config&.dig("macaw", "ssl")
122
+ @context = OpenSSL::SSL::SSLContext.new
123
+ @context.cert = OpenSSL::X509::Certificate.new(File.read(@macaw.config["macaw"]["ssl"]["cert_file_name"]))
124
+ @context.key = OpenSSL::PKey::RSA.new(File.read(@macaw.config["macaw"]["ssl"]["key_file_name"]))
125
+ end
126
+ @context ||= nil
127
+ rescue IOError => e
128
+ @macaw_log.error("It was not possible to read files #{@macaw.config["macaw"]["ssl"]["cert_file_name"]} and
129
+ #{@macaw.config["macaw"]["ssl"]["key_file_name"]}. Please assure the files exists and their names are correct.")
130
+ @macaw_log.error(e.backtrace)
131
+ raise e
132
+ end
133
+
134
+ def call_endpoint(name, client_data)
135
+ @macaw.send(
136
+ name.to_sym,
137
+ { headers: client_data[:headers], body: client_data[:body], params: client_data[:parameters] }
138
+ )
139
+ end
140
+
141
+ def get_client_data(body, headers, parameters)
142
+ { body: body, headers: headers, parameters: parameters }
98
143
  end
99
144
  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
@@ -4,15 +4,18 @@
4
4
  # Middleware responsible for storing and
5
5
  # invalidating cache.
6
6
  class CachingMiddleware
7
- attr_accessor :cache
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.0"
5
5
  end
@@ -2,7 +2,7 @@
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"
5
+ require_relative "macaw_framework/data_filters/request_data_filtering"
6
6
  require_relative "macaw_framework/middlewares/caching_middleware"
7
7
  require_relative "macaw_framework/core/server"
8
8
  require_relative "macaw_framework/version"
@@ -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 = CachingMiddleware.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
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.0
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-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prometheus-client
@@ -43,10 +43,13 @@ files:
43
43
  - lib/macaw_framework/aspects/logging_aspect.rb
44
44
  - lib/macaw_framework/aspects/prometheus_aspect.rb
45
45
  - lib/macaw_framework/core/server.rb
46
+ - lib/macaw_framework/data_filters/request_data_filtering.rb
47
+ - lib/macaw_framework/data_filters/response_data_filter.rb
46
48
  - lib/macaw_framework/errors/endpoint_not_mapped_error.rb
49
+ - lib/macaw_framework/errors/too_many_requests_error.rb
47
50
  - lib/macaw_framework/middlewares/caching_middleware.rb
48
51
  - lib/macaw_framework/middlewares/prometheus_middleware.rb
49
- - lib/macaw_framework/middlewares/request_data_filtering.rb
52
+ - lib/macaw_framework/middlewares/rate_limiter_middleware.rb
50
53
  - lib/macaw_framework/utils/http_status_code.rb
51
54
  - lib/macaw_framework/version.rb
52
55
  - macaw_logo.png
@@ -79,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
82
  - !ruby/object:Gem::Version
80
83
  version: '0'
81
84
  requirements: []
82
- rubygems_version: 3.4.10
85
+ rubygems_version: 3.4.12
83
86
  signing_key:
84
87
  specification_version: 4
85
88
  summary: A web framework still in development.