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 +4 -4
- data/changelog.md +31 -0
- data/lib/lennarb/application/base.rb +236 -0
- data/lib/lennarb/response.rb +2 -0
- data/lib/lennarb/version.rb +1 -1
- data/lib/lennarb.rb +25 -72
- metadata +34 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 888f84e566de65f5c87013ff56ac3fe485f59acba6f1cc39862178894cf101fa
|
4
|
+
data.tar.gz: 8703693e316b83f747f927c23b07d56f6dd571f813d8c00901432cf6c941d1f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/lennarb/response.rb
CHANGED
data/lib/lennarb/version.rb
CHANGED
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 :
|
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
|
-
@
|
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
|
59
|
-
parts = SplitPath[env
|
58
|
+
http_method = env[Rack::REQUEST_METHOD].to_sym
|
59
|
+
parts = SplitPath[env[Rack::PATH_INFO]]
|
60
60
|
|
61
|
-
block, params = @
|
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
|
66
|
-
instance_exec(req, res, &block)
|
64
|
+
@res = Response.new
|
65
|
+
req = Request.new(env, params)
|
67
66
|
|
68
|
-
|
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)
|
79
|
-
def put(path, &block)
|
80
|
-
def post(path, &block)
|
81
|
-
def head(path, &block)
|
82
|
-
def patch(path, &block)
|
83
|
-
def delete(path, &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
|
-
@
|
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
|
+
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-
|
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.
|
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.
|
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.
|
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.
|