lennarb 0.4.2 → 0.5.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: d8f83fbde3862f89138c06960e7e10d92febf1b2568ec1ac7d5f414e56eb7861
4
- data.tar.gz: 2ae1191814fc395c684f7d3b52a3d16d41cdb722d61ebd1c18937ab7e2eeff00
3
+ metadata.gz: 888f84e566de65f5c87013ff56ac3fe485f59acba6f1cc39862178894cf101fa
4
+ data.tar.gz: 8703693e316b83f747f927c23b07d56f6dd571f813d8c00901432cf6c941d1f2
5
5
  SHA512:
6
- metadata.gz: 9bf21b6aeaaafc3b1c1f795ae356883ad6c4581506527f6b92b2a10b823770e6d5a8249dc7ebacc0776e0d5a8b4d10a818dcb05ac22ec5cb48e84c7f027d9925
7
- data.tar.gz: 96a9067abdf1f6062da9cac71fd79df3a7da8d01a19d662419c9f7d9498438b57299074a5ac2ac86b44271f263e0a52c69d8f48e69c7843177191a653d68e508
6
+ metadata.gz: bc83791eb32ad87b5aa772f375b048829d794d35bdea5c5d1d083dc4a2e8b27560b313c79f51ca7a3a7084d73cc4ddeb9fb17a29a33a211bd53db57090af791a
7
+ data.tar.gz: a1fb09a73e8610d562cb4f4ea5e2ce1a07ffa68f4574363f07700b0e1004823fb5f1003d6cf6276fec2eb0002894ddf4dce7c7e4c4b55773bbb729a96ec74f4c
data/changelog.md CHANGED
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.4] - 2024-04-02
11
+
12
+ ### Added
13
+
14
+ - Add `Lennarb::Router` module to manage the routes in the project. Now, the `Lennarb` class is the main class of the project.
15
+
16
+ ## [0.4.3] - 2024-04-01
17
+
18
+ ### Added
19
+
20
+ ### Remove
21
+
22
+ - Remove `Lennarb::ApplicationBase` class from the project. Now, the `Lennarb` class is the main class of the project.
23
+
24
+ ### Changed
25
+
26
+ - Improve performance of the RPS (Requests per second), memory and CPU usage.
27
+ - Change the `finish` method from `Lennarb` class to call `halt(@res.finish)` method to finish the response.
28
+
29
+ ## [0.4.2] - 2024-08-02
30
+
31
+ ### Added
32
+
33
+ - Add `header` and `options` methods to `Lennarb` and `Lennarb::ApplicatiobBase`.
34
+
35
+ ### Fix
36
+
37
+ - Fix Content-Length header to be the length of the body in the response.
38
+
8
39
  ## [0.4.1] - 2024-08-02
9
40
 
