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.
@@ -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
+ ![RPS](https://raw.githubusercontent.com/aristotelesbr/lennarb/main/benchmark/rps.png)
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
+ ![Memory](https://raw.githubusercontent.com/aristotelesbr/lennarb/main/benchmark/memory.png)
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
+ ![Startup](https://raw.githubusercontent.com/aristotelesbr/lennarb/main/benchmark/runtime_with_startup.png)
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
@@ -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('?').first
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?('HTTP_') }
50
+ @headers ||= env.select { |key, _| key.start_with?("HTTP_") }
56
51
  end
57
52
 
58
- def ip = ip_address
59
- def secure? = scheme == 'https'
60
- def user_agent = headers['HTTP_USER_AGENT']
61
- def accept = headers['HTTP_ACCEPT']
62
- def referer = headers['HTTP_REFERER']
63
- def host = headers['HTTP_HOST']
64
- def content_length = headers['HTTP_CONTENT_LENGTH']
65
- def content_type = headers['HTTP_CONTENT_TYPE']
66
- def xhr? = headers['HTTP_X_REQUESTED_WITH']&.casecmp('XMLHttpRequest')&.zero?
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['HTTP_X_FORWARDED_FOR']
80
- forwarded_for ? forwarded_for.split(',').first.strip : env['REMOTE_ADDR']
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
@@ -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 = 'location'
25
+ LOCATION = "location"
31
26
  private_constant :LOCATION
32
27
 
33
- CONTENT_TYPE = 'content-type'
28
+ CONTENT_TYPE = "content-type"
34
29
  private_constant :CONTENT_TYPE
35
30
 
36
- CONTENT_LENGTH = 'content-length'
31
+ CONTENT_LENGTH = "content-length"
37
32
  private_constant :CONTENT_LENGTH
38
33
 
39
- ContentType = { HTML: 'text/html', TEXT: 'text/plain', JSON: 'application/json' }.freeze
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 = 404
40
+ @status = 404
48
41
  @headers = {}
49
- @body = []
50
- @length = 0
42
+ @body = []
43
+ @length = 0
51
44
  end
52
45
 
53
46
  # Set the response header
@@ -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 = nil
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]
@@ -1,10 +1,5 @@
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
- VERSION = '1.3.0'
2
+ VERSION = "1.4.0"
8
3
 
9
4
  public_constant :VERSION
10
5
  end
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 'pathname'
9
- require 'rack'
3
+ require "pathname"
4
+ require "rack"
5
+ require "bundler"
10
6
 
11
- require_relative 'lennarb/plugin'
12
- require_relative 'lennarb/request'
13
- require_relative 'lennarb/response'
14
- require_relative 'lennarb/route_node'
15
- require_relative 'lennarb/version'
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
- attr_reader :_root, :_plugins, :_loaded_plugins, :_middlewares, :_app
21
-
22
- def self.use(middleware, *args, &block)
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
- def self.freeze!
58
- app = new
59
- app.freeze!
60
- app
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 self.add_route(path, http_method, block)
64
- @_root ||= RouteNode.new
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
- private_class_method :add_route
70
-
71
- def initialize
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
- yield self if block_given?
81
- end
82
-
83
- def call(env) = @_mutex.synchronize { @_app.call(env) }
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 get(path, &block) = add_route(path, :GET, block)
97
- def put(path, &block) = add_route(path, :PUT, block)
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
- plugin_module = Plugin.load(plugin_name)
108
- self.class.extend plugin_module::ClassMethods if plugin_module.const_defined?(:ClassMethods)
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
- private
48
+ def call(env) = @_mutex.synchronize { app.call(env) }
116
49
 
117
- def build_app
118
- @_app = method(:process_request)
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 = env[Rack::PATH_INFO].split('/').reject(&:empty?)
57
+ parts = env[Rack::PATH_INFO].split("/").reject(&:empty?)
128
58
 
129
- block, params = @_root.match_route(parts, http_method)
130
- return not_found unless block
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 StandardError => e
140
- handle_error(e)
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
- # Lennarb
1
+ <div align="center">
2
+ <picture>
3
+ <img alt="Lennarb" src="logo/lennarb.png" width="250">
4
+ </picture>
2
5
 
3
- Lennarb is a lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.0+
6
+ ---
4
7
 
5
- **Basic Usage**
8
+ A lightweight, fast, and modular web framework for Ruby based on Rack. The **Lennarb** supports Ruby (MRI) 3.4+
9
+
10
+ [![Tests](https://github.com/aristotelesbr/lennarb/workflows/rubyby-tests/badge.svg)](https://github.com/aristotelesbr/lennarb)
11
+ [![Gem](https://img.shields.io/gem/v/lennarb.svg)](https://rubygems.org/gems/lennarb)
12
+ [![Gem](https://img.shields.io/gem/dt/lennarb.svg)](https://rubygems.org/gems/lennarb)
13
+ [![MIT License](https://img.shields.io/:License-MIT-blue.svg)](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
- - [Getting Started](https://aristotelesbr.github.io/lennarb/guides/getting-started/index) - This guide covers getting up and running with **Lennarb**.
50
+ - [Getting Started](https://aristotelesbr.github.io/lennarb/guides/getting-started/index) - This guide covers getting up and running with **Lennarb**.
40
51
 
41
- - [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).
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
- - [Plugin](https://aristotelesbr.github.io/lennarb/guides/plugin/index.html) - You can create your plugins to extend the functionality of the framework.
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
- - [Response](https://aristotelesbr.github.io/lennarb/guides/response/index.html) - This is the response guide.
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