macaw_framework 0.2.0 → 1.0.1

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: 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: []