macaw_framework 0.2.0 → 1.0.0

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: 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.