lennarb 1.2.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 +60 -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 +83 -41
- data/lib/lennarb/response.rb +131 -138
- data/lib/lennarb/route_node.rb +59 -82
- data/lib/lennarb/version.rb +2 -7
- data/lib/lennarb.rb +52 -124
- data/license.md +1 -2
- data/logo/lennarb.png +0 -0
- data/readme.md +18 -7
- metadata +103 -62
- data/lib/lennarb/application/base.rb +0 -283
- data/lib/lennarb/plugin.rb +0 -35
@@ -0,0 +1,201 @@
|
|
1
|
+
# Getting Started with Lennarb
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Lennarb is a minimalist, thread-safe Rack-based web framework for Ruby that focuses on simplicity and performance. It provides a clean routing DSL and straightforward request/response handling.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add Lennarb to your project's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'lennarb'
|
13
|
+
```
|
14
|
+
|
15
|
+
Or install it directly via RubyGems:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
gem install lennarb
|
19
|
+
```
|
20
|
+
|
21
|
+
## Quick Start
|
22
|
+
|
23
|
+
### Basic Application
|
24
|
+
|
25
|
+
Create a new file named `config.ru`:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'lennarb'
|
29
|
+
|
30
|
+
app = Lennarb.new do |app|
|
31
|
+
app.get '/' do |req, res|
|
32
|
+
res.status = 200
|
33
|
+
res.html('<h1>Welcome to Lennarb!</h1>')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
app.initializer!
|
38
|
+
run app
|
39
|
+
```
|
40
|
+
|
41
|
+
Start the server:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
rackup
|
45
|
+
```
|
46
|
+
|
47
|
+
Your application will be available at `http://localhost:9292`.
|
48
|
+
|
49
|
+
## Core Concepts
|
50
|
+
|
51
|
+
### Request Handling
|
52
|
+
|
53
|
+
Each route handler receives two arguments:
|
54
|
+
|
55
|
+
- `req`: A Request object wrapping the Rack environment
|
56
|
+
- `res`: A Response object for building the HTTP response
|
57
|
+
|
58
|
+
### Response Types
|
59
|
+
|
60
|
+
Lennarb provides three main response helpers:
|
61
|
+
|
62
|
+
```rb
|
63
|
+
app.get '/text' do |req, res|
|
64
|
+
res.text('Plain text response')
|
65
|
+
end
|
66
|
+
|
67
|
+
app.get '/html' do |req, res|
|
68
|
+
res.html('<h1>HTML response</h1>')
|
69
|
+
end
|
70
|
+
|
71
|
+
app.get '/json' do |req, res|
|
72
|
+
res.json('{"message": "JSON response"}')
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
### Redirects
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
app.get '/redirect' do |req, res|
|
80
|
+
res.redirect('/new-location', 302) # 302
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Routes are defined using HTTP method helpers:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
app = Lennarb.new do |l|
|
88
|
+
# Basic route
|
89
|
+
l.get '/' do |req, res|
|
90
|
+
res.html('Home page')
|
91
|
+
end
|
92
|
+
|
93
|
+
# Route with parameters
|
94
|
+
l.get '/users/:id' do |req, res|
|
95
|
+
user_id = req.params[:id]
|
96
|
+
res.json("{\"id\": #{user_id}}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
### Route Parameters
|
102
|
+
|
103
|
+
Parameters from dynamic route segments are available in `req.params`:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
app.get '/hello/:name' do |req, res|
|
107
|
+
name = req.params[:name]
|
108
|
+
res.text("Hello, #{name}!")
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
## Thread Safety
|
113
|
+
|
114
|
+
Lennarb is thread-safe by design:
|
115
|
+
|
116
|
+
- All request processing is synchronized using a mutex
|
117
|
+
- The router tree is frozen after initialization
|
118
|
+
- Response objects are created per-request
|
119
|
+
|
120
|
+
## Application Lifecycle
|
121
|
+
|
122
|
+
### Initialization
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
app = Lennarb.new do |l|
|
126
|
+
# Define routes and configuration
|
127
|
+
end
|
128
|
+
|
129
|
+
# Initialize and freeze the application
|
130
|
+
app.initializer!
|
131
|
+
```
|
132
|
+
|
133
|
+
The `initializer!` method:
|
134
|
+
|
135
|
+
- Loads environment-specific dependencies
|
136
|
+
- Freezes the route tree
|
137
|
+
- Freezes the Rack application
|
138
|
+
|
139
|
+
### Environment
|
140
|
+
|
141
|
+
Lennarb uses the `LENNA_ENV` environment variable (defaults to "development"):
|
142
|
+
|
143
|
+
```bash
|
144
|
+
LENNA_ENV=production rackup
|
145
|
+
```
|
146
|
+
|
147
|
+
## Error Handling
|
148
|
+
|
149
|
+
Lennarb provides basic error handling:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
app.get '/api' do |req, res|
|
153
|
+
# Errors are caught and return 500 with error message
|
154
|
+
raise "Something went wrong"
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
Default error responses:
|
159
|
+
|
160
|
+
- 404 for unmatched routes
|
161
|
+
- 500 for application errors
|
162
|
+
|
163
|
+
## Best Practices
|
164
|
+
|
165
|
+
1. **Always call initializer!**
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
app = Lennarb.new { |l| ... }
|
169
|
+
app.initializer!
|
170
|
+
run app
|
171
|
+
```
|
172
|
+
|
173
|
+
2. **Set response status**
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
app.get '/api' do |req, res|
|
177
|
+
res.status = 200
|
178
|
+
res.json('{"status": "ok"}')
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
3. **Use appropriate response types**
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
# HTML for web pages
|
186
|
+
res.html('<h1>Web Page</h1>')
|
187
|
+
|
188
|
+
# JSON for APIs
|
189
|
+
res.json('{"data": "value"}')
|
190
|
+
|
191
|
+
# Text for simple responses
|
192
|
+
res.text('Hello')
|
193
|
+
```
|
194
|
+
|
195
|
+
## Support
|
196
|
+
|
197
|
+
For help and bug reports, please visit:
|
198
|
+
|
199
|
+
- GitHub Issues: [lennarb/issues](https://github.com/aristotelesbr/lennarb/issues)
|
200
|
+
|
201
|
+
Now you can run your app!
|
data/guides/links.yaml
ADDED
@@ -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,44 +1,86 @@
|
|
1
|
-
|
1
|
+
class Lennarb
|
2
|
+
class Request < Rack::Request
|
3
|
+
# The environment variables of the request
|
4
|
+
#
|
5
|
+
# @returns [Hash]
|
6
|
+
#
|
7
|
+
attr_reader :env
|
2
8
|
|
3
|
-
#
|
4
|
-
#
|
9
|
+
# Initialize the request object
|
10
|
+
#
|
11
|
+
# @parameter [Hash] env
|
12
|
+
# @parameter [Hash] route_params
|
13
|
+
#
|
14
|
+
# @returns [Request]
|
15
|
+
#
|
16
|
+
def initialize(env, route_params = {})
|
17
|
+
super(env)
|
18
|
+
@route_params = route_params
|
19
|
+
end
|
5
20
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
21
|
+
# Get the request body
|
22
|
+
#
|
23
|
+
# @returns [String]
|
24
|
+
#
|
25
|
+
def params = @params ||= super.merge(@route_params)&.transform_keys(&:to_sym)
|
26
|
+
|
27
|
+
# Get the request path
|
28
|
+
#
|
29
|
+
# @returns [String]
|
30
|
+
#
|
31
|
+
def path = @path ||= super.split("?").first
|
32
|
+
|
33
|
+
# Read the body of the request
|
34
|
+
#
|
35
|
+
# @returns [String]
|
36
|
+
#
|
37
|
+
def body = @body ||= super.read
|
38
|
+
|
39
|
+
# Get the query parameters
|
40
|
+
#
|
41
|
+
# @returns [Hash]
|
42
|
+
#
|
43
|
+
def query_params
|
44
|
+
@query_params ||= Rack::Utils.parse_nested_query(query_string).transform_keys(&:to_sym)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the headers of the request
|
48
|
+
#
|
49
|
+
def headers
|
50
|
+
@headers ||= env.select { |key, _| key.start_with?("HTTP_") }
|
51
|
+
end
|
52
|
+
|
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?
|
70
|
+
|
71
|
+
def []=(key, value)
|
72
|
+
env[key] = value
|
73
|
+
end
|
74
|
+
|
75
|
+
def [](key)
|
76
|
+
env[key]
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def ip_address
|
82
|
+
forwarded_for = headers["HTTP_X_FORWARDED_FOR"]
|
83
|
+
forwarded_for ? forwarded_for.split(",").first.strip : env["REMOTE_ADDR"]
|
84
|
+
end
|
85
|
+
end
|
44
86
|
end
|