10
41
  ### Change
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Aristóteles Coutinho.
5
+
6
+ class Lennarb
7
+ module Application
8
+ class Base
9
+ # @attribute [r] _route
10
+ # @returns [RouteNode]
11
+ #
12
+ # @attribute [r] _middlewares
13
+ # @returns [Array]
14
+ #
15
+ # @attribute [r] _global_after_hooks
16
+ # @returns [Array]
17
+ #
18
+ # @attribute [r] _global_before_hooks
19
+ # @returns [Array]
20
+ #
21
+ # @attribute [r] _after_hooks
22
+ # @returns [RouteNode]
23
+ #
24
+ # @attribute [r] _before_hooks
25
+ # @returns [RouteNode]
26
+ #
27
+ attr_accessor :_route, :_global_after_hooks, :_global_before_hooks, :_after_hooks, :_before_hooks
28
+
29
+ # Initialize the Application
30
+ #
31
+ # @returns [Base]
32
+ #
33
+ def self.inherited(subclass)
34
+ subclass.instance_variable_set(:@_route, Lennarb.new)
35
+ subclass.instance_variable_set(:@_middlewares, [])
36
+ subclass.instance_variable_set(:@_global_after_hooks, [])
37
+ subclass.instance_variable_set(:@_global_before_hooks, [])
38
+ subclass.instance_variable_set(:@_after_hooks, Lennarb::RouteNode.new)
39
+ subclass.instance_variable_set(:@_before_hooks, Lennarb::RouteNode.new)
40
+ end
41
+
42
+ def self.get(...) = @_route.get(...)
43
+ def self.put(...) = @_route.put(...)
44
+ def self.post(...) = @_route.post(...)
45
+ def self.head(...) = @_route.head(...)
46
+ def self.match(...) = @_route.match(...)
47
+ def self.patch(...) = @_route.patch(...)
48
+ def self.delete(...) = @_route.delete(...)
49
+ def self.options(...) = @_route.options(...)
50
+
51
+ # @returns [Array] middlewares
52
+ def self.middlewares = @_middlewares
53
+
54
+ # Use a middleware
55
+ #
56
+ # @parameter [Object] middleware
57
+ # @parameter [Array] args
58
+ # @parameter [Block] block
59
+ #
60
+ # @returns [Array] middlewares
61
+ #
62
+ def self.use(middleware, *args, &block)
63
+ @_middlewares << [middleware, args, block]
64
+ end
65
+
66
+ # Add a before hook
67
+ #
68
+ # @parameter [String] path
69
+ # @parameter [Block] block
70
+ #
71
+ def self.before(path = nil, &block)
72
+ if path
73
+ parts = path.split('/').reject(&:empty?)
74
+ @_before_hooks.add_route(parts, :before, block)
75
+ else
76
+ @_global_before_hooks << block
77
+ end
78
+ end
79
+
80
+ # Add a after hook
81
+ #
82
+ # @parameter [String] path
83
+ # @parameter [Block] block
84
+ #
85
+ def self.after(path = nil, &block)
86
+ if path
87
+ parts = path.split('/').reject(&:empty?)
88
+ @_after_hooks.add_route(parts, :after, block)
89
+ else
90
+ @_global_after_hooks << block
91
+ end
92
+ end
93
+
94
+ # Call the application
95
+ #
96
+ # @parameter [Hash] env
97
+ #
98
+ # @returns [Array] response
99
+ #
100
+ def self.call(env)
101
+ response =
102
+ catch(:halt) do
103
+ execute_hooks(@_before_hooks, env, :before)
104
+
105
+ res = @_route.call(env)
106
+
107
+ execute_hooks(@_after_hooks, env, :after)
108
+ res
109
+ end
110
+
111
+ case response
112
+ in [404 | 404, _, _] if html_request?(env) && File.exist?('public/404.html')
113
+ env['PATH_INFO'] = '/404.html'
114
+ Rack::Static.new(-> { response }, urls: ['/404.html'], root: 'public').call(env)
115
+ in [404 | 404, _, _] then response
116
+ else
117
+ response.is_a?(Array) ? response : response.finish
118
+ end
119
+ end
120
+
121
+ # Run the Application
122
+ #
123
+ # @returns [Base] self
124
+ #
125
+ # When you use this method, the application will be frozen. And you can't add more routes after that.
126
+ # This method is used to run the application in a Rack server so, you can use the `rackup` command to run the application.
127
+ # Ex. rackup -p 3000
128
+ # This command will use the following middleware:
129
+ # - Rack::ShowExceptions
130
+ # - Rack::Lint
131
+ # - Rack::MethodOverride
132
+ # - Rack::Head
133
+ # - Rack::ContentLength
134
+ # - Rack::Static
135
+ #
136
+ def self.run!
137
+ stack = Rack::Builder.new
138
+
139
+ use Rack::ShowExceptions
140
+ use Rack::Lint
141
+ use Rack::MethodOverride
142
+ use Rack::Head
143
+ use Rack::ContentLength
144
+ use Rack::Static,
145
+ root: 'public',
146
+ urls: ['/404.html'],
147
+ header_rules: [
148
+ [200, %w[html], { 'content-type' => 'text/html; charset=utf-8' }]
149
+ ]
150
+
151
+ middlewares.each do |(middleware, args, block)|
152
+ stack.use(middleware, *args, &block)
153
+ end
154
+
155
+ stack.run @_route
156
+
157
+ @_route.freeze!
158
+
159
+ @_route = stack.to_app.freeze
160
+
161
+ self
162
+ end
163
+
164
+ # Redirect to a path
165
+ #
166
+ # @parameter [String] path
167
+ # @parameter [Integer] status default is 302
168
+ # @parameter [Hash] env (optional)
169
+ #
170
+ def self.redirect(path, status = 302, _env = nil)
171
+ response = Rack::Response.new([], status, 'location' => path)
172
+ throw :halt, response.finish
173
+ end
174
+
175
+ # Execute the hooks
176
+ #
177
+ # @parameter [RouteNode] hook_route
178
+ # @parameter [Hash] env
179
+ # @parameter [Symbol] action
180
+ #
181
+ # @returns [void]
182
+ #
183
+ def self.execute_hooks(hook_route, env, action)
184
+ execute_global_hooks(env, action)
185
+
186
+ execute_route_hooks(hook_route, env, action)
187
+ end
188
+
189
+ # Execute the global hooks
190
+ #
191
+ # @parameter [Hash] env
192
+ # @parameter [Symbol] action
193
+ #
194
+ # @returns [void]
195
+ #
196
+ def self.execute_global_hooks(env, action)
197
+ global_hooks = action == :before ? @_global_before_hooks : @_global_after_hooks
198
+ global_hooks.each { |hook| hook.call(env) }
199
+ end
200
+
201
+ # Execute the route hooks
202
+ #
203
+ # @parameter [RouteNode] hook_route
204
+ # @parameter [Hash] env
205
+ # @parameter [Symbol] action
206
+ #
207
+ # @returns [void]
208
+ #
209
+ def self.execute_route_hooks(hook_route, env, action)
210
+ parts = parse_path(env)
211
+ return unless parts
212
+
213
+ block, = hook_route.match_route(parts, action)
214
+ block&.call(env)
215
+ end
216
+
217
+ # Parse the path
218
+ #
219
+ # @parameter [Hash] env
220
+ #
221
+ # @returns [Array] parts
222
+ #
223
+ def self.parse_path(env) = env[Rack::PATH_INFO]&.split('/')&.reject(&:empty?)
224
+
225
+ # Check if the request is a HTML request
226
+ #
227
+ # @parameter [Hash] env
228
+ #
229
+ # @returns [Boolean]
230
+ #
231
+ def self.html_request?(env) = env['HTTP_ACCEPT']&.include?('text/html')
232
+
233
+ private_class_method :execute_hooks, :execute_global_hooks, :execute_route_hooks, :parse_path, :html_request?
234
+ end
235
+ end
236
+ end
@@ -125,6 +125,8 @@ class Lennarb
125
125
  def redirect(path, status = 302)
