lennarb 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/coverage.yaml +11 -33
- data/.github/workflows/documentation.yaml +32 -13
- data/.gitignore +9 -0
- data/.yardopts +8 -0
- data/CODE_OF_CONDUCT.md +118 -0
- data/CONTRIBUTING.md +155 -0
- data/README.pt-BR.md +147 -0
- data/Rakefile +32 -1
- data/changelog.md +38 -0
- data/gems.rb +3 -22
- data/guides/getting-started/readme.md +11 -7
- data/guides/mounting-applications/readme.md +38 -0
- data/lennarb.gemspec +4 -4
- data/lib/lennarb/app.rb +199 -118
- data/lib/lennarb/base.rb +314 -0
- data/lib/lennarb/config.rb +23 -5
- data/lib/lennarb/constants.rb +12 -0
- data/lib/lennarb/environment.rb +11 -7
- data/lib/lennarb/errors.rb +17 -0
- data/lib/lennarb/helpers.rb +40 -0
- data/lib/lennarb/hooks.rb +71 -0
- data/lib/lennarb/logger.rb +100 -0
- data/lib/lennarb/middleware/request_logger.rb +117 -0
- data/lib/lennarb/middleware_stack.rb +56 -0
- data/lib/lennarb/parameter_filter.rb +76 -0
- data/lib/lennarb/request.rb +51 -41
- data/lib/lennarb/request_handler.rb +39 -9
- data/lib/lennarb/response.rb +23 -21
- data/lib/lennarb/route_node.rb +17 -5
- data/lib/lennarb/routes.rb +43 -47
- data/lib/lennarb/version.rb +2 -2
- data/lib/lennarb.rb +22 -2
- data/logo/lennarb.svg +11 -0
- data/readme.md +176 -35
- metadata +36 -23
- data/lib/lennarb/constansts.rb +0 -6
- data/logo/lennarb.png +0 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Lennarb
|
2
|
+
module Middleware
|
3
|
+
# Request logger for the Lennarb framework
|
4
|
+
# Logs HTTP request details with color formatting
|
5
|
+
#
|
6
|
+
# @example Output:
|
7
|
+
# GET /users/123 (30ms)
|
8
|
+
# Status: 200 OK
|
9
|
+
# Params: {"id"=>"123", "password"=>"[FILTERED]"}
|
10
|
+
#
|
11
|
+
class RequestLogger
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get logger from application configuration
|
17
|
+
def logger = Lennarb::App.app.config.logger
|
18
|
+
|
19
|
+
# Process the request and log information
|
20
|
+
#
|
21
|
+
# @param [Hash] env Rack environment
|
22
|
+
# @return [Array] Rack response [status, headers, body]
|
23
|
+
def call(env)
|
24
|
+
request = Lennarb::Request.new(env)
|
25
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
26
|
+
|
27
|
+
status, headers, body = @app.call(env)
|
28
|
+
|
29
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
30
|
+
|
31
|
+
log_request(request, status, headers, duration)
|
32
|
+
|
33
|
+
[status, headers, body]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Format duration in a human-readable way
|
39
|
+
#
|
40
|
+
# @param [Float] seconds Duration in seconds
|
41
|
+
# @return [String] Formatted duration
|
42
|
+
def format_duration(seconds)
|
43
|
+
if seconds < 1
|
44
|
+
"#{(seconds * 1000).round}ms"
|
45
|
+
elsif seconds < 60
|
46
|
+
format("%.2fs", seconds)
|
47
|
+
else
|
48
|
+
minutes = (seconds / 60).to_i
|
49
|
+
seconds = (seconds % 60).round
|
50
|
+
"#{minutes}m #{seconds}s"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Log the complete request
|
55
|
+
def log_request(request, status, headers, duration)
|
56
|
+
logger.info { request_line(request, duration, status) }
|
57
|
+
|
58
|
+
logger.info { status_line(status) }
|
59
|
+
|
60
|
+
if request.params.any?
|
61
|
+
logger.info { params_line(request.params) }
|
62
|
+
end
|
63
|
+
|
64
|
+
if headers["Location"]
|
65
|
+
logger.info { redirect_line(headers["Location"]) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Format the request line
|
70
|
+
def request_line(request, duration, status)
|
71
|
+
method = request.request_method
|
72
|
+
path = filter_path(request.path)
|
73
|
+
duration_text = "(#{format_duration(duration)})"
|
74
|
+
|
75
|
+
"#{method} #{path} #{duration_text}".colorize(status_to_color(status)).bold
|
76
|
+
end
|
77
|
+
|
78
|
+
# Format the status line
|
79
|
+
def status_line(status)
|
80
|
+
status_text = "#{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
|
81
|
+
"Status: #{status_text}".colorize(status_to_color(status)).bold
|
82
|
+
end
|
83
|
+
|
84
|
+
# Format the parameters line
|
85
|
+
def params_line(params)
|
86
|
+
filtered = filter_params(params)
|
87
|
+
"Params: #{filtered.inspect}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Format the redirect line
|
91
|
+
def redirect_line(location)
|
92
|
+
"Redirect: #{location}".colorize(:yellow)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Filter the request path
|
96
|
+
def filter_path(path)
|
97
|
+
path
|
98
|
+
end
|
99
|
+
|
100
|
+
# Filter request parameters
|
101
|
+
def filter_params(params)
|
102
|
+
ParameterFilter.new.filter(params)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Determine color based on HTTP status
|
106
|
+
def status_to_color(status)
|
107
|
+
case status
|
108
|
+
when 200..299 then :green
|
109
|
+
when 300..399 then :yellow
|
110
|
+
when 400..499 then :magenta
|
111
|
+
when 500..599 then :red
|
112
|
+
else :white
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Lennarb
|
2
|
+
# Basic middleware stack implementation.
|
3
|
+
#
|
4
|
+
class MiddlewareStack
|
5
|
+
attr_reader :app
|
6
|
+
|
7
|
+
# The app's middleware stack.
|
8
|
+
#
|
9
|
+
# @param [Rack::Builder] app
|
10
|
+
#
|
11
|
+
def initialize(app = nil)
|
12
|
+
@app = app
|
13
|
+
@store = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Insert a middleware in the stack.
|
17
|
+
#
|
18
|
+
# @param [Class] middleware
|
19
|
+
# @param [Array] args
|
20
|
+
# @param [Proc] block
|
21
|
+
#
|
22
|
+
# @retrn [void]
|
23
|
+
#
|
24
|
+
def use(middleware, *args, &block)
|
25
|
+
@store << [middleware, args, block]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add a middleware to the beginning of the stack.
|
29
|
+
#
|
30
|
+
# @param [Class] middleware
|
31
|
+
# @param [Array] args
|
32
|
+
# @param [Proc] block
|
33
|
+
#
|
34
|
+
# @retrn [void]
|
35
|
+
#
|
36
|
+
def unshift(middleware, *args, &block)
|
37
|
+
@store.unshift([middleware, args, block])
|
38
|
+
end
|
39
|
+
|
40
|
+
# Clear the middleware stack.
|
41
|
+
#
|
42
|
+
# @retrn [void]
|
43
|
+
#
|
44
|
+
def clear
|
45
|
+
@store.clear
|
46
|
+
end
|
47
|
+
|
48
|
+
# Convert the middleware stack to an array.
|
49
|
+
#
|
50
|
+
# @retrn [Array]
|
51
|
+
#
|
52
|
+
def to_a
|
53
|
+
@store
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Lennarb
|
2
|
+
# Filtra parâmetros sensíveis de logs e exceções.
|
3
|
+
# Útil para evitar o vazamento de informações confidenciais.
|
4
|
+
#
|
5
|
+
# Por padrão, as seguintes chaves de parâmetros são filtradas:
|
6
|
+
#
|
7
|
+
# - `passw`
|
8
|
+
# - `email`
|
9
|
+
# - `secret`
|
10
|
+
# - `token`
|
11
|
+
# - `_key`
|
12
|
+
# - `crypt`
|
13
|
+
# - `salt`
|
14
|
+
# - `certificate`
|
15
|
+
# - `otp`
|
16
|
+
# - `ssn`
|
17
|
+
# - `cvv`
|
18
|
+
# - `cvc`
|
19
|
+
# - `signature`
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# filter = Lennarb::ParameterFilter.new
|
23
|
+
# filter.filter({ password: "secret", user: { email: "test@example.com" } })
|
24
|
+
# # => { password: "[filtered]", user: { email: "[filtered]" } }
|
25
|
+
#
|
26
|
+
class ParameterFilter
|
27
|
+
# @api private
|
28
|
+
DEFAULT_MASK = "[FILTERED]"
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
DEFAULT_FILTERS = %w[
|
32
|
+
passw email secret token _key crypt salt certificate otp ssn cvv cvc
|
33
|
+
signature
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
# Inicializa um novo filtro de parâmetros
|
37
|
+
#
|
38
|
+
# @param [Array<String, Regexp>] filters Lista de padrões para filtrar
|
39
|
+
def initialize(filters = DEFAULT_FILTERS)
|
40
|
+
@filter = Regexp.union(filters.map(&:to_s))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Filtra os parâmetros conforme o filtro configurado
|
44
|
+
#
|
45
|
+
# @param [Hash, Array] params Parâmetros a serem filtrados
|
46
|
+
# @param [String] mask Valor que substituirá os parâmetros filtrados
|
47
|
+
# @return [Hash, Array] Parâmetros filtrados
|
48
|
+
def filter(params, mask: DEFAULT_MASK)
|
49
|
+
filter_object(params.dup, mask)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Filtra recursivamente um objeto (hash ou array)
|
55
|
+
#
|
56
|
+
# @param [Object] object Objeto a ser filtrado
|
57
|
+
# @param [String] mask Valor que substituirá os parâmetros filtrados
|
58
|
+
# @return [Object] Objeto filtrado
|
59
|
+
def filter_object(object, mask)
|
60
|
+
case object
|
61
|
+
when Hash
|
62
|
+
object.each do |key, value|
|
63
|
+
object[key] = if key.to_s.match?(@filter)
|
64
|
+
mask
|
65
|
+
else
|
66
|
+
filter_object(value, mask)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
when Array
|
70
|
+
object = object.map { filter_object(it, mask) }
|
71
|
+
end
|
72
|
+
|
73
|
+
object
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/lennarb/request.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
module Lennarb
|
2
|
+
# Request object
|
3
|
+
#
|
2
4
|
class Request < Rack::Request
|
3
5
|
# The environment variables of the request
|
4
6
|
#
|
5
|
-
# @
|
7
|
+
# @return [Hash]
|
6
8
|
attr_reader :env
|
7
9
|
|
8
10
|
# Initialize the request object
|
9
11
|
#
|
10
|
-
# @
|
11
|
-
# @
|
12
|
+
# @param [Hash] env
|
13
|
+
# @param [Hash] route_params
|
12
14
|
#
|
13
|
-
# @
|
15
|
+
# @return [Request]
|
14
16
|
#
|
15
17
|
def initialize(env, route_params = {})
|
16
18
|
super(env)
|
@@ -19,7 +21,7 @@ module Lennarb
|
|
19
21
|
|
20
22
|
# Get the request parameters merged with route parameters
|
21
23
|
#
|
22
|
-
# @
|
24
|
+
# @return [Hash]
|
23
25
|
#
|
24
26
|
def params
|
25
27
|
@params ||= super.merge(@route_params)&.transform_keys(&:to_sym)
|
@@ -27,7 +29,7 @@ module Lennarb
|
|
27
29
|
|
28
30
|
# Get the request path without query string
|
29
31
|
#
|
30
|
-
# @
|
32
|
+
# @return [String]
|
31
33
|
#
|
32
34
|
def path
|
33
35
|
@path ||= super.split("?").first
|
@@ -35,7 +37,7 @@ module Lennarb
|
|
35
37
|
|
36
38
|
# Read the body of the request
|
37
39
|
#
|
38
|
-
# @
|
40
|
+
# @return [String]
|
39
41
|
#
|
40
42
|
def body
|
41
43
|
@body ||= super.read
|
@@ -43,7 +45,7 @@ module Lennarb
|
|
43
45
|
|
44
46
|
# Get the query parameters
|
45
47
|
#
|
46
|
-
# @
|
48
|
+
# @return [Hash]
|
47
49
|
#
|
48
50
|
def query_params
|
49
51
|
@query_params ||= Rack::Utils.parse_nested_query(query_string || "").transform_keys(&:to_sym)
|
@@ -51,9 +53,9 @@ module Lennarb
|
|
51
53
|
|
52
54
|
# Set a value in the environment
|
53
55
|
#
|
54
|
-
# @
|
55
|
-
# @
|
56
|
-
# @
|
56
|
+
# @param [String] key
|
57
|
+
# @param [Object] value
|
58
|
+
# @return [Object] the value
|
57
59
|
#
|
58
60
|
def []=(key, value)
|
59
61
|
env[key] = value
|
@@ -61,8 +63,8 @@ module Lennarb
|
|
61
63
|
|
62
64
|
# Get a value from the environment
|
63
65
|
#
|
64
|
-
# @
|
65
|
-
# @
|
66
|
+
# @param [String] key
|
67
|
+
# @return [Object]
|
66
68
|
#
|
67
69
|
def [](key)
|
68
70
|
env[key]
|
@@ -70,15 +72,20 @@ module Lennarb
|
|
70
72
|
|
71
73
|
# Get the headers of the request
|
72
74
|
#
|
73
|
-
# @
|
75
|
+
# @return [Hash]
|
74
76
|
#
|
75
77
|
def headers
|
76
|
-
@headers ||= env.
|
78
|
+
@headers ||= env.each_with_object({}) do |(key, value), result|
|
79
|
+
if key.start_with?("HTTP_")
|
80
|
+
header_name = key.sub("HTTP_", "").split("_").map(&:capitalize).join("-")
|
81
|
+
result[header_name] = value
|
82
|
+
end
|
83
|
+
end
|
77
84
|
end
|
78
85
|
|
79
86
|
# Get the client IP address
|
80
87
|
#
|
81
|
-
# @
|
88
|
+
# @return [String]
|
82
89
|
#
|
83
90
|
def ip
|
84
91
|
ip_address
|
@@ -86,7 +93,7 @@ module Lennarb
|
|
86
93
|
|
87
94
|
# Check if the request is secure (HTTPS)
|
88
95
|
#
|
89
|
-
# @
|
96
|
+
# @return [Boolean]
|
90
97
|
#
|
91
98
|
def secure?
|
92
99
|
scheme == "https"
|
@@ -96,7 +103,7 @@ module Lennarb
|
|
96
103
|
|
97
104
|
# Get the user agent
|
98
105
|
#
|
99
|
-
# @
|
106
|
+
# @return [String, nil]
|
100
107
|
#
|
101
108
|
def user_agent
|
102
109
|
env["HTTP_USER_AGENT"]
|
@@ -104,7 +111,7 @@ module Lennarb
|
|
104
111
|
|
105
112
|
# Get the accept header
|
106
113
|
#
|
107
|
-
# @
|
114
|
+
# @return [String, nil]
|
108
115
|
#
|
109
116
|
def accept
|
110
117
|
env["HTTP_ACCEPT"]
|
@@ -112,7 +119,7 @@ module Lennarb
|
|
112
119
|
|
113
120
|
# Get the referer header
|
114
121
|
#
|
115
|
-
# @
|
122
|
+
# @return [String, nil]
|
116
123
|
#
|
117
124
|
def referer
|
118
125
|
env["HTTP_REFERER"]
|
@@ -120,7 +127,7 @@ module Lennarb
|
|
120
127
|
|
121
128
|
# Get the host header
|
122
129
|
#
|
123
|
-
# @
|
130
|
+
# @return [String, nil]
|
124
131
|
#
|
125
132
|
def host
|
126
133
|
env["HTTP_HOST"]
|
@@ -128,7 +135,7 @@ module Lennarb
|
|
128
135
|
|
129
136
|
# Get the content length header
|
130
137
|
#
|
131
|
-
# @
|
138
|
+
# @return [String, nil]
|
132
139
|
#
|
133
140
|
def content_length
|
134
141
|
env["HTTP_CONTENT_LENGTH"]
|
@@ -136,7 +143,7 @@ module Lennarb
|
|
136
143
|
|
137
144
|
# Get the content type header
|
138
145
|
#
|
139
|
-
# @
|
146
|
+
# @return [String, nil]
|
140
147
|
#
|
141
148
|
def content_type
|
142
149
|
env["HTTP_CONTENT_TYPE"]
|
@@ -144,7 +151,7 @@ module Lennarb
|
|
144
151
|
|
145
152
|
# Check if the request is an XHR request
|
146
153
|
#
|
147
|
-
# @
|
154
|
+
# @return [Boolean]
|
148
155
|
#
|
149
156
|
def xhr?
|
150
157
|
env["HTTP_X_REQUESTED_WITH"]&.casecmp("XMLHttpRequest")&.zero? || false
|
@@ -152,7 +159,7 @@ module Lennarb
|
|
152
159
|
|
153
160
|
# Check if the request is a JSON request
|
154
161
|
#
|
155
|
-
# @
|
162
|
+
# @return [Boolean]
|
156
163
|
#
|
157
164
|
def json?
|
158
165
|
content_type&.include?("application/json")
|
@@ -160,12 +167,11 @@ module Lennarb
|
|
160
167
|
|
161
168
|
# Parse JSON body if content type is application/json
|
162
169
|
#
|
163
|
-
# @
|
170
|
+
# @return [Hash, nil]
|
164
171
|
#
|
165
172
|
def json_body
|
166
173
|
return nil unless json?
|
167
174
|
@json_body ||= begin
|
168
|
-
require "json"
|
169
175
|
JSON.parse(body, symbolize_names: true)
|
170
176
|
rescue JSON::ParserError
|
171
177
|
nil
|
@@ -174,7 +180,7 @@ module Lennarb
|
|
174
180
|
|
175
181
|
# Check if the request is an AJAX request (alias for xhr?)
|
176
182
|
#
|
177
|
-
# @
|
183
|
+
# @return [Boolean]
|
178
184
|
#
|
179
185
|
def ajax?
|
180
186
|
xhr?
|
@@ -182,19 +188,21 @@ module Lennarb
|
|
182
188
|
|
183
189
|
# Get the requested format (.html, .json, etc)
|
184
190
|
#
|
185
|
-
# @
|
191
|
+
# @return [Symbol, nil]
|
186
192
|
#
|
187
193
|
def format
|
188
|
-
|
189
|
-
|
194
|
+
@format ||= begin
|
195
|
+
path_info = env["PATH_INFO"]
|
196
|
+
return nil unless path_info.include?(".")
|
190
197
|
|
191
|
-
|
192
|
-
|
198
|
+
extension = File.extname(path_info).delete(".")
|
199
|
+
extension.empty? ? nil : extension.to_sym
|
200
|
+
end
|
193
201
|
end
|
194
202
|
|
195
203
|
# Check if the request is a GET request
|
196
204
|
#
|
197
|
-
# @
|
205
|
+
# @return [Boolean]
|
198
206
|
#
|
199
207
|
def get?
|
200
208
|
request_method == "GET"
|
@@ -202,7 +210,7 @@ module Lennarb
|
|
202
210
|
|
203
211
|
# Check if the request is a POST request
|
204
212
|
#
|
205
|
-
# @
|
213
|
+
# @return [Boolean]
|
206
214
|
#
|
207
215
|
def post?
|
208
216
|
request_method == "POST"
|
@@ -210,7 +218,7 @@ module Lennarb
|
|
210
218
|
|
211
219
|
# Check if the request is a PUT request
|
212
220
|
#
|
213
|
-
# @
|
221
|
+
# @return [Boolean]
|
214
222
|
#
|
215
223
|
def put?
|
216
224
|
request_method == "PUT"
|
@@ -218,7 +226,7 @@ module Lennarb
|
|
218
226
|
|
219
227
|
# Check if the request is a DELETE request
|
220
228
|
#
|
221
|
-
# @
|
229
|
+
# @return [Boolean]
|
222
230
|
#
|
223
231
|
def delete?
|
224
232
|
request_method == "DELETE"
|
@@ -226,7 +234,7 @@ module Lennarb
|
|
226
234
|
|
227
235
|
# Check if the request is a HEAD request
|
228
236
|
#
|
229
|
-
# @
|
237
|
+
# @return [Boolean]
|
230
238
|
#
|
231
239
|
def head?
|
232
240
|
request_method == "HEAD"
|
@@ -234,7 +242,7 @@ module Lennarb
|
|
234
242
|
|
235
243
|
# Check if the request is a PATCH request
|
236
244
|
#
|
237
|
-
# @
|
245
|
+
# @return [Boolean]
|
238
246
|
#
|
239
247
|
def patch?
|
240
248
|
request_method == "PATCH"
|
@@ -244,11 +252,13 @@ module Lennarb
|
|
244
252
|
|
245
253
|
# Get the client IP address
|
246
254
|
#
|
247
|
-
# @
|
255
|
+
# @return [String]
|
248
256
|
#
|
249
257
|
def ip_address
|
250
258
|
forwarded_for = env["HTTP_X_FORWARDED_FOR"]
|
251
|
-
|
259
|
+
return forwarded_for.split(",").map(&:strip).first if forwarded_for
|
260
|
+
|
261
|
+
env["REMOTE_ADDR"]
|
252
262
|
end
|
253
263
|
end
|
254
264
|
end
|
@@ -1,31 +1,61 @@
|
|
1
1
|
module Lennarb
|
2
|
+
# Handles requests and executes routes with helpers and hooks.
|
3
|
+
#
|
2
4
|
class RequestHandler
|
3
|
-
Lennarb::Error = Class.new(StandardError)
|
4
|
-
|
5
5
|
attr_reader :app
|
6
6
|
|
7
|
+
# Initialize the request handler
|
8
|
+
#
|
9
|
+
# @param [Lennarb::App] app The application instance
|
7
10
|
def initialize(app)
|
8
11
|
@app = app
|
9
12
|
end
|
10
13
|
|
14
|
+
# Handle a request according to Rack interface
|
15
|
+
#
|
16
|
+
# @param [Hash] env The Rack environment
|
17
|
+
# @return [Array] Rack response [status, headers, body]
|
11
18
|
def call(env)
|
12
19
|
http_method = env[Rack::REQUEST_METHOD].to_sym
|
13
20
|
parts = env[Rack::PATH_INFO].split("/").reject(&:empty?)
|
14
21
|
block, params = app.routes.match_route(parts, http_method)
|
15
22
|
|
16
|
-
unless block
|
17
|
-
return [404, {"content-type" => CONTENT_TYPE[:TEXT]}, ["Not Found"]]
|
18
|
-
end
|
23
|
+
return [404, {"content-type" => CONTENT_TYPE[:TEXT]}, ["Not Found"]] unless block
|
19
24
|
|
20
|
-
req = Request.new(env, params)
|
25
|
+
req = Request.new(env, params || {})
|
21
26
|
res = Response.new
|
22
27
|
|
23
28
|
catch(:halt) do
|
24
|
-
|
29
|
+
context = create_context
|
30
|
+
|
31
|
+
Hooks.execute(context, app.class, :before, req, res)
|
32
|
+
|
33
|
+
context.instance_exec(req, res, params, &block)
|
34
|
+
|
35
|
+
Hooks.execute(context, app.class, :after, req, res)
|
36
|
+
|
25
37
|
res.finish
|
26
|
-
rescue Lennarb::Error =>
|
27
|
-
|
38
|
+
rescue Lennarb::Error => e
|
39
|
+
app.class.config.logger.error("Error: #{e.message}")
|
40
|
+
app.class.config.logger.error(e.backtrace.first)
|
41
|
+
[500, {"content-type" => "text/plain"}, ["Internal Server Error"]]
|
28
42
|
end
|
29
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Create a context object with app's helper methods
|
48
|
+
#
|
49
|
+
# @return [Object] A context object with helper methods
|
50
|
+
def create_context
|
51
|
+
context = Object.new
|
52
|
+
|
53
|
+
context.define_singleton_method(:app) { app }
|
54
|
+
|
55
|
+
helpers_module = Helpers.for(app.class)
|
56
|
+
context.extend(helpers_module) if helpers_module
|
57
|
+
|
58
|
+
context
|
59
|
+
end
|
30
60
|
end
|
31
61
|
end
|