lennarb 1.4.0 → 1.4.1
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/.github/workflows/test.yaml +2 -1
- data/Rakefile +4 -2
- data/changelog.md +38 -0
- data/guides/getting-started/readme.md +41 -27
- data/guides/response/readme.md +6 -6
- data/lennarb.gemspec +3 -0
- data/lib/lennarb/app.rb +172 -0
- data/lib/lennarb/config.rb +34 -0
- data/lib/lennarb/constansts.rb +6 -1
- data/lib/lennarb/environment.rb +76 -0
- data/lib/lennarb/request.rb +192 -24
- data/lib/lennarb/request_handler.rb +31 -0
- data/lib/lennarb/response.rb +11 -7
- data/lib/lennarb/route_node.rb +46 -4
- data/lib/lennarb/routes.rb +71 -0
- data/lib/lennarb/version.rb +2 -4
- data/lib/lennarb.rb +16 -68
- data/readme.md +16 -8
- metadata +49 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd39c745c65bf722a7075f57bbd36a2a603b659ddecb23bac33b7733c0b912e2
|
4
|
+
data.tar.gz: 723a60bc0e9cf0ed60c3f548e8e9004310a29c3c36c63b6ab55308ca99c83da3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 901cfe54fe27ea6addd4b48690f3cd12b73a9f85e05066f9382f6467e65e537760509958bfb4ffd23cc2ab523168428dd3c2b50027c523b8cfb15b3c1af1b4a0
|
7
|
+
data.tar.gz: c877fb10d4a3f918016390b43fcc54ed3b70fb8c38577b23a4c34b1820f3c2d898c685f738de1f6c7ff6e2c25066b2fb95e8020ef7a00c22a02f0c5f847fa76b
|
data/.github/workflows/test.yaml
CHANGED
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
require "standard/rake"
|
2
3
|
require "rake/testtask"
|
3
4
|
|
4
|
-
Rake::TestTask.new
|
5
|
+
Rake::TestTask.new do |t|
|
5
6
|
t.libs << "test"
|
6
7
|
t.libs << "lib"
|
7
8
|
t.test_files = FileList["test/**/*_test.rb"]
|
9
|
+
t.verbose = true
|
8
10
|
end
|
9
11
|
|
10
|
-
task default:
|
12
|
+
task default: :test
|
data/changelog.md
CHANGED
@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [1.4.1] - 2025-02-23
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Add support to mount routes. Now, you can centralize the routes in a single file and mount them in the main application. Ex.
|
15
|
+
|
16
|
+
```rb
|
17
|
+
class PostsController
|
18
|
+
extend Lennarb::Routes::Mixin
|
19
|
+
|
20
|
+
get '/posts' do |req, res|
|
21
|
+
res.html('Posts')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
SampleApp = Lennarb.new do |router|
|
26
|
+
mount PostsController
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
The `mount` method will add the routes from the `PostsController` class to the main application. You can use the `mount` method with multiple classes, ex. `mount PostsController, CommentsController`.
|
31
|
+
|
32
|
+
- Add `Lennarb::Environment` module to manage the environment variables in the project. Now, the `Lennarb` class is the main class of the project.
|
33
|
+
- Add `Lennarb::Config` module to manage the configuration in the project. Now, the `Lennarb` class is the main class of the project.
|
34
|
+
- Add `Lennarb::App` class.
|
35
|
+
- Lint the code with `standard` gem on the CI/CD pipeline.
|
36
|
+
|
37
|
+
### Changed
|
38
|
+
|
39
|
+
- Convert the `Lennarb` class to a module. Now, the `App` class is the main class of the project.
|
40
|
+
- Move the request process to `Lennarb::RequestHandler` class.
|
41
|
+
- Improve the method `merge!` from `Lennarb::RouterNode` to prevent the duplication of the routes.
|
42
|
+
|
43
|
+
### Fixed
|
44
|
+
|
45
|
+
- Software design issues.
|
46
|
+
|
10
47
|
## [1.4.0] - 2025-02-09
|
11
48
|
|
12
49
|
### Changed
|
@@ -177,6 +214,7 @@ end
|
|
177
214
|
- Add `console` gem to print the logs in the console.
|
178
215
|
|
179
216
|
- Add CLI module to:
|
217
|
+
|
180
218
|
- Create a new project with `lennarb new` command.
|
181
219
|
- Run the server with `lennarb server` command.
|
182
220
|
|
@@ -27,15 +27,17 @@ Create a new file named `config.ru`:
|
|
27
27
|
```ruby
|
28
28
|
require 'lennarb'
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
MyApp = Lennarb::App.new do
|
31
|
+
routes
|
32
|
+
get '/' do |req, res|
|
33
|
+
res.status = 200
|
34
|
+
res.html('<h1>Welcome to Lennarb!</h1>')
|
35
|
+
end
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
|
38
|
-
run
|
39
|
+
MyApp.initialize!
|
40
|
+
run App
|
39
41
|
```
|
40
42
|
|
41
43
|
Start the server:
|
@@ -84,16 +86,16 @@ end
|
|
84
86
|
Routes are defined using HTTP method helpers:
|
85
87
|
|
86
88
|
```ruby
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
Lennarb::App.new do
|
90
|
+
routes do
|
91
|
+
get '/' do |req, res|
|
92
|
+
res.html('Home page')
|
93
|
+
end
|
94
|
+
|
95
|
+
get '/users/:id' do |req, res|
|
96
|
+
user_id = req.params[:id]
|
97
|
+
res.json("{\"id\": #{user_id}}")
|
98
|
+
end
|
97
99
|
end
|
98
100
|
end
|
99
101
|
```
|
@@ -103,7 +105,7 @@ end
|
|
103
105
|
Parameters from dynamic route segments are available in `req.params`:
|
104
106
|
|
105
107
|
```ruby
|
106
|
-
|
108
|
+
get '/hello/:name' do |req, res|
|
107
109
|
name = req.params[:name]
|
108
110
|
res.text("Hello, #{name}!")
|
109
111
|
end
|
@@ -117,20 +119,26 @@ Lennarb is thread-safe by design:
|
|
117
119
|
- The router tree is frozen after initialization
|
118
120
|
- Response objects are created per-request
|
119
121
|
|
120
|
-
## Application
|
122
|
+
## Application Life-cycle
|
121
123
|
|
122
124
|
### Initialization
|
123
125
|
|
124
126
|
```ruby
|
125
|
-
|
126
|
-
# Define routes
|
127
|
+
Myapp = Lennarb::App.new do
|
128
|
+
# Define routes
|
129
|
+
routes do
|
130
|
+
end
|
131
|
+
|
132
|
+
# Define Configurations
|
133
|
+
config do
|
134
|
+
end
|
127
135
|
end
|
128
136
|
|
129
137
|
# Initialize and freeze the application
|
130
|
-
|
138
|
+
MyApp.initialize!
|
131
139
|
```
|
132
140
|
|
133
|
-
The `
|
141
|
+
The `initialize!` method:
|
134
142
|
|
135
143
|
- Loads environment-specific dependencies
|
136
144
|
- Freezes the route tree
|
@@ -140,6 +148,12 @@ The `initializer!` method:
|
|
140
148
|
|
141
149
|
Lennarb uses the `LENNA_ENV` environment variable (defaults to "development"):
|
142
150
|
|
151
|
+
It can be set using the following environment variables:
|
152
|
+
|
153
|
+
- `LENNA_ENV`
|
154
|
+
- `APP_ENV`
|
155
|
+
- `RACK_ENV`
|
156
|
+
|
143
157
|
```bash
|
144
158
|
LENNA_ENV=production rackup
|
145
159
|
```
|
@@ -149,7 +163,7 @@ LENNA_ENV=production rackup
|
|
149
163
|
Lennarb provides basic error handling:
|
150
164
|
|
151
165
|
```ruby
|
152
|
-
|
166
|
+
get '/api' do |req, res|
|
153
167
|
# Errors are caught and return 500 with error message
|
154
168
|
raise "Something went wrong"
|
155
169
|
end
|
@@ -162,18 +176,18 @@ Default error responses:
|
|
162
176
|
|
163
177
|
## Best Practices
|
164
178
|
|
165
|
-
1. **Always call
|
179
|
+
1. **Always call initialize!**
|
166
180
|
|
167
181
|
```ruby
|
168
|
-
app = Lennarb.new
|
169
|
-
app.
|
182
|
+
app = Lennarb::App.new
|
183
|
+
app.initialize!
|
170
184
|
run app
|
171
185
|
```
|
172
186
|
|
173
187
|
2. **Set response status**
|
174
188
|
|
175
189
|
```ruby
|
176
|
-
|
190
|
+
get '/api' do |req, res|
|
177
191
|
res.status = 200
|
178
192
|
res.json('{"status": "ok"}')
|
179
193
|
end
|
data/guides/response/readme.md
CHANGED
@@ -10,19 +10,19 @@ You can use the `res` object to send a response to the client.
|
|
10
10
|
```ruby
|
11
11
|
# app.rb
|
12
12
|
|
13
|
-
|
13
|
+
get '/' do |req, res|
|
14
14
|
res.html 'Hello World'
|
15
15
|
end
|
16
16
|
```
|
17
17
|
|
18
18
|
## Content Types
|
19
19
|
|
20
|
-
|
20
|
+
Lennarb supports the following content types:
|
21
21
|
|
22
22
|
```ruby
|
23
23
|
# app.rb
|
24
24
|
|
25
|
-
|
25
|
+
get '/' do |req, res|
|
26
26
|
res.html 'Hello World'
|
27
27
|
res.json '{"message": "Hello World"}'
|
28
28
|
res.text 'Hello World'
|
@@ -43,7 +43,7 @@ You can use the `res.write` method to write to the response body:
|
|
43
43
|
```ruby
|
44
44
|
# app.rb
|
45
45
|
|
46
|
-
|
46
|
+
get '/' do |req, res|
|
47
47
|
res.write 'Hello World'
|
48
48
|
end
|
49
49
|
```
|
@@ -53,7 +53,7 @@ JSON example:
|
|
53
53
|
```ruby
|
54
54
|
# app.rb
|
55
55
|
|
56
|
-
|
56
|
+
post '/posts' do |req, res|
|
57
57
|
req.params # => { name: 'Lenna' }
|
58
58
|
name = req.params[:name]
|
59
59
|
|
@@ -76,7 +76,7 @@ You can redirect the client using the `res.redirect` method:
|
|
76
76
|
```ruby
|
77
77
|
# app.ruby
|
78
78
|
|
79
|
-
|
79
|
+
get '/' do |req, res|
|
80
80
|
# Stuff code here...
|
81
81
|
res.redirect '/hello'
|
82
82
|
end
|
data/lennarb.gemspec
CHANGED
@@ -31,14 +31,17 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_dependency "bigdecimal"
|
32
32
|
spec.add_dependency "colorize", "~> 1.1"
|
33
33
|
spec.add_dependency "rack", "~> 3.1"
|
34
|
+
spec.add_dependency "superconfig"
|
34
35
|
spec.add_development_dependency "bundler"
|
35
36
|
spec.add_development_dependency "covered"
|
36
37
|
spec.add_development_dependency "simplecov"
|
37
38
|
spec.add_development_dependency "minitest"
|
39
|
+
spec.add_development_dependency "minitest-utils"
|
38
40
|
spec.add_development_dependency "rack-test"
|
39
41
|
spec.add_development_dependency "rake"
|
40
42
|
spec.add_development_dependency "standard"
|
41
43
|
spec.add_development_dependency "standard-custom"
|
42
44
|
spec.add_development_dependency "standard-performance"
|
43
45
|
spec.add_development_dependency "m"
|
46
|
+
spec.add_development_dependency "debug"
|
44
47
|
end
|
data/lib/lennarb/app.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
module Lennarb
|
2
|
+
class App
|
3
|
+
# This error is raised whenever the app is initialized more than once.
|
4
|
+
AlreadyInitializedError = Class.new(StandardError)
|
5
|
+
|
6
|
+
# The root app directory of the app.
|
7
|
+
#
|
8
|
+
# @returns [Pathname]
|
9
|
+
#
|
10
|
+
attr_accessor :root
|
11
|
+
|
12
|
+
# The current environment. Defaults to "development".
|
13
|
+
# It can be set using the following environment variables:
|
14
|
+
#
|
15
|
+
# - `LENNA_ENV`
|
16
|
+
# - `APP_ENV`
|
17
|
+
# - `RACK_ENV`
|
18
|
+
#
|
19
|
+
# @returns [Lennarb::Environment]
|
20
|
+
#
|
21
|
+
attr_reader :env
|
22
|
+
|
23
|
+
def initialize(&)
|
24
|
+
@initialized = false
|
25
|
+
self.root = Pathname.pwd
|
26
|
+
self.env = compute_env
|
27
|
+
instance_eval(&) if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set the current environment. See {Lennarb::Environment} for more details.
|
31
|
+
#
|
32
|
+
# @parameter [Hash] env
|
33
|
+
#
|
34
|
+
def env=(env)
|
35
|
+
raise AlreadyInitializedError if initialized?
|
36
|
+
|
37
|
+
@env = Environment.new(env)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Mount an app at a specific path.
|
41
|
+
#
|
42
|
+
# @parameter [Object] The controller|app to mount.
|
43
|
+
#
|
44
|
+
# @returns [void]
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
#
|
48
|
+
# class PostController
|
49
|
+
# extend Lennarb::Routes::Mixin
|
50
|
+
#
|
51
|
+
# get "/post/:id" do |req, res|
|
52
|
+
# res.text("Post ##{req.params[:id]}")
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# MyApp = Lennarb::App.new do
|
57
|
+
# routes do
|
58
|
+
# mount PostController
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
def mount(*controllers)
|
62
|
+
controllers.each do |controller|
|
63
|
+
raise ArgumentError, "Controller must respond to :routes" unless controller.respond_to?(:routes)
|
64
|
+
|
65
|
+
self.controllers << controller
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Define the app's configuration. See {Lennarb::Config}.
|
70
|
+
#
|
71
|
+
# @returns [Lennarb::Config]
|
72
|
+
#
|
73
|
+
# @example Run config on every environment
|
74
|
+
# app.config do
|
75
|
+
# mandatory :database_url, string
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# @example Run config on every a specific environment
|
79
|
+
# app.config :development do
|
80
|
+
# set :domain, "example.dev"
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# @example Run config on every a specific environment
|
84
|
+
# app.config :development, :test do
|
85
|
+
# set :domain, "example.dev"
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
def config(*envs, &)
|
89
|
+
@config ||= Config.new
|
90
|
+
|
91
|
+
write = block_given? &&
|
92
|
+
(envs.map(&:to_sym).include?(env.to_sym) || envs.empty?)
|
93
|
+
|
94
|
+
@config.instance_eval(&) if write
|
95
|
+
|
96
|
+
@config
|
97
|
+
end
|
98
|
+
|
99
|
+
# Define the app's route. See {Lennarb::RouteNode} for more details.
|
100
|
+
#
|
101
|
+
# @returns [Lennarb::RouteNode]
|
102
|
+
#
|
103
|
+
def routes(&)
|
104
|
+
@routes ||= Routes.new
|
105
|
+
@routes.instance_eval(&) if block_given?
|
106
|
+
@routes
|
107
|
+
end
|
108
|
+
|
109
|
+
# The Rack app.
|
110
|
+
#
|
111
|
+
def app
|
112
|
+
@app ||= begin
|
113
|
+
request_handler = RequestHandler.new(self)
|
114
|
+
|
115
|
+
Rack::Builder.app do
|
116
|
+
run request_handler
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Store mounted app's
|
122
|
+
#
|
123
|
+
def controllers
|
124
|
+
@controllers ||= []
|
125
|
+
end
|
126
|
+
alias_method :mounted_apps, :controllers
|
127
|
+
|
128
|
+
# Check if the app is initialized.
|
129
|
+
#
|
130
|
+
# @returns [Boolean]
|
131
|
+
#
|
132
|
+
def initialized? = @initialized
|
133
|
+
|
134
|
+
# Initialize the app.
|
135
|
+
#
|
136
|
+
# @returns [void]
|
137
|
+
#
|
138
|
+
def initialize!
|
139
|
+
raise AlreadyInitializedError if initialized?
|
140
|
+
|
141
|
+
controllers.each do
|
142
|
+
routes.store.merge!(it.routes.store)
|
143
|
+
end
|
144
|
+
|
145
|
+
@initialized = true
|
146
|
+
|
147
|
+
app.freeze
|
148
|
+
routes.freeze
|
149
|
+
end
|
150
|
+
|
151
|
+
# Call the app.
|
152
|
+
#
|
153
|
+
# @parameter [Hash] env
|
154
|
+
#
|
155
|
+
def call(env)
|
156
|
+
env[RACK_LENNA_APP] = self
|
157
|
+
Dir.chdir(root) { return app.call(env) }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Compute the current environment.
|
161
|
+
#
|
162
|
+
# @returns [String]
|
163
|
+
#
|
164
|
+
# @private
|
165
|
+
#
|
166
|
+
private def compute_env
|
167
|
+
env = ENV_NAMES.map { ENV[_1] }.compact.first.to_s
|
168
|
+
|
169
|
+
env.empty? ? "development" : env
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Lennarb
|
2
|
+
# The configuration for the application.
|
3
|
+
# It uses {https://rubygems.org/gems/superconfig SuperConfig} to define the
|
4
|
+
# configuration.
|
5
|
+
class Config < SuperConfig::Base
|
6
|
+
MissingEnvironmentVariable = Class.new(StandardError)
|
7
|
+
MissingCallable = Class.new(StandardError)
|
8
|
+
|
9
|
+
undef_method :credential
|
10
|
+
|
11
|
+
def initialize(**)
|
12
|
+
block = proc { true }
|
13
|
+
super(**, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def to_s = "#<Lennarb::Config>"
|
18
|
+
|
19
|
+
# @private
|
20
|
+
def mandatory(*, **)
|
21
|
+
super
|
22
|
+
rescue SuperConfig::MissingEnvironmentVariable => error
|
23
|
+
raise MissingEnvironmentVariable, error.message
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
def property(*, **, &)
|
28
|
+
super
|
29
|
+
rescue SuperConfig::MissingCallable
|
30
|
+
raise MissingCallable,
|
31
|
+
"arg[1] must respond to #call or a block must be provided"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/lennarb/constansts.rb
CHANGED
@@ -1 +1,6 @@
|
|
1
|
-
|
1
|
+
module Lennarb
|
2
|
+
RACK_LENNA_APP = "lennarb.app"
|
3
|
+
ENV_NAMES = %w[LENNA_ENV APP_ENV RACK_ENV]
|
4
|
+
HTTP_METHODS = %i[GET POST PUT PATCH DELETE HEAD OPTIONS]
|
5
|
+
CONTENT_TYPE = {HTML: "text/html", TEXT: "text/plain", JSON: "application/json"}
|
6
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Lennarb
|
2
|
+
class Environment
|
3
|
+
NAMES = %i[development test production local]
|
4
|
+
|
5
|
+
# Returns the name of the environment.
|
6
|
+
# @parameter name [Symbol]
|
7
|
+
#
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# Initialize the environment.
|
11
|
+
# @parameter name [String, Symbol] The name of the environment.
|
12
|
+
#
|
13
|
+
def initialize(name)
|
14
|
+
@name = name.to_sym
|
15
|
+
|
16
|
+
return if NAMES.include?(@name)
|
17
|
+
|
18
|
+
raise ArgumentError, "Invalid environment: #{@name.inspect}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns true if the environment is development.
|
22
|
+
#
|
23
|
+
def development? = name == :development
|
24
|
+
|
25
|
+
# Returns true if the environment is test.
|
26
|
+
#
|
27
|
+
def test? = name == :test
|
28
|
+
|
29
|
+
# Returns true if the environment is production.
|
30
|
+
#
|
31
|
+
def production? = name == :production
|
32
|
+
|
33
|
+
# Returns true if the environment is local (either `test` or `development`).
|
34
|
+
#
|
35
|
+
def local? = test? || development?
|
36
|
+
|
37
|
+
# Implements equality for the environment.
|
38
|
+
#
|
39
|
+
def ==(other) = name == other || name.to_s == other
|
40
|
+
alias_method :eql?, :==
|
41
|
+
alias_method :equal?, :==
|
42
|
+
alias_method :===, :==
|
43
|
+
|
44
|
+
# Returns the name of the environment as a symbol.
|
45
|
+
# @returns [Symbol]
|
46
|
+
#
|
47
|
+
def to_sym = name
|
48
|
+
|
49
|
+
# Returns the name of the environment as a string.
|
50
|
+
# @returns [String]
|
51
|
+
#
|
52
|
+
def to_s = name.to_s
|
53
|
+
|
54
|
+
# Returns the name of the environment as a string.
|
55
|
+
# @returns [String]
|
56
|
+
def inspect = to_s.inspect
|
57
|
+
|
58
|
+
# Yields a block if the environment is the same as the given environment.
|
59
|
+
# - To match all environments use `:any` or `:all`.
|
60
|
+
# - To match local environments use `:local`.
|
61
|
+
# @param envs [Array<Symbol>] The environment(s) to check.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# app.env.on(:development) do
|
65
|
+
# # Code to run in development
|
66
|
+
# end
|
67
|
+
def on(*envs)
|
68
|
+
matched = envs.include?(:any) ||
|
69
|
+
envs.include?(:all) ||
|
70
|
+
envs.include?(name) ||
|
71
|
+
(envs.include?(:local) && local?)
|
72
|
+
|
73
|
+
yield if matched
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/lennarb/request.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
|
1
|
+
module Lennarb
|
2
2
|
class Request < Rack::Request
|
3
3
|
# The environment variables of the request
|
4
4
|
#
|
5
5
|
# @returns [Hash]
|
6
|
-
#
|
7
6
|
attr_reader :env
|
8
7
|
|
9
8
|
# Initialize the request object
|
@@ -15,71 +14,240 @@ class Lennarb
|
|
15
14
|
#
|
16
15
|
def initialize(env, route_params = {})
|
17
16
|
super(env)
|
18
|
-
@route_params = route_params
|
17
|
+
@route_params = route_params || {}
|
19
18
|
end
|
20
19
|
|
21
|
-
# Get the request
|
20
|
+
# Get the request parameters merged with route parameters
|
22
21
|
#
|
23
|
-
# @returns [
|
22
|
+
# @returns [Hash]
|
24
23
|
#
|
25
|
-
def params
|
24
|
+
def params
|
25
|
+
@params ||= super.merge(@route_params)&.transform_keys(&:to_sym)
|
26
|
+
end
|
26
27
|
|
27
|
-
# Get the request path
|
28
|
+
# Get the request path without query string
|
28
29
|
#
|
29
30
|
# @returns [String]
|
30
31
|
#
|
31
|
-
def path
|
32
|
+
def path
|
33
|
+
@path ||= super.split("?").first
|
34
|
+
end
|
32
35
|
|
33
36
|
# Read the body of the request
|
34
37
|
#
|
35
38
|
# @returns [String]
|
36
39
|
#
|
37
|
-
def body
|
40
|
+
def body
|
41
|
+
@body ||= super.read
|
42
|
+
end
|
38
43
|
|
39
44
|
# Get the query parameters
|
40
45
|
#
|
41
46
|
# @returns [Hash]
|
42
47
|
#
|
43
48
|
def query_params
|
44
|
-
@query_params ||= Rack::Utils.parse_nested_query(query_string).transform_keys(&:to_sym)
|
49
|
+
@query_params ||= Rack::Utils.parse_nested_query(query_string || "").transform_keys(&:to_sym)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set a value in the environment
|
53
|
+
#
|
54
|
+
# @parameter [String] key
|
55
|
+
# @parameter [Object] value
|
56
|
+
# @returns [Object] the value
|
57
|
+
#
|
58
|
+
def []=(key, value)
|
59
|
+
env[key] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get a value from the environment
|
63
|
+
#
|
64
|
+
# @parameter [String] key
|
65
|
+
# @returns [Object]
|
66
|
+
#
|
67
|
+
def [](key)
|
68
|
+
env[key]
|
45
69
|
end
|
46
70
|
|
47
71
|
# Get the headers of the request
|
48
72
|
#
|
73
|
+
# @returns [Hash]
|
74
|
+
#
|
49
75
|
def headers
|
50
76
|
@headers ||= env.select { |key, _| key.start_with?("HTTP_") }
|
51
77
|
end
|
52
78
|
|
53
|
-
|
79
|
+
# Get the client IP address
|
80
|
+
#
|
81
|
+
# @returns [String]
|
82
|
+
#
|
83
|
+
def ip
|
84
|
+
ip_address
|
85
|
+
end
|
54
86
|
|
55
|
-
|
87
|
+
# Check if the request is secure (HTTPS)
|
88
|
+
#
|
89
|
+
# @returns [Boolean]
|
90
|
+
#
|
91
|
+
def secure?
|
92
|
+
scheme == "https"
|
93
|
+
end
|
56
94
|
|
57
|
-
|
95
|
+
# Shorthand methods for common headers
|
58
96
|
|
59
|
-
|
97
|
+
# Get the user agent
|
98
|
+
#
|
99
|
+
# @returns [String, nil]
|
100
|
+
#
|
101
|
+
def user_agent
|
102
|
+
env["HTTP_USER_AGENT"]
|
103
|
+
end
|
60
104
|
|
61
|
-
|
105
|
+
# Get the accept header
|
106
|
+
#
|
107
|
+
# @returns [String, nil]
|
108
|
+
#
|
109
|
+
def accept
|
110
|
+
env["HTTP_ACCEPT"]
|
111
|
+
end
|
62
112
|
|
63
|
-
|
113
|
+
# Get the referer header
|
114
|
+
#
|
115
|
+
# @returns [String, nil]
|
116
|
+
#
|
117
|
+
def referer
|
118
|
+
env["HTTP_REFERER"]
|
119
|
+
end
|
64
120
|
|
65
|
-
|
121
|
+
# Get the host header
|
122
|
+
#
|
123
|
+
# @returns [String, nil]
|
124
|
+
#
|
125
|
+
def host
|
126
|
+
env["HTTP_HOST"]
|
127
|
+
end
|
66
128
|
|
67
|
-
|
129
|
+
# Get the content length header
|
130
|
+
#
|
131
|
+
# @returns [String, nil]
|
132
|
+
#
|
133
|
+
def content_length
|
134
|
+
env["HTTP_CONTENT_LENGTH"]
|
135
|
+
end
|
68
136
|
|
69
|
-
|
137
|
+
# Get the content type header
|
138
|
+
#
|
139
|
+
# @returns [String, nil]
|
140
|
+
#
|
141
|
+
def content_type
|
142
|
+
env["HTTP_CONTENT_TYPE"]
|
143
|
+
end
|
70
144
|
|
71
|
-
|
72
|
-
|
145
|
+
# Check if the request is an XHR request
|
146
|
+
#
|
147
|
+
# @returns [Boolean]
|
148
|
+
#
|
149
|
+
def xhr?
|
150
|
+
env["HTTP_X_REQUESTED_WITH"]&.casecmp("XMLHttpRequest")&.zero? || false
|
73
151
|
end
|
74
152
|
|
75
|
-
|
76
|
-
|
153
|
+
# Check if the request is a JSON request
|
154
|
+
#
|
155
|
+
# @returns [Boolean]
|
156
|
+
#
|
157
|
+
def json?
|
158
|
+
content_type&.include?("application/json")
|
159
|
+
end
|
160
|
+
|
161
|
+
# Parse JSON body if content type is application/json
|
162
|
+
#
|
163
|
+
# @returns [Hash, nil]
|
164
|
+
#
|
165
|
+
def json_body
|
166
|
+
return nil unless json?
|
167
|
+
@json_body ||= begin
|
168
|
+
require "json"
|
169
|
+
JSON.parse(body, symbolize_names: true)
|
170
|
+
rescue JSON::ParserError
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Check if the request is an AJAX request (alias for xhr?)
|
176
|
+
#
|
177
|
+
# @returns [Boolean]
|
178
|
+
#
|
179
|
+
def ajax?
|
180
|
+
xhr?
|
181
|
+
end
|
182
|
+
|
183
|
+
# Get the requested format (.html, .json, etc)
|
184
|
+
#
|
185
|
+
# @returns [Symbol, nil]
|
186
|
+
#
|
187
|
+
def format
|
188
|
+
path_info = env["PATH_INFO"]
|
189
|
+
return nil unless path_info.include?(".")
|
190
|
+
|
191
|
+
extension = File.extname(path_info).delete(".")
|
192
|
+
extension.empty? ? nil : extension.to_sym
|
193
|
+
end
|
194
|
+
|
195
|
+
# Check if the request is a GET request
|
196
|
+
#
|
197
|
+
# @returns [Boolean]
|
198
|
+
#
|
199
|
+
def get?
|
200
|
+
request_method == "GET"
|
201
|
+
end
|
202
|
+
|
203
|
+
# Check if the request is a POST request
|
204
|
+
#
|
205
|
+
# @returns [Boolean]
|
206
|
+
#
|
207
|
+
def post?
|
208
|
+
request_method == "POST"
|
209
|
+
end
|
210
|
+
|
211
|
+
# Check if the request is a PUT request
|
212
|
+
#
|
213
|
+
# @returns [Boolean]
|
214
|
+
#
|
215
|
+
def put?
|
216
|
+
request_method == "PUT"
|
217
|
+
end
|
218
|
+
|
219
|
+
# Check if the request is a DELETE request
|
220
|
+
#
|
221
|
+
# @returns [Boolean]
|
222
|
+
#
|
223
|
+
def delete?
|
224
|
+
request_method == "DELETE"
|
225
|
+
end
|
226
|
+
|
227
|
+
# Check if the request is a HEAD request
|
228
|
+
#
|
229
|
+
# @returns [Boolean]
|
230
|
+
#
|
231
|
+
def head?
|
232
|
+
request_method == "HEAD"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Check if the request is a PATCH request
|
236
|
+
#
|
237
|
+
# @returns [Boolean]
|
238
|
+
#
|
239
|
+
def patch?
|
240
|
+
request_method == "PATCH"
|
77
241
|
end
|
78
242
|
|
79
243
|
private
|
80
244
|
|
245
|
+
# Get the client IP address
|
246
|
+
#
|
247
|
+
# @returns [String]
|
248
|
+
#
|
81
249
|
def ip_address
|
82
|
-
forwarded_for =
|
250
|
+
forwarded_for = env["HTTP_X_FORWARDED_FOR"]
|
83
251
|
forwarded_for ? forwarded_for.split(",").first.strip : env["REMOTE_ADDR"]
|
84
252
|
end
|
85
253
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Lennarb
|
2
|
+
class RequestHandler
|
3
|
+
Lennarb::Error = Class.new(StandardError)
|
4
|
+
|
5
|
+
attr_reader :app
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
http_method = env[Rack::REQUEST_METHOD].to_sym
|
13
|
+
parts = env[Rack::PATH_INFO].split("/").reject(&:empty?)
|
14
|
+
block, params = app.routes.match_route(parts, http_method)
|
15
|
+
|
16
|
+
unless block
|
17
|
+
return [404, {"content-type" => CONTENT_TYPE[:TEXT]}, ["Not Found"]]
|
18
|
+
end
|
19
|
+
|
20
|
+
req = Request.new(env, params)
|
21
|
+
res = Response.new
|
22
|
+
|
23
|
+
catch(:halt) do
|
24
|
+
block.call(req, res)
|
25
|
+
res.finish
|
26
|
+
rescue Lennarb::Error => error
|
27
|
+
[500, {"content-type" => CONTENT_TYPE[:TEXT]}, ["Internal Server Error (#{error.message})"]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/lennarb/response.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
module Lennarb
|
2
2
|
class Response
|
3
3
|
# @!attribute [rw] status
|
4
4
|
# @returns [Integer]
|
@@ -31,13 +31,12 @@ class Lennarb
|
|
31
31
|
CONTENT_LENGTH = "content-length"
|
32
32
|
private_constant :CONTENT_LENGTH
|
33
33
|
|
34
|
-
ContentType = {HTML: "text/html", TEXT: "text/plain", JSON: "application/json"}.freeze
|
35
34
|
# Initialize the response object
|
36
35
|
#
|
37
36
|
# @returns [Response]
|
38
37
|
#
|
39
38
|
def initialize
|
40
|
-
@status =
|
39
|
+
@status = 200
|
41
40
|
@headers = {}
|
42
41
|
@body = []
|
43
42
|
@length = 0
|
@@ -84,7 +83,7 @@ class Lennarb
|
|
84
83
|
# @returns [String] str
|
85
84
|
#
|
86
85
|
def text(str)
|
87
|
-
@headers[CONTENT_TYPE] =
|
86
|
+
@headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:TEXT]
|
88
87
|
write(str)
|
89
88
|
end
|
90
89
|
|
@@ -95,7 +94,7 @@ class Lennarb
|
|
95
94
|
# @returns [String] str
|
96
95
|
#
|
97
96
|
def html(str)
|
98
|
-
@headers[CONTENT_TYPE] =
|
97
|
+
@headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:HTML]
|
99
98
|
write(str)
|
100
99
|
end
|
101
100
|
|
@@ -106,8 +105,13 @@ class Lennarb
|
|
106
105
|
# @returns [String] str
|
107
106
|
#
|
108
107
|
def json(str)
|
109
|
-
|
110
|
-
|
108
|
+
json_str = JSON.generate(str)
|
109
|
+
@headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:JSON]
|
110
|
+
write(json_str)
|
111
|
+
rescue JSON::GeneratorError => e
|
112
|
+
@status = 500
|
113
|
+
@headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:TEXT]
|
114
|
+
write("JSON generation error: #{e.message}")
|
111
115
|
end
|
112
116
|
|
113
117
|
# Redirect the response
|
data/lib/lennarb/route_node.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
module Lennarb
|
2
2
|
class RouteNode
|
3
|
+
DuplicateRouteError = Class.new(StandardError)
|
3
4
|
attr_accessor :static_children, :dynamic_children, :blocks, :param_key
|
4
5
|
|
5
6
|
def initialize
|
@@ -9,6 +10,14 @@ class Lennarb
|
|
9
10
|
@dynamic_children = {}
|
10
11
|
end
|
11
12
|
|
13
|
+
# Add a route to the route node.
|
14
|
+
#
|
15
|
+
# @param parts [Array<String>] The parts of the route.
|
16
|
+
# @param http_method [String] The HTTP method.
|
17
|
+
# @param block [Proc] The block to be executed when the route is matched.
|
18
|
+
#
|
19
|
+
# @returns [void]
|
20
|
+
#
|
12
21
|
def add_route(parts, http_method, block)
|
13
22
|
current_node = self
|
14
23
|
|
@@ -28,6 +37,14 @@ class Lennarb
|
|
28
37
|
current_node.blocks[http_method] = block
|
29
38
|
end
|
30
39
|
|
40
|
+
# Match a route.
|
41
|
+
#
|
42
|
+
# @param parts [Array<String>] The parts of the route.
|
43
|
+
# @param http_method [String] The HTTP method.
|
44
|
+
# @param params [Hash] The parameters of the route.
|
45
|
+
#
|
46
|
+
# @returns [Array<Proc, Hash>]
|
47
|
+
#
|
31
48
|
def match_route(parts, http_method, params: {})
|
32
49
|
if parts.empty?
|
33
50
|
return [blocks[http_method], params] if blocks[http_method]
|
@@ -52,10 +69,35 @@ class Lennarb
|
|
52
69
|
[nil, nil]
|
53
70
|
end
|
54
71
|
|
72
|
+
# Merge another route node into this one.
|
73
|
+
#
|
74
|
+
# @param other [RouteNode] The other route node.
|
75
|
+
#
|
76
|
+
# @returns [void|DuplicateRouteError]
|
77
|
+
#
|
55
78
|
def merge!(other)
|
56
|
-
|
57
|
-
|
58
|
-
|
79
|
+
other.blocks.each do |http_method, block|
|
80
|
+
if @blocks[http_method]
|
81
|
+
raise DuplicateRouteError, "Duplicate route for HTTP method: #{http_method}"
|
82
|
+
end
|
83
|
+
@blocks[http_method] = block
|
84
|
+
end
|
85
|
+
|
86
|
+
other.static_children.each do |path, node|
|
87
|
+
if @static_children[path]
|
88
|
+
@static_children[path].merge!(node)
|
89
|
+
else
|
90
|
+
@static_children[path] = node
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
other.dynamic_children.each do |param, node|
|
95
|
+
if @dynamic_children[param]
|
96
|
+
@dynamic_children[param].merge!(node)
|
97
|
+
else
|
98
|
+
@dynamic_children[param] = node
|
99
|
+
end
|
100
|
+
end
|
59
101
|
end
|
60
102
|
end
|
61
103
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Lennarb
|
2
|
+
class Routes
|
3
|
+
attr_reader :store
|
4
|
+
# RouteNode is a trie data structure that stores routes.
|
5
|
+
# see {Lennarb::RouteNode} for more details.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# node = RouteNode.new
|
9
|
+
# node.add_route(["foo", "bar"], :GET, -> {})
|
10
|
+
#
|
11
|
+
def initialize(&)
|
12
|
+
@store = RouteNode.new
|
13
|
+
instance_eval(&) if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
# Define the HTTP methods.
|
17
|
+
#
|
18
|
+
# get, post, put, delete, patch, options, head
|
19
|
+
#
|
20
|
+
HTTP_METHODS.each do |http_method|
|
21
|
+
define_method(http_method.downcase) do |path, &block|
|
22
|
+
register_route(http_method, path, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Define the root route.
|
27
|
+
#
|
28
|
+
# @param [String] path
|
29
|
+
#
|
30
|
+
# @param [Proc] block
|
31
|
+
#
|
32
|
+
# @returns [void]
|
33
|
+
#
|
34
|
+
def root(&block) = register_route(:GET, "/", &block)
|
35
|
+
|
36
|
+
# Match the route.
|
37
|
+
#
|
38
|
+
# @param [Array<String>] parts
|
39
|
+
#
|
40
|
+
# @param [Symbol] http_method
|
41
|
+
#
|
42
|
+
def match_route(...) = @store.match_route(...)
|
43
|
+
|
44
|
+
# Freeze store object.
|
45
|
+
#
|
46
|
+
# @returns [void]
|
47
|
+
#
|
48
|
+
def freeze = @store.freeze
|
49
|
+
|
50
|
+
private def register_route(http_method, path, &block)
|
51
|
+
parts = path.split("/").reject(&:empty?)
|
52
|
+
@store.add_route(parts, http_method, block)
|
53
|
+
end
|
54
|
+
|
55
|
+
module Mixin
|
56
|
+
extend self
|
57
|
+
|
58
|
+
def routes(&block)
|
59
|
+
@routes ||= Routes.new(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
HTTP_METHODS.each do |http_method|
|
63
|
+
define_method(http_method.downcase) do |path, &block|
|
64
|
+
routes.send(http_method.downcase, path, &block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def root(&) = routes.root(&)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/lennarb/version.rb
CHANGED
data/lib/lennarb.rb
CHANGED
@@ -1,72 +1,20 @@
|
|
1
1
|
# Core extensions
|
2
2
|
#
|
3
|
-
require "pathname"
|
4
|
-
require "rack"
|
5
3
|
require "bundler"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
define_method(http_method.downcase) do |path, &block|
|
23
|
-
add_route(path, http_method, block)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def root
|
28
|
-
@root ||= RouteNode.new
|
29
|
-
end
|
30
|
-
|
31
|
-
def app
|
32
|
-
@app ||= begin
|
33
|
-
request_handler = ->(env) { process_request(env) }
|
34
|
-
|
35
|
-
Rack::Builder.app do
|
36
|
-
run request_handler
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def initializer!
|
42
|
-
Bundler.require(:default, ENV["LENNA_ENV"] || "development")
|
43
|
-
|
44
|
-
root.freeze
|
45
|
-
app.freeze
|
46
|
-
end
|
47
|
-
|
48
|
-
def call(env) = @_mutex.synchronize { app.call(env) }
|
49
|
-
|
50
|
-
def add_route(path, http_method, block)
|
51
|
-
parts = path.split("/").reject(&:empty?)
|
52
|
-
root.add_route(parts, http_method, block)
|
53
|
-
end
|
54
|
-
|
55
|
-
private def process_request(env)
|
56
|
-
http_method = env[Rack::REQUEST_METHOD].to_sym
|
57
|
-
parts = env[Rack::PATH_INFO].split("/").reject(&:empty?)
|
58
|
-
|
59
|
-
block, params = root.match_route(parts, http_method)
|
60
|
-
return [404, {"content-type" => Response::ContentType[:TEXT]}, ["Not Found"]] unless block
|
61
|
-
|
62
|
-
res = Response.new
|
63
|
-
req = Request.new(env, params)
|
64
|
-
|
65
|
-
catch(:halt) do
|
66
|
-
instance_exec(req, res, &block)
|
67
|
-
res.finish
|
68
|
-
end
|
69
|
-
rescue => e
|
70
|
-
[500, {"content-type" => Response::ContentType[:TEXT]}, ["Internal Server Error - #{e.message}"]]
|
71
|
-
end
|
4
|
+
require "rack"
|
5
|
+
require "json"
|
6
|
+
require "pathname"
|
7
|
+
require "superconfig"
|
8
|
+
|
9
|
+
module Lennarb
|
10
|
+
require_relative "lennarb/constansts"
|
11
|
+
require_relative "lennarb/environment"
|
12
|
+
require_relative "lennarb/version"
|
13
|
+
require_relative "lennarb/request_handler"
|
14
|
+
require_relative "lennarb/request"
|
15
|
+
require_relative "lennarb/response"
|
16
|
+
require_relative "lennarb/route_node"
|
17
|
+
require_relative "lennarb/routes"
|
18
|
+
require_relative "lennarb/config"
|
19
|
+
require_relative "lennarb/app"
|
72
20
|
end
|
data/readme.md
CHANGED
@@ -7,10 +7,11 @@
|
|
7
7
|
|
8
8
|
A lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.4+
|
9
9
|
|
10
|
-
[](https://github.com/aristotelesbr/lennarb/actions/workflows/test.yaml)
|
11
11
|
[](https://rubygems.org/gems/lennarb)
|
12
12
|
[](https://rubygems.org/gems/lennarb)
|
13
13
|
[](https://tldrlegal.com/license/mit-license)
|
14
|
+
|
14
15
|
</div>
|
15
16
|
|
16
17
|
## Basic Usage
|
@@ -18,10 +19,17 @@ A lightweight, fast, and modular web framework for Ruby based on Rack. The **Len
|
|
18
19
|
```ruby
|
19
20
|
require "lennarb"
|
20
21
|
|
21
|
-
Lennarb.new do
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
Lennarb::App.new do
|
23
|
+
config do
|
24
|
+
optional :env, string, "prodcution"
|
25
|
+
optional :port, int, 9292
|
26
|
+
end
|
27
|
+
|
28
|
+
routes do
|
29
|
+
get("/hello/:name") do |req, res|
|
30
|
+
name = req.params[:name]
|
31
|
+
res.html("Hello, #{name}!")
|
32
|
+
end
|
25
33
|
end
|
26
34
|
end
|
27
35
|
```
|
@@ -43,7 +51,7 @@ See all [graphs](https://github.com/aristotelesbr/lennarb/blob/main/benchmark)
|
|
43
51
|
|
44
52
|
This table ranks the routers by the number of requests they can process per second. Higher numbers indicate better performance.
|
45
53
|
|
46
|
-
|
54
|
+
Please see [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) for more information.
|
47
55
|
|
48
56
|
## Usage
|
49
57
|
|
@@ -51,10 +59,10 @@ Plese see [Performance](https://aristotelesbr.github.io/lennarb/guides/performan
|
|
51
59
|
|
52
60
|
- [Performance](https://aristotelesbr.github.io/lennarb/guides/performance/index.html) - The **Lennarb** is very fast. The following benchmarks were performed on a MacBook Pro (Retina, 13-inch, Early 2013) with 2,7 GHz Intel Core i7 and 8 GB 1867 MHz DDR3. Based on [jeremyevans/r10k](https://github.com/jeremyevans/r10k) using the following [template build](static/r10k/build/lennarb.rb).
|
53
61
|
|
54
|
-
- [
|
62
|
+
- [Request]() - TODO
|
55
63
|
|
56
64
|
- [Response](https://aristotelesbr.github.io/lennarb/guides/response/index.html) - This is the response guide.
|
57
|
-
|
65
|
+
The `res` object is used to send a response to the client. The Lennarb use a custom response object to send responses to the client. The `res` object is an instance of `Lennarb::Response`.
|
58
66
|
|
59
67
|
### Developer Certificate of Origin
|
60
68
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lennarb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aristóteles Coutinho
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-02-
|
10
|
+
date: 2025-02-26 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: bigdecimal
|
@@ -51,6 +51,20 @@ dependencies:
|
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '3.1'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: superconfig
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
54
68
|
- !ruby/object:Gem::Dependency
|
55
69
|
name: bundler
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,6 +121,20 @@ dependencies:
|
|
107
121
|
- - ">="
|
108
122
|
- !ruby/object:Gem::Version
|
109
123
|
version: '0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: minitest-utils
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
110
138
|
- !ruby/object:Gem::Dependency
|
111
139
|
name: rack-test
|
112
140
|
requirement: !ruby/object:Gem::Requirement
|
@@ -191,6 +219,20 @@ dependencies:
|
|
191
219
|
- - ">="
|
192
220
|
- !ruby/object:Gem::Version
|
193
221
|
version: '0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: debug
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ">="
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
229
|
+
type: :development
|
230
|
+
prerelease: false
|
231
|
+
version_requirements: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '0'
|
194
236
|
executables:
|
195
237
|
- lenna
|
196
238
|
extensions: []
|
@@ -223,10 +265,15 @@ files:
|
|
223
265
|
- guides/response/readme.md
|
224
266
|
- lennarb.gemspec
|
225
267
|
- lib/lennarb.rb
|
268
|
+
- lib/lennarb/app.rb
|
269
|
+
- lib/lennarb/config.rb
|
226
270
|
- lib/lennarb/constansts.rb
|
271
|
+
- lib/lennarb/environment.rb
|
227
272
|
- lib/lennarb/request.rb
|
273
|
+
- lib/lennarb/request_handler.rb
|
228
274
|
- lib/lennarb/response.rb
|
229
275
|
- lib/lennarb/route_node.rb
|
276
|
+
- lib/lennarb/routes.rb
|
230
277
|
- lib/lennarb/version.rb
|
231
278
|
- license.md
|
232
279
|
- logo/lennarb.png
|