lennarb 1.3.0 → 1.4.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/.devcontainer/Dockerfile +8 -0
- data/.devcontainer/devcontainer.json +20 -0
- data/.editorconfig +9 -0
- data/.github/workflows/coverage.yaml +58 -0
- data/.github/workflows/documentation.yaml +47 -0
- data/.github/workflows/main.yaml +27 -0
- data/.github/workflows/test.yaml +49 -0
- data/.gitignore +12 -0
- data/.standard.yml +23 -0
- data/.tool-versions +1 -0
- data/LICENCE +24 -0
- data/Rakefile +10 -0
- data/benchmark/memory.png +0 -0
- data/benchmark/rps.png +0 -0
- data/benchmark/runtime_with_startup.png +0 -0
- data/bin/console +8 -0
- data/bin/release +15 -0
- data/bin/setup +8 -0
- data/changelog.md +40 -7
- data/exe/lenna +2 -3
- data/gems.rb +29 -0
- data/guides/getting-started/readme.md +201 -0
- data/guides/links.yaml +6 -0
- data/guides/performance/readme.md +120 -0
- data/guides/response/readme.md +83 -0
- data/lennarb.gemspec +44 -0
- data/lib/lennarb/constansts.rb +1 -0
- data/lib/lennarb/request.rb +21 -18
- data/lib/lennarb/response.rb +7 -14
- data/lib/lennarb/route_node.rb +4 -9
- data/lib/lennarb/version.rb +1 -6
- data/lib/lennarb.rb +38 -124
- data/logo/lennarb.png +0 -0
- data/readme.md +18 -7
- metadata +103 -63
- data/lib/lennarb/plugin.rb +0 -49
- data/lib/lennarb/plugins/hooks.rb +0 -117
- data/lib/lennarb/plugins/mount.rb +0 -66
@@ -0,0 +1,120 @@
|
|
1
|
+
# Performance
|
2
|
+
|
3
|
+
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. All tests are performed using the **Ruby 3.3.0**
|
4
|
+
|
5
|
+
## Benchmark results
|
6
|
+
|
7
|
+
This document contains the benchmarks comparing **Lennarb** with other routers based on Rack. Metrics evaluated include Requests per Second, Initial memory usage and Startup time.
|
8
|
+
|
9
|
+
### 1. Requests per Second (RPS)
|
10
|
+
|
11
|
+

|
12
|
+
|
13
|
+
| Position | Application | 10 RPS | 100 RPS | 1.000 RPS | 10.000 RPS |
|
14
|
+
| -------- | ----------- | ---------- | ---------- | --------- | ---------- |
|
15
|
+
| 1 | Lenna | 126.252,36 | 108.086,55 | 87.111,91 | 68.460,64 |
|
16
|
+
| 2 | Roda | 123.360,37 | 88.380,56 | 66.990,77 | 48.108,29 |
|
17
|
+
| 3 | Syro | 114.105,38 | 80.909,39 | 61.415,86 | 46.639,81 |
|
18
|
+
| 4 | Hanami-API | 68.089,18 | 52.851,88 | 40.801,78 | 27.996,00 |
|
19
|
+
|
20
|
+
This table ranks the routers by the number of requests they can process per second. Higher numbers indicate better performance.
|
21
|
+
|
22
|
+
### 2. Initial memory usage (in KB)
|
23
|
+
|
24
|
+

|
25
|
+
|
26
|
+
| Position | Application | 10 KB | 100 KB | 1.000 KB | 10.000 KB |
|
27
|
+
| -------- | ----------- | ------ | ------ | -------- | --------- |
|
28
|
+
| 1 | Syro | 12,160 | 12,544 | 16,460 | 49,692 |
|
29
|
+
| 2 | Lenna | 14,464 | 14,720 | 18,232 | 56,812 |
|
30
|
+
| 3 | Roda | 15,104 | 15,104 | 18,220 | 49,900 |
|
31
|
+
| 4 | Hanami-API | 15,744 | 16,128 | 20,888 | 64,824 |
|
32
|
+
|
33
|
+
This table shows the initial memory usage in KB. Lower values indicate lower memory consumption.
|
34
|
+
|
35
|
+
### 3. Startup time (in seconds)
|
36
|
+
|
37
|
+

