macaw_framework 1.0.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: 3b35cc5b2fba0adc8ded0f76db41699323cc3957d54382d19e554d0e3dc77f51
4
- data.tar.gz: bcd983f6afe8b8864b5d2c012c120ead8290b521ed66f49477f8defb7eed2ede
3
+ metadata.gz: 03ac2a8df24de8757381abc1e9a85f7854ac71a49b44d51aa7a23f1d42f95380
4
+ data.tar.gz: 4431ce9ab63887660aaa5cc464c00a1dd3a81b08cf4f99bb53926d0aec440fc2
5
5
  SHA512:
6
- metadata.gz: 9a7f7ef95d76e906b706a036d8cb299ba1b5e3be607ba8cdc13da216b11665b019635bca062ac83fc3067a18ab36148df12d3af2bdbc0fbe42fa3a8e0cb2c018
7
- data.tar.gz: 56a97dff9ddac5f4142b82d441de3264d4044ace0f8f9496a2e0f49a8e384c6cf57ed705bcf68dd051c536b0dc4e4b9a643161f6563a7bcfe095f2b6f691e5ff
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
@@ -41,3 +41,9 @@
41
41
  - Implemented a middleware for rate limiting to prevent DoS attacks
42
42
  - Improvement of caching strategy to ignore optional headers
43
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/README.md CHANGED
@@ -1,8 +1,17 @@
1
1
  # MacawFramework
2
2
 
3
- This is a framework for developing web applications. Please have in mind that this is still a work in progress and
4
- it is strongly advised to not use it for production purposes for now. Actually it supports only HTTP. and HTTPS/SSL
5
- support will be implemented soon. Anyone who wishes to contribute is welcome.
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.
6
+
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
6
15
 
7
16
  ## Installation
8
17
 
@@ -16,10 +25,56 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
25
 
17
26
  ## Usage
18
27
 
19
- 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
+ ```
20
58
 
21
- The default server port is 8080. To choose a different port, create a file with the name `application.json`
22
- 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
23
78
 
24
79
  ```json
25
80
  {
@@ -28,68 +83,54 @@ in the same directory of the script that will start the application with the fol
28
83
  "bind": "localhost",
29
84
  "threads": 10,
30
85
  "cache": {
31
- "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
+ ]
32
91
  },
33
92
  "prometheus": {
34
93
  "endpoint": "/metrics"
35
94
  },
36
95
  "rate_limiting": {
37
96
  "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
- ]
97
+ "max_requests": 3
43
98
  },
44
99
  "ssl": {
45
- "ssl": {
46
- "cert_file_name": "path/to/cert/file/file.crt",
47
- "key_file_name": "path/to/cert/key/file.key"
48
- }
100
+ "cert_file_name": "path/to/cert/file/file.crt",
101
+ "key_file_name": "path/to/cert/key/file.key"
49
102
  }
50
103
  }
51
104
  }
52
105
  ```
53
106
 
107
+ ### Monitoring: Easily monitor your application performance and metrics with built-in Prometheus support
108
+
109
+ ```shell
110
+ curl http://localhost:8080/metrics
111
+ ```
112
+
113
+ ### Tips
114
+
54
115
  Cache invalidation time should be specified in seconds. In order to enable caching, The application.json file
55
116
  should exist in the app main directory and it need the `cache_invalidation` config set. It is possible to
56
117
  provide a list of strings in the property `ignore_headers`. All the client headers with the same name of any
57
118
  of the strings provided will be ignored from caching strategy. This is useful to exclude headers like
58
119
  correlation IDs from the caching strategy.
59
120
 
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.
65
-
66
- Example of usage:
121
+ URL parameters like `...endOfUrl?key1=value1&key2=value2` can be find in the `context[:params]`
67
122
 
68
123
  ```ruby