126
126
  @headers[LOCATION] = path
127
127
  @status = status
128
+
129
+ throw :halt, finish
128
130
  end
129
131
 
130
132
  # Finish the response
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2023-2024, by Aristóteles Coutinho.
5
5
 
6
6
  class Lennarb
7
- VERSION = '0.4.2'
7
+ VERSION = '0.5.0'
8
8
 
9
9
  public_constant :VERSION
10
10
  end
data/lib/lennarb.rb CHANGED
@@ -12,6 +12,7 @@ require 'rack'
12
12
 
13
13
  # Base class for Lennarb
14
14
  #
15
+ require_relative 'lennarb/application/base'
15
16
  require_relative 'lennarb/request'
16
17
  require_relative 'lennarb/response'
17
18
  require_relative 'lennarb/route_node'
@@ -25,9 +26,8 @@ class Lennarb
25
26
  # @attribute [r] root
26
27
  # @returns [RouteNode]
27
28
  #
28
- attr_reader :root
29
+ attr_reader :_root
29
30
 
30
- @routes = []
31
31
  # Initialize the application
32
32
  #
33
33
  # @yield { ... } The application
@@ -35,7 +35,7 @@ class Lennarb
35
35
  # @returns [Lennarb]
36
36
  #
37
37
  def initialize
38
- @root = RouteNode.new
38
+ @_root = RouteNode.new
39
39
  yield self if block_given?
40
40
  end
41
41
 
@@ -55,19 +55,27 @@ class Lennarb
55
55
  # @returns [Array] response
56
56
  #
57
57
  def call(env)
58
- http_method = env.fetch('REQUEST_METHOD').to_sym
59
- parts = SplitPath[env.fetch('PATH_INFO')]
58
+ http_method = env[Rack::REQUEST_METHOD].to_sym
59
+ parts = SplitPath[env[Rack::PATH_INFO]]
60
60
 
61
- block, params = @root.match_route(parts, http_method)
61
+ block, params = @_root.match_route(parts, http_method)
62
62
  return [404, { 'content-type' => 'text/plain' }, ['Not Found']] unless block
63
63
 
64
- res = Response.new
65
- req = Request.new(env, params)
66
- instance_exec(req, res, &block)
64
+ @res = Response.new
65
+ req = Request.new(env, params)
67
66
 
68
- res.finish
67
+ catch(:halt) do
68
+ instance_exec(req, @res, &block)
69
+ @res.finish
70
+ end
69
71
  end
70
72
 
73
+ # Freeze the routes
74
+ #
75
+ # @returns [void]
76
+ #
77
+ def freeze! = @_root.freeze
78
+
71
79
  # Add a routes