|
38
|
+
|
39
|
+
| Position | Application | 10 seg | 100 seg | 1.000 seg | 10.000 seg |
|
40
|
+
| -------- | ----------- | ------ | ------- | --------- | ---------- |
|
41
|
+
| 1 | Syro | 0.274 | 0.347 | 0.455 | 0.997 |
|
42
|
+
| 2 | Lenna | 0.289 | 0.312 | 0.393 | 0.914 |
|
43
|
+
| 3 | Roda | 0.294 | 0.378 | 0.467 | 0.918 |
|
44
|
+
| 4 | Hanami-API | 0.445 | 0.550 | 0.808 | 3.074 |
|
45
|
+
|
46
|
+
This table shows the startup time in seconds. Lower values indicate faster startup times.
|
47
|
+
|
48
|
+
## Graphs
|
49
|
+
|
50
|
+
See the graphs in the `benchmarks` directory of the lennarb project.
|
51
|
+
|
52
|
+
## Steps to run the benchmarks
|
53
|
+
|
54
|
+
### 1. Install the router gem you want to test
|
55
|
+
|
56
|
+
```bash
|
57
|
+
gem install lennarb
|
58
|
+
gem install syro
|
59
|
+
gem install roda
|
60
|
+
```
|
61
|
+
|
62
|
+
### 2. Clone the jeremyevans/r10k repository
|
63
|
+
|
64
|
+
```bash
|
65
|
+
git clone https://github.com/jeremyevans/r10k
|
66
|
+
```
|
67
|
+
|
68
|
+
### 3. Create a new file in the `r10k` directory
|
69
|
+
|
70
|
+
In the `r10k` directory, create a new file called `lennarb.rb` into `builders` directory with the code below:
|
71
|
+
|
72
|
+
```bash
|
73
|
+
touch r10k/builders/lennarb.rb
|
74
|
+
```
|
75
|
+
|
76
|
+
Put the code below into `lennarb.rb` file:
|
77
|
+
|
78
|
+
```rb
|
79
|
+
# frozen_string_literal: true
|
80
|
+
|
81
|
+
# Released under the MIT License.
|
82
|
+
# Copyright, 2024, by Aristóteles Coutinho.
|
83
|
+
|
84
|
+
lennarb_routes =
|
85
|
+
lambda do |f, level, prefix, calc_path, lvars|
|
86
|
+
base = BASE_ROUTE.dup
|
87
|
+
ROUTES_PER_LEVEL.times do
|
88
|
+
route = "#{prefix}#{base}"
|
89
|
+
if level == 1
|
90
|
+
params = lvars.map { |lvar| "\#{req.params[:#{lvar}]}" }
|
91
|
+
.join('-')
|
92
|
+
f.puts " app.get '#{route}/:#{lvars.last}' do |req, res|"
|
93
|
+
f.puts " body = \"#{calc_path[1..]}#{base}-#{params}\""
|
94
|
+
f.puts ' res.html body'
|
95
|
+
f.puts ' end'
|
96
|
+
else
|
97
|
+
lennarb_routes.call(f, level - 1, "#{route}/:#{lvars.last}/", "#{calc_path}#{base}/", lvars + [lvars.last.succ])
|
98
|
+
end
|
99
|
+
base.succ!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
File.open("#{File.dirname(__FILE__)}/../apps/lennarb_#{LEVELS}_#{ROUTES_PER_LEVEL}.rb", 'wb') do |f|
|
104
|
+
f.puts '# frozen_string_literal: true'
|
105
|
+
f.puts "require 'lennarb'"
|
106
|
+
f.puts 'app = Lennarb.new'
|
107
|
+
lennarb_routes.call(f, LEVELS, '/', '/', ['a'])
|
108
|
+
f.puts 'App = app'
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### 4. Run the benchmarks
|
113
|
+
|
114
|
+
```bash
|
115
|
+
bundle exec rake bench graphs R10K_APPS="lennarb syro roda"
|
116
|
+
```
|
117
|
+
|
118
|
+
## Conclusion
|
119
|
+
|
120
|
+
These numbers are just a small reference, **Lennarb** is not a framework, it is a router. In my opinion, **Roda** is the best router for Ruby because it has many interesting features, such as a middleware manager, and very good development performance.
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Response
|
2
|
+
|
3
|
+
This is the response guide.
|
4
|
+
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}.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
You can use the `res` object to send a response to the client.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
# app.rb
|
12
|
+
|
13
|
+
app.get '/' do |req, res|
|
14
|
+
res.html 'Hello World'
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
## Content Types
|
19
|
+
|
20
|
+
Lenna supports the following content types:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
# app.rb
|
24
|
+
|
25
|
+
app.get '/' do |req, res|
|
26
|
+
res.html 'Hello World'
|
27
|
+
res.json '{"message": "Hello World"}'
|
28
|
+
res.text 'Hello World'
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
But you can also set your own content type:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
res['content-type'] = 'text/markdown'
|
36
|
+
res.write '# Hello World'
|
37
|
+
```
|
38
|
+
|
39
|
+
## The write method
|
40
|
+
|
41
|
+
You can use the `res.write` method to write to the response body:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# app.rb
|
45
|
+
|
46
|
+
app.get '/' do |req, res|
|
47
|
+
res.write 'Hello World'
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
JSON example:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# app.rb
|
55
|
+
|
56
|
+
app.post '/posts' do |req, res|
|
57
|
+
req.params # => { name: 'Lenna' }
|
58
|
+
name = req.params[:name]
|
59
|
+
|
60
|
+
res.write({ data: { name: } }.to_json) # This will write to the response body
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
## Status Codes
|
65
|
+
|
66
|
+
You can set the status code using the `res.status` method:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
res.status 200
|
70
|
+
```
|
71
|
+
|
72
|
+
## Redirects
|
73
|
+
|
74
|
+
You can redirect the client using the `res.redirect` method:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# app.ruby
|
78
|
+
|
79
|
+
app.get '/' do |req, res|
|
80
|
+
# Stuff code here...
|
81
|
+
res.redirect '/hello'
|
82
|
+
end
|
83
|
+
```
|
data/lennarb.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "lib/lennarb/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "lennarb"
|
5
|
+
spec.version = Lennarb::VERSION
|
6
|
+
|
7
|
+
spec.summary = <<~DESC
|
8
|
+
Lennarb provides a lightweight yet robust solution for web routing in Ruby, focusing on performance and simplicity.
|
9
|
+
DESC
|
10
|
+
spec.authors = ["Aristóteles Coutinho"]
|
11
|
+
spec.license = "MIT"
|
12
|
+
spec.homepage = "https://aristotelesbr.github.io/lennarb"
|
13
|
+
spec.metadata = {
|
14
|
+
"allowed_push_host" => "https://rubygems.org",
|
15
|
+
"changelog_uri" => "https://github.com/aristotelesbr/lennarb/blob/master/changelog.md",
|
16
|
+
"homepage_uri" => "https://aristotelesbr.github.io/lennarb",
|
17
|
+
"rubygems_mfa_required" => "true",
|
18
|
+
"source_code_uri" => "https://github.com/aristotelesbr/lennarb"
|
19
|
+
}
|
20
|
+
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`
|
23
|
+
.split("\x0")
|
24
|
+
.reject { |f| f.match(%r{^(test|features)/}) }
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = ["lenna"]
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "bigdecimal"
|
32
|
+
spec.add_dependency "colorize", "~> 1.1"
|
33
|
+
spec.add_dependency "rack", "~> 3.1"
|
34
|
+
spec.add_development_dependency "bundler"
|
35
|
+
spec.add_development_dependency "covered"
|
36
|
+
spec.add_development_dependency "simplecov"
|
37
|
+
spec.add_development_dependency "minitest"
|
38
|
+
spec.add_development_dependency "rack-test"
|
39
|
+
spec.add_development_dependency "rake"
|
40
|
+
spec.add_development_dependency "standard"
|
41
|
+
spec.add_development_dependency "standard-custom"
|
42
|
+
spec.add_development_dependency "standard-performance"
|
43
|
+
spec.add_development_dependency "m"
|
44
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
HTTP_METHODS = %i[GET POST PUT PATCH DELETE HEAD OPTIONS].freeze
|
data/lib/lennarb/request.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-2024, by Aristóteles Coutinho.
|
5
|
-
|
6
1
|
class Lennarb
|
7
2
|
class Request < Rack::Request
|
8
3
|
# The environment variables of the request
|
@@ -33,7 +28,7 @@ class Lennarb
|
|
33
28
|
#
|
34
29
|
# @returns [String]
|
35
30
|
#
|
36
|
-
def path = @path ||= super.split(
|
31
|
+
def path = @path ||= super.split("?").first
|
37
32
|
|
38
33
|
# Read the body of the request
|
39
34
|
#
|
@@ -52,18 +47,26 @@ class Lennarb
|
|
52
47
|
# Get the headers of the request
|
53
48
|
#
|
54
49
|
def headers
|
55
|
-
@headers ||= env.select { |key, _| key.start_with?(
|
50
|
+
@headers ||= env.select { |key, _| key.start_with?("HTTP_") }
|
56
51
|
end
|
57
52
|
|
58
|
-
def ip
|
59
|
-
|
60
|
-
def
|
61
|
-
|
62
|
-
def
|
63
|
-
|
64
|
-
def
|
65
|
-
|
66
|
-
def
|
53
|
+
def ip = ip_address
|
54
|
+
|
55
|
+
def secure? = scheme == "https"
|
56
|
+
|
57
|
+
def user_agent = headers["HTTP_USER_AGENT"]
|
58
|
+
|
59
|
+
def accept = headers["HTTP_ACCEPT"]
|
60
|
+
|
61
|
+
def referer = headers["HTTP_REFERER"]
|
62
|
+
|
63
|
+
def host = headers["HTTP_HOST"]
|
64
|
+
|
65
|
+
def content_length = headers["HTTP_CONTENT_LENGTH"]
|
66
|
+
|
67
|
+
def content_type = headers["HTTP_CONTENT_TYPE"]
|
68
|
+
|
69
|
+
def xhr? = headers["HTTP_X_REQUESTED_WITH"]&.casecmp("XMLHttpRequest")&.zero?
|
67
70
|
|
68
71
|
def []=(key, value)
|
69
72
|
env[key] = value
|
@@ -76,8 +79,8 @@ class Lennarb
|
|
76
79
|
private
|
77
80
|
|
78
81
|
def ip_address
|
79
|
-
forwarded_for = headers[
|
80
|
-
forwarded_for ? forwarded_for.split(
|
82
|
+
forwarded_for = headers["HTTP_X_FORWARDED_FOR"]
|
83
|
+
forwarded_for ? forwarded_for.split(",").first.strip : env["REMOTE_ADDR"]
|
81
84
|
end
|
82
85
|
end
|
83
86
|
end
|
data/lib/lennarb/response.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-2024, by Aristóteles Coutinho.
|
5
|
-
|
6
1
|
class Lennarb
|
7
2
|
class Response
|
8
3
|
# @!attribute [rw] status
|
@@ -27,27 +22,25 @@ class Lennarb
|
|
27
22
|
|
28
23
|
# Constants
|
29
24
|
#
|
30
|
-
LOCATION =
|
25
|
+
LOCATION = "location"
|
31
26
|
private_constant :LOCATION
|
32
27
|
|
33
|
-
CONTENT_TYPE =
|
28
|
+
CONTENT_TYPE = "content-type"
|
34
29
|
private_constant :CONTENT_TYPE
|
35
30
|
|
36
|
-
CONTENT_LENGTH =
|
31
|
+
CONTENT_LENGTH = "content-length"
|
37
32
|
private_constant :CONTENT_LENGTH
|
38
33
|
|
39
|
-
ContentType = {
|
40
|
-
private_constant :ContentType
|
41
|
-
|
34
|
+
ContentType = {HTML: "text/html", TEXT: "text/plain", JSON: "application/json"}.freeze
|
42
35
|
# Initialize the response object
|
43
36
|
#
|
44
37
|
# @returns [Response]
|
45
38
|
#
|
46
39
|
def initialize
|
47
|
-
@status
|
40
|
+
@status = 404
|
48
41
|
@headers = {}
|
49
|
-
@body
|
50
|
-
@length
|
42
|
+
@body = []
|
43
|
+
@length = 0
|
51
44
|
end
|
52
45
|
|
53
46
|
# Set the response header
|
data/lib/lennarb/route_node.rb
CHANGED
@@ -1,16 +1,11 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-2024, by Aristóteles Coutinho.
|
5
|
-
|
6
1
|
class Lennarb
|
7
2
|
class RouteNode
|
8
3
|
attr_accessor :static_children, :dynamic_children, :blocks, :param_key
|
9
4
|
|
10
5
|
def initialize
|
11
|
-
@blocks
|
12
|
-
@param_key
|
13
|
-
@static_children
|
6
|
+
@blocks = {}
|
7
|
+
@param_key = nil
|
8
|
+
@static_children = {}
|
14
9
|
@dynamic_children = {}
|
15
10
|
end
|
16
11
|
|
@@ -18,7 +13,7 @@ class Lennarb
|
|
18
13
|
current_node = self
|
19
14
|
|
20
15
|
parts.each do |part|
|
21
|
-
if part.start_with?(
|
16
|
+
if part.start_with?(":")
|
22
17
|
param_sym = part[1..].to_sym
|
23
18
|
current_node.dynamic_children[param_sym] ||= RouteNode.new
|
24
19
|
dynamic_node = current_node.dynamic_children[param_sym]
|
data/lib/lennarb/version.rb
CHANGED
data/lib/lennarb.rb
CHANGED
@@ -1,133 +1,63 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-2024, by Aristóteles Coutinho.
|
5
|
-
|
6
1
|
# Core extensions
|
7
2
|
#
|
8
|
-
require
|
9
|
-
require
|
3
|
+
require "pathname"
|
4
|
+
require "rack"
|
5
|
+
require "bundler"
|
10
6
|
|
11
|
-
require_relative
|
12
|
-
require_relative
|
13
|
-
require_relative
|
14
|
-
require_relative
|
15
|
-
require_relative
|
7
|
+
require_relative "lennarb/request"
|
8
|
+
require_relative "lennarb/response"
|
9
|
+
require_relative "lennarb/route_node"
|
10
|
+
require_relative "lennarb/version"
|
11
|
+
require_relative "lennarb/constansts"
|
16
12
|
|
17
13
|
class Lennarb
|
18
14
|
class LennarbError < StandardError; end
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@_middlewares ||= []
|
24
|
-
@_middlewares << [middleware, args, block]
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.get(path, &block) = add_route(path, :GET, block)
|
28
|
-
def self.put(path, &block) = add_route(path, :PUT, block)
|
29
|
-
def self.post(path, &block) = add_route(path, :POST, block)
|
30
|
-
def self.head(path, &block) = add_route(path, :HEAD, block)
|
31
|
-
def self.patch(path, &block) = add_route(path, :PATCH, block)
|
32
|
-
def self.delete(path, &block) = add_route(path, :DELETE, block)
|
33
|
-
def self.options(path, &block) = add_route(path, :OPTIONS, block)
|
34
|
-
|
35
|
-
def self.inherited(subclass)
|
36
|
-
super
|
37
|
-
subclass.instance_variable_set(:@_root, RouteNode.new)
|
38
|
-
subclass.instance_variable_set(:@_plugins, [])
|
39
|
-
subclass.instance_variable_set(:@_middlewares, @_middlewares&.dup || [])
|
40
|
-
|
41
|
-
Plugin.load_defaults! if Plugin.load_defaults?
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.plugin(plugin_name, *, &)
|
45
|
-
@_loaded_plugins ||= {}
|
46
|
-
@_plugins ||= []
|
47
|
-
|
48
|
-
return if @_loaded_plugins.key?(plugin_name)
|
49
|
-
|
50
|
-
plugin_module = Plugin.load(plugin_name)
|
51
|
-
plugin_module.configure(self, *, &) if plugin_module.respond_to?(:configure)
|
52
|
-
|
53
|
-
@_loaded_plugins[plugin_name] = plugin_module
|
54
|
-
@_plugins << plugin_name
|
16
|
+
def initialize
|
17
|
+
@_mutex ||= Mutex.new
|
18
|
+
yield self if block_given?
|
55
19
|
end
|
56
20
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
21
|
+
HTTP_METHODS.each do |http_method|
|
22
|
+
define_method(http_method.downcase) do |path, &block|
|
23
|
+
add_route(path, http_method, block)
|
24
|
+
end
|
61
25
|
end
|
62
26
|
|
63
|
-
def
|
64
|
-
@
|
65
|
-
parts = path.split('/').reject(&:empty?)
|
66
|
-
@_root.add_route(parts, http_method, block)
|
27
|
+
def root
|
28
|
+
@root ||= RouteNode.new
|
67
29
|
end
|
68
30
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@_mutex = Mutex.new
|
73
|
-
@_root = self.class.instance_variable_get(:@_root)&.dup || RouteNode.new
|
74
|
-
@_plugins = self.class.instance_variable_get(:@_plugins)&.dup || []
|
75
|
-
@_loaded_plugins = self.class.instance_variable_get(:@_loaded_plugins)&.dup || {}
|
76
|
-
@_middlewares = self.class.instance_variable_get(:@_middlewares)&.dup || []
|
77
|
-
|
78
|
-
build_app
|
31
|
+
def app
|
32
|
+
@app ||= begin
|
33
|
+
request_handler = ->(env) { process_request(env) }
|
79
34
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
def freeze!
|
86
|
-
return self if @_mounted
|
87
|
-
|
88
|
-
@_root.freeze
|
89
|
-
@_plugins.freeze
|
90
|
-
@_loaded_plugins.freeze
|
91
|
-
@_middlewares.freeze
|
92
|
-
@_app.freeze if @_app.respond_to?(:freeze)
|
93
|
-
self
|
35
|
+
Rack::Builder.app do
|
36
|
+
run request_handler
|
37
|
+
end
|
38
|
+
end
|
94
39
|
end
|
95
40
|
|
96
|
-
def
|
97
|
-
|
98
|
-
def post(path, &block) = add_route(path, :POST, block)
|
99
|
-
def head(path, &block) = add_route(path, :HEAD, block)
|
100
|
-
def patch(path, &block) = add_route(path, :PATCH, block)
|
101
|
-
def delete(path, &block) = add_route(path, :DELETE, block)
|
102
|
-
def options(path, &block) = add_route(path, :OPTIONS, block)
|
103
|
-
|
104
|
-
def plugin(plugin_name, *, &)
|
105
|
-
return if @_loaded_plugins.key?(plugin_name)
|
41
|
+
def initializer!
|
42
|
+
Bundler.require(:default, ENV["LENNA_ENV"] || "development")
|
106
43
|
|
107
|
-
|
108
|
-
|
109
|
-
self.class.include plugin_module::InstanceMethods if plugin_module.const_defined?(:InstanceMethods)
|
110
|
-
plugin_module.configure(self, *, &) if plugin_module.respond_to?(:configure)
|
111
|
-
@_loaded_plugins[plugin_name] = plugin_module
|
112
|
-
@_plugins << plugin_name
|
44
|
+
root.freeze
|
45
|
+
app.freeze
|
113
46
|
end
|
114
47
|
|
115
|
-
|
48
|
+
def call(env) = @_mutex.synchronize { app.call(env) }
|
116
49
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
@_middlewares.reverse_each do |middleware, args, block|
|
121
|
-
@_app = middleware.new(@_app, *args, &block)
|
122
|
-
end
|
50
|
+
def add_route(path, http_method, block)
|
51
|
+
parts = path.split("/").reject(&:empty?)
|
52
|
+
root.add_route(parts, http_method, block)
|
123
53
|
end
|
124
54
|
|
125
|
-
def process_request(env)
|
55
|
+
private def process_request(env)
|
126
56
|
http_method = env[Rack::REQUEST_METHOD].to_sym
|
127
|
-
parts
|
57
|
+
parts = env[Rack::PATH_INFO].split("/").reject(&:empty?)
|
128
58
|
|
129
|
-
block, params =
|
130
|
-
return
|
59
|
+
block, params = root.match_route(parts, http_method)
|
60
|
+
return [404, {"content-type" => Response::ContentType[:TEXT]}, ["Not Found"]] unless block
|
131
61
|
|
132
62
|
res = Response.new
|
133
63
|
req = Request.new(env, params)
|
@@ -136,23 +66,7 @@ class Lennarb
|
|
136
66
|
instance_exec(req, res, &block)
|
137
67
|
res.finish
|
138
68
|
end
|
139
|
-
rescue
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
def handle_error(error)
|
144
|
-
case error
|
145
|
-
when ArgumentError
|
146
|
-
[400, { 'content-type' => 'text/plain' }, ["Bad Request: #{error.message}"]]
|
147
|
-
else
|
148
|
-
[500, { 'content-type' => 'text/plain' }, ["Internal Server Error: #{error.message}"]]
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def not_found = [404, { 'content-type' => 'text/plain' }, ['Not Found']]
|
153
|
-
|
154
|
-
def add_route(path, http_method, block)
|
155
|
-
parts = path.split('/').reject(&:empty?)
|
156
|
-
@_root.add_route(parts, http_method, block)
|
69
|
+
rescue => e
|
70
|
+
[500, {"content-type" => Response::ContentType[:TEXT]}, ["Internal Server Error - #{e.message}"]]
|
157
71
|
end
|
158
72
|
end
|
data/logo/lennarb.png
ADDED
Binary file
|
data/readme.md
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
-
|
1
|
+
<div align="center">
|
2
|
+
<picture>
|
3
|
+
<img alt="Lennarb" src="logo/lennarb.png" width="250">
|
4
|
+
</picture>
|
2
5
|
|
3
|
-
|
6
|
+
---
|
4
7
|
|
5
|
-
**
|
8
|
+
A lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.4+
|
9
|
+
|
10
|
+
[](https://github.com/aristotelesbr/lennarb)
|
11
|
+
[](https://rubygems.org/gems/lennarb)
|
12
|
+
[](https://rubygems.org/gems/lennarb)
|
13
|
+
[](https://tldrlegal.com/license/mit-license)
|
14
|
+
</div>
|
15
|
+
|
16
|
+
## Basic Usage
|
6
17
|
|
7
18
|
```ruby
|
8
19
|
require "lennarb"
|
@@ -36,13 +47,13 @@ Plese see [Performance](https://aristotelesbr.github.io/lennarb/guides/performan
|
|
36
47
|
|
37
48
|
## Usage
|
38
49
|
|
39
|
-
|
50
|
+
- [Getting Started](https://aristotelesbr.github.io/lennarb/guides/getting-started/index) - This guide covers getting up and running with **Lennarb**.
|
40
51
|
|
41
|
-
|
52
|
+
- [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).
|
42
53
|
|
43
|
-
|
54
|
+
- [Plugin](https://aristotelesbr.github.io/lennarb/guides/plugin/index.html) - You can create your plugins to extend the functionality of the framework.
|
44
55
|
|
45
|
-
|
56
|
+
- [Response](https://aristotelesbr.github.io/lennarb/guides/response/index.html) - This is the response guide.
|
46
57
|
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`.
|
47
58
|
|
48
59
|
### Developer Certificate of Origin
|