69
- require 'macaw_framework'
70
- require 'json'
71
-
72
- m = MacawFramework::Macaw.new
73
-
74
- m.get('/hello_world', cache: true) do |context|
75
- context[:body] # Returns the request body as string
76
- context[:params] # Returns query parameters and path variables as a hash
77
- context[:headers] # Returns headers as a hash
78
- return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200, {"Content-Type" => "application/json"}
79
- end
80
-
81
- m.post('/hello_world/:path_variable') do |context|
82
- context[:body] # Returns the request body as string
83
- context[:params] # Returns query parameters and path variables as a hash
84
- context[:headers] # Returns headers as a hash
85
- context[:params][:path_variable] # The defined path variable can be found in :params
86
- return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
124
+ m.get('/test_params') do |context|
125
+ context[:params]["key1"] # returns: value1
87
126
  end
88
-
89
- m.start!
90
127
  ```
91
128
 
92
- 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.
93
134
 
94
135
  If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
95
136
  is configurable via the `application.json` file.
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
@@ -4,7 +4,7 @@
4
4
  # Aspect that provide cache for the endpoints.
5
5
  module CacheAspect
6
6
  def call_endpoint(cache, *args)
7
- return super(*args) unless !cache[:cache].nil? && cache[:endpoints_to_cache].include?(args[0])
7
+ return super(*args) unless !cache[:cache].nil? && cache[:endpoints_to_cache]&.include?(args[0])
8
8
 
9
9
  cache_filtered_name = cache_name_filter(args[1], cache[:ignored_headers])
10
10
 
@@ -20,7 +20,7 @@ module CacheAspect
20
20
  private
21
21
 
22
22
  def cache_name_filter(client_data, ignored_headers)
23
- filtered_headers = client_data[:headers].filter { |key, _value| !ignored_headers.include?(key) }
23
+ filtered_headers = client_data[:headers].filter { |key, _value| !ignored_headers&.include?(key) }
24
24
  [{ body: client_data[:body], params: client_data[:params], headers: filtered_headers }].to_s.to_sym
25
25
  end
26
26
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../middlewares/rate_limiter_middleware"
4
4
  require_relative "../data_filters/response_data_filter"
5
+ require_relative "../middlewares/memory_invalidation_middleware"
5
6
  require_relative "../errors/too_many_requests_error"
6
7
  require_relative "../aspects/prometheus_aspect"
7
8
  require_relative "../aspects/logging_aspect"
@@ -24,7 +25,7 @@ class Server
24
25
  # @param {Integer} port
25
26
  # @param {String} bind
26
27
  # @param {Integer} num_threads
27
- # @param {CachingMiddleware} cache
28
+ # @param {MemoryInvalidationMiddleware} cache
28
29
  # @param {Prometheus::Client:Registry} prometheus
29
30
  # @return {Server}
30
31
  def initialize(macaw, endpoints_to_cache = nil, cache = nil, prometheus = nil, prometheus_mw = nil)
@@ -34,8 +35,8 @@ class Server
34
35
  @macaw_log = macaw.macaw_log
35
36
  @num_threads = macaw.threads
36
37
  @work_queue = Queue.new
37
- ignored_headers = set_rate_limiting
38
- set_ssl
38
+ ignored_headers = set_cache_ignored_h
39
+ set_features
39
40
  @rate_limit ||= nil
40
41
  ignored_headers ||= nil
41
42
  @cache = { cache: cache, endpoints_to_cache: endpoints_to_cache || [], ignored_headers: ignored_headers }
@@ -86,11 +87,12 @@ class Server
86
87
  raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
87
88
  raise TooManyRequestsError unless @rate_limit.nil? || @rate_limit.allow?(client.peeraddr[3])
88
89
 
90
+ declare_client_session(client)
89
91
  client_data = get_client_data(body, headers, parameters)
90
92
 
91
93
  @macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
92
94
  message, status, response_headers = call_endpoint(@prometheus_middleware, @macaw_log, @cache,
93
- method_name, client_data)
95
+ method_name, client_data, client.peeraddr[3])
94
96
  status ||= 200
95
97
  message ||= nil
96
98
  response_headers ||= nil
@@ -106,13 +108,24 @@ class Server
106
108
  client.close
107
109
  end
108
110
 
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
+
109
116
  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
- )
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"] || []
116
129
  end
117
130
  ignored_headers
118
131
  end
@@ -131,10 +144,31 @@ class Server
131
144
  raise e
132
145
  end
133
146
 
134
- def call_endpoint(name, client_data)
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)
135
164
  @macaw.send(
136
165
  name.to_sym,
137
- { headers: client_data[:headers], body: client_data[:body], params: client_data[:parameters] }
166
+ {
167
+ headers: client_data[:headers],
168
+ body: client_data[:body],
169
+ params: client_data[:parameters],
170
+ client: @session[client_ip][0]
171
+ }
138
172
  )
139
173
  end
140
174
 
@@ -3,7 +3,7 @@
3
3
  ##
4
4
  # Middleware responsible for storing and
5
5
  # invalidating cache.
6
- class CachingMiddleware
6
+ class MemoryInvalidationMiddleware
7
7
  attr_accessor :cache, :mutex
8
8
 
9
9
  def initialize(inv_time_seconds = 3_600)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end
@@ -3,7 +3,7 @@
3
3
  require_relative "macaw_framework/errors/endpoint_not_mapped_error"
4
4
  require_relative "macaw_framework/middlewares/prometheus_middleware"
5
5
  require_relative "macaw_framework/data_filters/request_data_filtering"
6
- require_relative "macaw_framework/middlewares/caching_middleware"
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"
@@ -31,7 +31,7 @@ module MacawFramework
31
31
  @bind = @config["macaw"]["bind"] || "localhost"
32
32
  @threads = @config["macaw"]["threads"] || 5
33
33
  unless @config["macaw"]["cache"].nil?
34
- @cache = CachingMiddleware.new(@config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
34
+ @cache = MemoryInvalidationMiddleware.new(@config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
35
35
  end
36
36
  @prometheus = Prometheus::Client::Registry.new if @config["macaw"]["prometheus"]
37
37
  @prometheus_middleware = PrometheusMiddleware.new if @config["macaw"]["prometheus"]
@@ -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]]
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: 1.0.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-28 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,6 +40,7 @@ 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
@@ -47,17 +50,17 @@ files:
47
50
  - lib/macaw_framework/data_filters/response_data_filter.rb
48
51
  - lib/macaw_framework/errors/endpoint_not_mapped_error.rb
49
52
  - lib/macaw_framework/errors/too_many_requests_error.rb
50
- - lib/macaw_framework/middlewares/caching_middleware.rb
53
+ - lib/macaw_framework/middlewares/memory_invalidation_middleware.rb
51
54
  - lib/macaw_framework/middlewares/prometheus_middleware.rb
52
55
  - lib/macaw_framework/middlewares/rate_limiter_middleware.rb
53
56
  - lib/macaw_framework/utils/http_status_code.rb
54
57
  - lib/macaw_framework/version.rb
55
58
  - macaw_logo.png
56
- - sig/caching_middleware.rbs
57
59
  - sig/http_status_code.rbs
58
60
  - sig/logging_aspect.rbs
59
61
  - sig/macaw_framework.rbs
60
62
  - sig/macaw_framework/macaw.rbs
63
+ - sig/memory_invalidation_middleware.rbs
61
64
  - sig/request_data_filtering.rbs
62
65
  - sig/server.rbs
63
66
  homepage: https://github.com/ariasdiniz/macaw_framework
@@ -85,5 +88,5 @@ requirements: []
85
88
  rubygems_version: 3.4.12
86
89
  signing_key:
87
90
  specification_version: 4
88
- summary: A web framework still in development.
91
+ summary: A lightweight back-end web framework
89
92
  test_files: []