lennarb 0.4.2 → 0.5.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: 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.