72
80
  #
73
81
  # @parameter [String] path
@@ -75,12 +83,12 @@ class Lennarb
75
83
  #
76
84
  # @returns [void]
77
85
  #
78
- def get(path, &block) = add_route(path, :GET, block)
79
- def put(path, &block) = add_route(path, :PUT, block)
80
- def post(path, &block) = add_route(path, :POST, block)
81
- def head(path, &block) = add_route(path, :HEAD, block)
82
- def patch(path, &block) = add_route(path, :PATCH, block)
83
- def delete(path, &block) = add_route(path, :DELETE, block)
86
+ def get(path, &block) = add_route(path, :GET, block)
87
+ def put(path, &block) = add_route(path, :PUT, block)
88
+ def post(path, &block) = add_route(path, :POST, block)
89
+ def head(path, &block) = add_route(path, :HEAD, block)
90
+ def patch(path, &block) = add_route(path, :PATCH, block)
91
+ def delete(path, &block) = add_route(path, :DELETE, block)
84
92
  def options(path, &block) = add_route(path, :OPTIONS, block)
85
93
 
86
94
  private
@@ -95,61 +103,6 @@ class Lennarb
95
103
  #
96
104
  def add_route(path, http_method, block)
97
105
  parts = SplitPath[path]
98
- @root.add_route(parts, http_method, block)
99
- end
100
-
101
- # Base class for Lennarb applications
102
- #
103
- class ApplicationBase < Lennarb
104
- @routes = []
105
-
106
- class << self
107
- attr_reader :routes
108
-
109
- # Add a route
110
- #
111
- # @parameter [String] path
112
- # @parameter [Proc] block
113
- #
114
- # @returns [void]
115
- #
116
- %i[get post put patch delete].each do |http_method|
117
- define_method(http_method) do |path, &block|
118
- routes << { method: http_method, path:, proc: block }
119
- end
120
- end
121
- end
122
-
123
- # Inherited hook
124
- #
125
- # @parameter [Class] subclass
126
- #
127
- # @returns [void]
128
- #
129
- def self.inherited(subclass)
130
- super
131
- subclass.instance_variable_set(:@routes, routes.dup)
132
- end
133
-
134
- # Initialize the application
135
- #
136
- # @returns [ApplicationBase]
137
- #
138
- def initialize
139
- super
140
- setup_routes
141
- end
142
-
143
- private
144
-
145
- # Setup the routes
146
- #
147
- # @returns [void]
148
- #
149
- def setup_routes
150
- self.class.routes.each do |route_info|
151
- __send__(route_info[:method], route_info[:path], &route_info[:proc])
152
- end
153
- end
106
+ @_root.add_route(parts, http_method, block)
154
107
  end
155
108
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lennarb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aristóteles Coutinho
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-08 00:00:00.000000000 Z
11
+ date: 2024-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -44,6 +44,34 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 3.0.8
47
+ - !ruby/object:Gem::Dependency
48
+ name: rack-protection
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '4.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rack-session
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
47
75
  - !ruby/object:Gem::Dependency
48
76
  name: bake
49
77
  requirement: !ruby/object:Gem::Requirement
@@ -120,14 +148,14 @@ dependencies:
120
148
  requirements:
121
149
  - - "~>"
122
150
  - !ruby/object:Gem::Version
123
- version: '13.0'
151
+ version: '13.1'
124
152
  type: :development
125
153
  prerelease: false
126
154
  version_requirements: !ruby/object:Gem::Requirement
127
155
  requirements:
128
156
  - - "~>"
129
157
  - !ruby/object:Gem::Version
130
- version: '13.0'
158
+ version: '13.1'
131
159
  - !ruby/object:Gem::Dependency
132
160
  name: rack-test
133
161
  requirement: !ruby/object:Gem::Requirement
@@ -180,6 +208,7 @@ files:
180
208
  - changelog.md
181
209
  - exe/lenna
182
210
  - lib/lennarb.rb
211
+ - lib/lennarb/application/base.rb
183
212
  - lib/lennarb/request.rb
184
213
  - lib/lennarb/response.rb
185
214
  - lib/lennarb/route_node.rb
@@ -210,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
210
239
  - !ruby/object:Gem::Version
211
240
  version: '0'
212
241
  requirements: []
213
- rubygems_version: 3.5.5
242
+ rubygems_version: 3.5.9
214
243
  signing_key:
215
244
  specification_version: 4
216
245
  summary: A lightweight and experimental web framework for Ruby.