roda 3.108.0 → 3.109.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/lib/roda/opts.rb +22 -0
- data/lib/roda/plugins/Integer_matcher_max.rb +1 -1
- data/lib/roda/plugins/_add_script_name.rb +14 -0
- data/lib/roda/plugins/_json.rb +22 -0
- data/lib/roda/plugins/_symbol_class_matchers.rb +6 -6
- data/lib/roda/plugins/additional_render_engines.rb +5 -1
- data/lib/roda/plugins/additional_view_directories.rb +5 -1
- data/lib/roda/plugins/assets.rb +7 -10
- data/lib/roda/plugins/autoload_hash_branches.rb +9 -3
- data/lib/roda/plugins/autoload_named_routes.rb +9 -3
- data/lib/roda/plugins/capture_erb.rb +5 -1
- data/lib/roda/plugins/chunked.rb +11 -5
- data/lib/roda/plugins/class_level_routing.rb +5 -3
- data/lib/roda/plugins/class_matchers.rb +3 -1
- data/lib/roda/plugins/common_logger.rb +7 -3
- data/lib/roda/plugins/conditional_sessions.rb +1 -1
- data/lib/roda/plugins/content_for.rb +5 -1
- data/lib/roda/plugins/content_security_policy.rb +6 -2
- data/lib/roda/plugins/cookie_flags.rb +6 -2
- data/lib/roda/plugins/cookies.rb +7 -3
- data/lib/roda/plugins/custom_block_results.rb +5 -3
- data/lib/roda/plugins/custom_matchers.rb +4 -2
- data/lib/roda/plugins/default_headers.rb +2 -5
- data/lib/roda/plugins/default_status.rb +1 -0
- data/lib/roda/plugins/each_part.rb +19 -23
- data/lib/roda/plugins/environments.rb +3 -7
- data/lib/roda/plugins/error_email.rb +9 -5
- data/lib/roda/plugins/error_handler.rb +5 -3
- data/lib/roda/plugins/error_mail.rb +6 -2
- data/lib/roda/plugins/hash_branch_view_subdir.rb +6 -4
- data/lib/roda/plugins/hash_branches.rb +7 -5
- data/lib/roda/plugins/hash_paths.rb +7 -5
- data/lib/roda/plugins/hash_public.rb +11 -7
- data/lib/roda/plugins/hash_public_cache.rb +9 -7
- data/lib/roda/plugins/hash_routes.rb +5 -3
- data/lib/roda/plugins/heartbeat.rb +6 -2
- data/lib/roda/plugins/hmac_paths.rb +9 -3
- data/lib/roda/plugins/hooks.rb +13 -10
- data/lib/roda/plugins/host_authorization.rb +7 -2
- data/lib/roda/plugins/host_routing.rb +7 -2
- data/lib/roda/plugins/invalid_request_body.rb +20 -4
- data/lib/roda/plugins/ip_from_header.rb +5 -1
- data/lib/roda/plugins/json.rb +10 -8
- data/lib/roda/plugins/json_parser.rb +26 -12
- data/lib/roda/plugins/mail_processor.rb +9 -6
- data/lib/roda/plugins/mailer.rb +5 -3
- data/lib/roda/plugins/match_affix.rb +6 -1
- data/lib/roda/plugins/match_hook_args.rb +7 -5
- data/lib/roda/plugins/middleware.rb +13 -7
- data/lib/roda/plugins/module_include.rb +1 -1
- data/lib/roda/plugins/multi_public.rb +6 -2
- data/lib/roda/plugins/multi_route.rb +1 -1
- data/lib/roda/plugins/multi_run.rb +13 -17
- data/lib/roda/plugins/named_routes.rb +8 -6
- data/lib/roda/plugins/named_templates.rb +5 -3
- data/lib/roda/plugins/path.rb +16 -14
- data/lib/roda/plugins/path_rewriter.rb +8 -5
- data/lib/roda/plugins/permissions_policy.rb +7 -3
- data/lib/roda/plugins/public.rb +11 -10
- data/lib/roda/plugins/recheck_precompiled_assets.rb +1 -1
- data/lib/roda/plugins/redirect_http_to_https.rb +6 -3
- data/lib/roda/plugins/render.rb +34 -49
- data/lib/roda/plugins/render_coverage.rb +6 -3
- data/lib/roda/plugins/render_locals.rb +12 -6
- data/lib/roda/plugins/request_aref.rb +1 -1
- data/lib/roda/plugins/response_content_type.rb +7 -3
- data/lib/roda/plugins/route_csrf.rb +7 -2
- data/lib/roda/plugins/run_append_slash.rb +5 -1
- data/lib/roda/plugins/sec_fetch_site_csrf.rb +6 -2
- data/lib/roda/plugins/sessions.rb +15 -11
- data/lib/roda/plugins/shape_friendly.rb +3 -1
- data/lib/roda/plugins/sinatra_helpers.rb +7 -3
- data/lib/roda/plugins/status_handler.rb +5 -3
- data/lib/roda/plugins/strip_path_prefix.rb +4 -2
- data/lib/roda/plugins/symbol_matchers.rb +3 -1
- data/lib/roda/plugins/symbol_views.rb +1 -1
- data/lib/roda/plugins/timestamp_public.rb +8 -4
- data/lib/roda/plugins/type_routing.rb +9 -5
- data/lib/roda/plugins/typecast_params.rb +2 -2
- data/lib/roda/plugins/typecast_params_sized_integers.rb +1 -1
- data/lib/roda/plugins/url_escape.rb +2 -14
- data/lib/roda/plugins.rb +10 -0
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +25 -14
- metadata +6 -3
|
@@ -20,11 +20,15 @@ class Roda
|
|
|
20
20
|
app.opts[:ip_from_header_env_key] = "HTTP_#{header.upcase.tr('-', '_')}".freeze
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
module ClassMethods
|
|
24
|
+
RodaPlugins.opt_attr_reader(self, :ip_from_header_env_key)
|
|
25
|
+
end
|
|
26
|
+
|
|
23
27
|
module RequestMethods
|
|
24
28
|
# Return the IP address continained in the configured header, if present.
|
|
25
29
|
# Fallback to the default behavior if not present.
|
|
26
30
|
def ip
|
|
27
|
-
@env[roda_class.
|
|
31
|
+
@env[roda_class.ip_from_header_env_key] || super
|
|
28
32
|
end
|
|
29
33
|
end
|
|
30
34
|
end
|
data/lib/roda/plugins/json.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
3
|
class Roda
|
|
6
4
|
module RodaPlugins
|
|
7
5
|
# The json plugin allows match blocks to return
|
|
@@ -57,6 +55,7 @@ class Roda
|
|
|
57
55
|
# not support treating String, FalseClass, or NilClass values as JSON.
|
|
58
56
|
module Json
|
|
59
57
|
def self.load_dependencies(app, opts=OPTS)
|
|
58
|
+
app.plugin :_json
|
|
60
59
|
app.plugin :custom_block_results
|
|
61
60
|
end
|
|
62
61
|
|
|
@@ -69,10 +68,10 @@ class Roda
|
|
|
69
68
|
classes.uniq!
|
|
70
69
|
classes.freeze
|
|
71
70
|
classes.each do |klass|
|
|
72
|
-
app.
|
|
71
|
+
app.custom_block_results[klass] = :handle_json_block_result
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
app.opts[:json_result_serializer] = opts[:serializer] || app.
|
|
74
|
+
app.opts[:json_result_serializer] = opts[:serializer] || app.json_result_serializer || app.json_serializer
|
|
76
75
|
|
|
77
76
|
app.opts[:json_result_include_request] = opts[:include_request] if opts.has_key?(:include_request)
|
|
78
77
|
|
|
@@ -80,6 +79,10 @@ class Roda
|
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
module ClassMethods
|
|
82
|
+
RodaPlugins.opt_attr_reader(self, :json_result_serializer)
|
|
83
|
+
RodaPlugins.opt_attr_reader(self, :json_result_include_request, name: :json_result_include_request?)
|
|
84
|
+
RodaPlugins.opt_attr_reader(self, :json_result_content_type)
|
|
85
|
+
|
|
83
86
|
# The classes that should be automatically converted to json
|
|
84
87
|
def json_result_classes
|
|
85
88
|
# RODA4: remove, only used by previous implementation.
|
|
@@ -91,7 +94,7 @@ class Roda
|
|
|
91
94
|
# Handle a result for one of the registered JSON result classes
|
|
92
95
|
# by converting the result to JSON.
|
|
93
96
|
def handle_json_block_result(result)
|
|
94
|
-
@_response[RodaResponseHeaders::CONTENT_TYPE] ||=
|
|
97
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] ||= self.class.json_result_content_type
|
|
95
98
|
@_request.send(:convert_to_json, result)
|
|
96
99
|
end
|
|
97
100
|
end
|
|
@@ -102,10 +105,9 @@ class Roda
|
|
|
102
105
|
# Convert the given object to JSON. Uses to_json by default,
|
|
103
106
|
# but can use a custom serializer passed to the plugin.
|
|
104
107
|
def convert_to_json(result)
|
|
105
|
-
|
|
106
|
-
serializer = opts[:json_result_serializer]
|
|
108
|
+
serializer = roda_class.json_result_serializer
|
|
107
109
|
|
|
108
|
-
if
|
|
110
|
+
if roda_class.json_result_include_request?
|
|
109
111
|
serializer.call(result, self)
|
|
110
112
|
else
|
|
111
113
|
serializer.call(result)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
3
|
class Roda
|
|
6
4
|
module RodaPlugins
|
|
7
5
|
# The json_parser plugin parses request bodies in JSON format
|
|
@@ -22,6 +20,10 @@ class Roda
|
|
|
22
20
|
# simplecov:enable
|
|
23
21
|
private_constant :MATCH_METHOD
|
|
24
22
|
|
|
23
|
+
def self.load_dependencies(app, opts=OPTS)
|
|
24
|
+
app.plugin :_json
|
|
25
|
+
end
|
|
26
|
+
|
|
25
27
|
# Handle options for the json_parser plugin:
|
|
26
28
|
# :content_type_regexp :: A regexp used to determine if the request's
|
|
27
29
|
# content type is JSON. For backwards compatibility,
|
|
@@ -46,12 +48,12 @@ class Roda
|
|
|
46
48
|
# :always will wrap all values, and a value of :unless_hash will
|
|
47
49
|
# only wrap values that are not already hashes.
|
|
48
50
|
def self.configure(app, opts=OPTS)
|
|
49
|
-
app.opts[:json_parser_error_handler] = opts[:error_handler] || app.
|
|
50
|
-
app.opts[:json_parser_parser] = opts[:parser] || app.
|
|
51
|
+
app.opts[:json_parser_error_handler] = opts[:error_handler] || app.json_parser_error_handler || DEFAULT_ERROR_HANDLER
|
|
52
|
+
app.opts[:json_parser_parser] = opts[:parser] || app.json_parser_parser || app.json_parser
|
|
51
53
|
app.opts[:json_parser_include_request] = opts[:include_request] if opts.has_key?(:include_request)
|
|
52
|
-
app.opts[:json_parser_content_type_regexp] = opts[:content_type_regexp] || app.
|
|
54
|
+
app.opts[:json_parser_content_type_regexp] = opts[:content_type_regexp] || app.json_parser_content_type_regexp
|
|
53
55
|
|
|
54
|
-
unless app.
|
|
56
|
+
unless app.json_parser_content_type_regexp
|
|
55
57
|
# RODA4: Switch to /\Aapplication\/(?:vnd\.api\+)?json\b/i by default
|
|
56
58
|
RodaPlugins.warn(':content_type_regexp option not provided to json_parser plugin, using /json/ for backwards compatibility, which may be insecure, using /\Aapplication\/json\b/i or /\Aapplication\/(?:vnd\.api\+)?json\b/i is recommended')
|
|
57
59
|
app.opts[:json_parser_content_type_regexp] = /json/
|
|
@@ -67,6 +69,14 @@ class Roda
|
|
|
67
69
|
end
|
|
68
70
|
end
|
|
69
71
|
|
|
72
|
+
module ClassMethods
|
|
73
|
+
RodaPlugins.opt_attr_reader(self, :json_parser_error_handler)
|
|
74
|
+
RodaPlugins.opt_attr_reader(self, :json_parser_parser)
|
|
75
|
+
RodaPlugins.opt_attr_reader(self, :json_parser_include_request, name: :json_parser_include_request?)
|
|
76
|
+
RodaPlugins.opt_attr_reader(self, :json_parser_content_type_regexp)
|
|
77
|
+
RodaPlugins.opt_attr_reader(self, :json_parser_wrap)
|
|
78
|
+
end
|
|
79
|
+
|
|
70
80
|
module RequestMethods
|
|
71
81
|
# If the Content-Type header in the request includes "json",
|
|
72
82
|
# parse the request body as JSON. Ignore an empty request body.
|
|
@@ -76,7 +86,7 @@ class Roda
|
|
|
76
86
|
return post_params
|
|
77
87
|
end
|
|
78
88
|
|
|
79
|
-
unless (input = env["rack.input"]) && (content_type = self.content_type) && roda_class.
|
|
89
|
+
unless (input = env["rack.input"]) && (content_type = self.content_type) && roda_class.json_parser_content_type_regexp.send(MATCH_METHOD, content_type)
|
|
80
90
|
return super
|
|
81
91
|
end
|
|
82
92
|
|
|
@@ -85,10 +95,10 @@ class Roda
|
|
|
85
95
|
begin
|
|
86
96
|
json_params = parse_json(str)
|
|
87
97
|
rescue
|
|
88
|
-
roda_class.
|
|
98
|
+
roda_class.json_parser_error_handler.call(self)
|
|
89
99
|
end
|
|
90
100
|
|
|
91
|
-
wrap = roda_class.
|
|
101
|
+
wrap = roda_class.json_parser_wrap
|
|
92
102
|
if wrap == :always || (wrap == :unless_hash && !json_params.is_a?(Hash))
|
|
93
103
|
json_params = {"_json"=>json_params}
|
|
94
104
|
end
|
|
@@ -99,9 +109,13 @@ class Roda
|
|
|
99
109
|
private
|
|
100
110
|
|
|
101
111
|
def parse_json(str)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
roda_class.
|
|
112
|
+
parser = roda_class.json_parser_parser
|
|
113
|
+
|
|
114
|
+
if roda_class.json_parser_include_request?
|
|
115
|
+
parser.call(str, self)
|
|
116
|
+
else
|
|
117
|
+
parser.call(str)
|
|
118
|
+
end
|
|
105
119
|
end
|
|
106
120
|
|
|
107
121
|
# Rack 3 dropped requirement that input be rewindable
|
|
@@ -285,11 +285,14 @@ class Roda
|
|
|
285
285
|
class UnhandledMail < StandardError; end
|
|
286
286
|
|
|
287
287
|
module ClassMethods
|
|
288
|
+
RodaPlugins.opt_attr_reader(self, :mail_processor_string_routes)
|
|
289
|
+
RodaPlugins.opt_attr_reader(self, :mail_processor_regexp_routes)
|
|
290
|
+
|
|
288
291
|
# Freeze the rcpt routes if they are present.
|
|
289
292
|
def freeze
|
|
290
|
-
if string_routes =
|
|
293
|
+
if string_routes = mail_processor_string_routes.freeze
|
|
291
294
|
string_routes.freeze
|
|
292
|
-
|
|
295
|
+
mail_processor_regexp_routes.freeze
|
|
293
296
|
end
|
|
294
297
|
super
|
|
295
298
|
end
|
|
@@ -337,12 +340,12 @@ class Roda
|
|
|
337
340
|
unless string_meth
|
|
338
341
|
string_meth = define_roda_method("mail_processor_string_route_#{address}", 1, &convert_route_block(block))
|
|
339
342
|
end
|
|
340
|
-
|
|
343
|
+
mail_processor_string_routes[address] = string_meth
|
|
341
344
|
when Regexp
|
|
342
345
|
unless regexp_meth
|
|
343
346
|
regexp_meth = define_roda_method("mail_processor_regexp_route_#{address}", :any, &convert_route_block(block))
|
|
344
347
|
end
|
|
345
|
-
|
|
348
|
+
mail_processor_regexp_routes[address] = regexp_meth
|
|
346
349
|
else
|
|
347
350
|
raise RodaError, "invalid address format passed to rcpt, should be Array or String"
|
|
348
351
|
end
|
|
@@ -382,7 +385,7 @@ class Roda
|
|
|
382
385
|
# routes defined via the class-level +rcpt+ method, and then the
|
|
383
386
|
# normal routing tree passed in as the block.
|
|
384
387
|
def process_mail(&block)
|
|
385
|
-
if string_routes =
|
|
388
|
+
if string_routes = self.class.mail_processor_string_routes
|
|
386
389
|
addresses = mail_recipients
|
|
387
390
|
|
|
388
391
|
addresses.each do |address|
|
|
@@ -392,7 +395,7 @@ class Roda
|
|
|
392
395
|
end
|
|
393
396
|
end
|
|
394
397
|
|
|
395
|
-
|
|
398
|
+
self.class.mail_processor_regexp_routes.each do |regexp, meth|
|
|
396
399
|
addresses.each do |address|
|
|
397
400
|
if md = regexp.match(address)
|
|
398
401
|
_roda_handle_route{send(meth, @_request, *md.captures)}
|
data/lib/roda/plugins/mailer.rb
CHANGED
|
@@ -136,10 +136,12 @@ class Roda
|
|
|
136
136
|
# Set the options for the mailer. Options:
|
|
137
137
|
# :content_type :: The default content type for emails (default: text/plain)
|
|
138
138
|
def self.configure(app, opts=OPTS)
|
|
139
|
-
app.opts[:mailer] = (app.
|
|
139
|
+
app.opts[:mailer] = (app.mailer_opts||OPTS).merge(opts).freeze
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
module ClassMethods
|
|
143
|
+
RodaPlugins.opt_attr_reader(self, :mailer, name: :mailer_opts)
|
|
144
|
+
|
|
143
145
|
# Return a Mail::Message instance for the email for the given request path
|
|
144
146
|
# and arguments. Any arguments given are yielded to the appropriate +r.mail+
|
|
145
147
|
# block after any usual match block arguments. You can further manipulate the
|
|
@@ -177,7 +179,7 @@ class Roda
|
|
|
177
179
|
def mail(*args)
|
|
178
180
|
if @env["REQUEST_METHOD"] == "MAIL"
|
|
179
181
|
# RODA4: Make terminal match the default
|
|
180
|
-
send(roda_class.
|
|
182
|
+
send(roda_class.mailer_opts[:terminal] ? :_verb : :if_match, args) do |*vs|
|
|
181
183
|
yield(*(vs + @env['roda.mail_args']))
|
|
182
184
|
end
|
|
183
185
|
end
|
|
@@ -202,7 +204,7 @@ class Roda
|
|
|
202
204
|
block.call if block
|
|
203
205
|
end
|
|
204
206
|
|
|
205
|
-
if content_type = header_content_type || roda_class.
|
|
207
|
+
if content_type = header_content_type || roda_class.mailer_opts[:content_type]
|
|
206
208
|
if mail.multipart?
|
|
207
209
|
if /multipart\/mixed/ =~ mail.content_type &&
|
|
208
210
|
mail.parts.length >= 2 &&
|
|
@@ -39,13 +39,18 @@ class Roda
|
|
|
39
39
|
app.opts[:match_suffix] = suffix if suffix
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
module ClassMethods
|
|
43
|
+
RodaPlugins.opt_attr_reader(self, :match_prefix)
|
|
44
|
+
RodaPlugins.opt_attr_reader(self, :match_suffix)
|
|
45
|
+
end
|
|
46
|
+
|
|
42
47
|
module RequestClassMethods
|
|
43
48
|
private
|
|
44
49
|
|
|
45
50
|
# Use the match prefix and suffix provided when loading the plugin, or fallback
|
|
46
51
|
# to Roda's default prefix/suffix if one was not provided.
|
|
47
52
|
def consume_pattern(pattern)
|
|
48
|
-
/\A#{roda_class.
|
|
53
|
+
/\A#{roda_class.match_prefix || "/"}(?:#{pattern})#{roda_class.match_suffix || "(?=\/|\z)"}/
|
|
49
54
|
end
|
|
50
55
|
end
|
|
51
56
|
|
|
@@ -40,20 +40,22 @@ class Roda
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
module ClassMethods
|
|
43
|
+
RodaPlugins.opt_attr_reader(self, :match_hook_args)
|
|
44
|
+
|
|
43
45
|
# Freeze the array of hook methods when freezing the app
|
|
44
46
|
def freeze
|
|
45
|
-
|
|
47
|
+
match_hook_args.freeze
|
|
46
48
|
super
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
# Add a match hook that will be called with matchers and block args.
|
|
50
52
|
def add_match_hook(&block)
|
|
51
|
-
|
|
53
|
+
match_hook_args << define_roda_method("match_hook_args", :any, &block)
|
|
52
54
|
|
|
53
|
-
if
|
|
54
|
-
class_eval("alias _match_hook_args #{
|
|
55
|
+
if match_hook_args.length == 1
|
|
56
|
+
class_eval("alias _match_hook_args #{match_hook_args.first}", __FILE__, __LINE__)
|
|
55
57
|
else
|
|
56
|
-
class_eval("def _match_hook_args(v, a); #{
|
|
58
|
+
class_eval("def _match_hook_args(v, a); #{match_hook_args.map{|m| "#{m}(v, a)"}.join(';')} end", __FILE__, __LINE__)
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
public :_match_hook_args
|
|
@@ -136,10 +136,10 @@ class Roda
|
|
|
136
136
|
def initialize(mid, app, *args, &block)
|
|
137
137
|
@mid = Class.new(mid)
|
|
138
138
|
RodaPlugins.set_temp_name(@mid){"#{mid}::middleware_subclass"}
|
|
139
|
-
if @mid.
|
|
139
|
+
if @mid.middleware_next_if_not_found?
|
|
140
140
|
@mid.plugin(:not_found, &NEXT_PROC)
|
|
141
141
|
end
|
|
142
|
-
if configure = @mid.
|
|
142
|
+
if configure = @mid.middleware_configure
|
|
143
143
|
configure.call(@mid, *args, &block)
|
|
144
144
|
elsif block || !args.empty?
|
|
145
145
|
raise RodaError, "cannot provide middleware args or block unless loading middleware plugin with a block"
|
|
@@ -154,7 +154,7 @@ class Roda
|
|
|
154
154
|
res = nil
|
|
155
155
|
|
|
156
156
|
call_next = catch(:next) do
|
|
157
|
-
env[@mid.
|
|
157
|
+
env[@mid.middleware_env_var] = true
|
|
158
158
|
res = @mid.call(env)
|
|
159
159
|
false
|
|
160
160
|
end
|
|
@@ -167,7 +167,7 @@ class Roda
|
|
|
167
167
|
end
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
-
if handle_result = @mid.
|
|
170
|
+
if handle_result = @mid.middleware_handle_result
|
|
171
171
|
handle_result.call(env, res)
|
|
172
172
|
end
|
|
173
173
|
|
|
@@ -176,6 +176,12 @@ class Roda
|
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
module ClassMethods
|
|
179
|
+
RodaPlugins.opt_attr_reader(self, :middleware_env_var)
|
|
180
|
+
RodaPlugins.opt_attr_reader(self, :middleware_configure)
|
|
181
|
+
RodaPlugins.opt_attr_reader(self, :middleware_handle_result)
|
|
182
|
+
RodaPlugins.opt_attr_reader(self, :middleware_forward_response_headers)
|
|
183
|
+
RodaPlugins.opt_attr_reader(self, :middleware_next_if_not_found, name: :middleware_next_if_not_found?)
|
|
184
|
+
|
|
179
185
|
# Create a Forwarder instead of a new instance if a non-Hash is given.
|
|
180
186
|
def new(app, *args, &block)
|
|
181
187
|
if app.is_a?(Hash)
|
|
@@ -193,7 +199,7 @@ class Roda
|
|
|
193
199
|
super do |r|
|
|
194
200
|
res = instance_exec(r, &block) # call Fallback
|
|
195
201
|
if r.forward_next
|
|
196
|
-
r.env['roda.response_headers'] = response.headers if
|
|
202
|
+
r.env['roda.response_headers'] = response.headers if self.class.middleware_forward_response_headers
|
|
197
203
|
throw :next, true
|
|
198
204
|
end
|
|
199
205
|
res
|
|
@@ -205,7 +211,7 @@ class Roda
|
|
|
205
211
|
def _roda_run_main_route(r)
|
|
206
212
|
res = super
|
|
207
213
|
if r.forward_next
|
|
208
|
-
r.env['roda.response_headers'] = response.headers if
|
|
214
|
+
r.env['roda.response_headers'] = response.headers if self.class.middleware_forward_response_headers
|
|
209
215
|
throw :next, true
|
|
210
216
|
end
|
|
211
217
|
res
|
|
@@ -216,7 +222,7 @@ class Roda
|
|
|
216
222
|
# Whether to forward the request to the next application. Set only if
|
|
217
223
|
# this request is being performed for middleware.
|
|
218
224
|
def forward_next
|
|
219
|
-
env[roda_class.
|
|
225
|
+
env[roda_class.middleware_env_var]
|
|
220
226
|
end
|
|
221
227
|
end
|
|
222
228
|
end
|
|
@@ -72,7 +72,7 @@ class Roda
|
|
|
72
72
|
|
|
73
73
|
# Use the given directories to setup servers. Any opts are passed to the public plugin.
|
|
74
74
|
def self.configure(app, directories, _=OPTS)
|
|
75
|
-
roots = app.opts[:multi_public_servers] = (app.
|
|
75
|
+
roots = app.opts[:multi_public_servers] = (app.multi_public_servers || {}).dup
|
|
76
76
|
directories.each do |key, path|
|
|
77
77
|
path, headers, mime = path
|
|
78
78
|
roots[key] = RACK_FILES.new(app.expand_path(path), headers||{}, mime||'text/plain')
|
|
@@ -80,11 +80,15 @@ class Roda
|
|
|
80
80
|
roots.freeze
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
module ClassMethods
|
|
84
|
+
RodaPlugins.opt_attr_reader(self, :multi_public_servers)
|
|
85
|
+
end
|
|
86
|
+
|
|
83
87
|
module RequestMethods
|
|
84
88
|
# Serve files from the directory corresponding to the given key if the file exists and
|
|
85
89
|
# this is a GET request.
|
|
86
90
|
def multi_public(key)
|
|
87
|
-
public_serve_with(roda_class.
|
|
91
|
+
public_serve_with(roda_class.multi_public_servers.fetch(key))
|
|
88
92
|
end
|
|
89
93
|
end
|
|
90
94
|
end
|
|
@@ -94,7 +94,7 @@ class Roda
|
|
|
94
94
|
# Freeze the multi_route regexp matchers so that there can be no thread safety issues at runtime.
|
|
95
95
|
def freeze
|
|
96
96
|
super
|
|
97
|
-
|
|
97
|
+
namespaced_routes.each_key do |k|
|
|
98
98
|
self::RodaRequest.named_route_regexp(k)
|
|
99
99
|
end
|
|
100
100
|
self::RodaRequest.instance_variable_get(:@namespaced_route_regexps).freeze
|
|
@@ -70,12 +70,15 @@ class Roda
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
module ClassMethods
|
|
73
|
+
RodaPlugins.opt_attr_reader(self, :multi_run_apps)
|
|
74
|
+
RodaPlugins.opt_attr_reader(self, :multi_run_app_blocks)
|
|
75
|
+
|
|
73
76
|
# Convert app blocks into apps by calling them, in order to force autoloads
|
|
74
77
|
# and to speed up subsequent calls.
|
|
75
78
|
# Freeze the multi_run apps so that there can be no thread safety issues at runtime.
|
|
76
79
|
def freeze
|
|
77
|
-
app_blocks =
|
|
78
|
-
apps =
|
|
80
|
+
app_blocks = multi_run_app_blocks
|
|
81
|
+
apps = multi_run_apps
|
|
79
82
|
app_blocks.each do |prefix, block|
|
|
80
83
|
apps[prefix] = block.call
|
|
81
84
|
end
|
|
@@ -85,12 +88,6 @@ class Roda
|
|
|
85
88
|
super
|
|
86
89
|
end
|
|
87
90
|
|
|
88
|
-
# Hash storing rack applications to dispatch to, keyed by the prefix
|
|
89
|
-
# for the application.
|
|
90
|
-
def multi_run_apps
|
|
91
|
-
opts[:multi_run_apps]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
91
|
# Add a rack application to dispatch to for the given prefix when
|
|
95
92
|
# r.multi_run is called. If a block is given, it is called every time
|
|
96
93
|
# there is a request for the route to get the app to call. If neither
|
|
@@ -100,14 +97,14 @@ class Roda
|
|
|
100
97
|
prefix = prefix.to_s
|
|
101
98
|
if app
|
|
102
99
|
raise Roda::RodaError, "cannot provide both app and block to Roda.run" if block
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
multi_run_apps[prefix] = app
|
|
101
|
+
multi_run_app_blocks.delete(prefix)
|
|
105
102
|
elsif block
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
multi_run_apps.delete(prefix)
|
|
104
|
+
multi_run_app_blocks[prefix] = block
|
|
108
105
|
else
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
multi_run_apps.delete(prefix)
|
|
107
|
+
multi_run_app_blocks.delete(prefix)
|
|
111
108
|
end
|
|
112
109
|
self::RodaRequest.refresh_multi_run_regexp!
|
|
113
110
|
end
|
|
@@ -117,7 +114,7 @@ class Roda
|
|
|
117
114
|
# Refresh the multi_run_regexp, using the stored route prefixes,
|
|
118
115
|
# preferring longer routes before shorter routes.
|
|
119
116
|
def refresh_multi_run_regexp!
|
|
120
|
-
@multi_run_regexp = /(#{Regexp.union((roda_class.
|
|
117
|
+
@multi_run_regexp = /(#{Regexp.union((roda_class.multi_run_apps.keys + roda_class.multi_run_app_blocks.keys).sort.reverse)})/
|
|
121
118
|
end
|
|
122
119
|
|
|
123
120
|
# Refresh the multi_run_regexp if it hasn't been loaded yet.
|
|
@@ -132,8 +129,7 @@ class Roda
|
|
|
132
129
|
def multi_run
|
|
133
130
|
on self.class.multi_run_regexp do |prefix|
|
|
134
131
|
yield prefix if defined?(yield)
|
|
135
|
-
|
|
136
|
-
run(opts[:multi_run_apps][prefix] || opts[:multi_run_app_blocks][prefix].call)
|
|
132
|
+
run(roda_class.multi_run_apps[prefix] || roda_class.multi_run_app_blocks[prefix].call)
|
|
137
133
|
end
|
|
138
134
|
end
|
|
139
135
|
end
|
|
@@ -111,22 +111,24 @@ class Roda
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
module ClassMethods
|
|
114
|
+
RodaPlugins.opt_attr_reader(self, :namespaced_routes)
|
|
115
|
+
|
|
114
116
|
# Freeze the namespaced routes so that there can be no thread safety issues at runtime.
|
|
115
117
|
def freeze
|
|
116
|
-
|
|
118
|
+
namespaced_routes.freeze.each_value(&:freeze)
|
|
117
119
|
super
|
|
118
120
|
end
|
|
119
121
|
|
|
120
122
|
# Copy the named routes into the subclass when inheriting.
|
|
121
123
|
def inherited(subclass)
|
|
122
124
|
super
|
|
123
|
-
nsr = subclass.
|
|
124
|
-
|
|
125
|
+
nsr = subclass.namespaced_routes
|
|
126
|
+
namespaced_routes.each{|k, v| nsr[k] = v.dup}
|
|
125
127
|
end
|
|
126
128
|
|
|
127
129
|
# The names for the currently stored named routes
|
|
128
130
|
def named_routes(namespace=nil)
|
|
129
|
-
unless routes =
|
|
131
|
+
unless routes = namespaced_routes[namespace]
|
|
130
132
|
raise RodaError, "unsupported named_routes namespace used: #{namespace.inspect}"
|
|
131
133
|
end
|
|
132
134
|
routes.keys
|
|
@@ -134,7 +136,7 @@ class Roda
|
|
|
134
136
|
|
|
135
137
|
# Return the named route with the given name.
|
|
136
138
|
def named_route(name, namespace=nil)
|
|
137
|
-
|
|
139
|
+
namespaced_routes[namespace][name]
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
# If the given route has a name, treat it as a named route and
|
|
@@ -142,7 +144,7 @@ class Roda
|
|
|
142
144
|
# call super.
|
|
143
145
|
def route(name=nil, namespace=nil, &block)
|
|
144
146
|
if name
|
|
145
|
-
routes =
|
|
147
|
+
routes = namespaced_routes[namespace] ||= {}
|
|
146
148
|
if block
|
|
147
149
|
routes[name] = define_roda_method(routes[name] || "named_routes_#{namespace}_#{name}", 1, &convert_route_block(block))
|
|
148
150
|
elsif meth = routes.delete(name)
|
|
@@ -61,15 +61,17 @@ class Roda
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
module ClassMethods
|
|
64
|
+
RodaPlugins.opt_attr_reader(self, :named_templates)
|
|
65
|
+
|
|
64
66
|
# Freeze the named templates so that there can be no thread safety issues at runtime.
|
|
65
67
|
def freeze
|
|
66
|
-
|
|
68
|
+
named_templates.freeze
|
|
67
69
|
super
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
# Store a new template block and options for the given template name.
|
|
71
73
|
def template(name, options=nil, &block)
|
|
72
|
-
|
|
74
|
+
named_templates[name.to_s] = [options, define_roda_method("named_templates_#{name}", 0, &block)].freeze
|
|
73
75
|
nil
|
|
74
76
|
end
|
|
75
77
|
end
|
|
@@ -80,7 +82,7 @@ class Roda
|
|
|
80
82
|
# If a template name is given and it matches a named template, call
|
|
81
83
|
# the named template block to get the inline template to use.
|
|
82
84
|
def find_template(options)
|
|
83
|
-
if options[:template] && (template_opts, meth =
|
|
85
|
+
if options[:template] && (template_opts, meth = self.class.named_templates[template_name(options)]; meth)
|
|
84
86
|
if template_opts
|
|
85
87
|
options = template_opts.merge(options)
|
|
86
88
|
else
|
data/lib/roda/plugins/path.rb
CHANGED
|
@@ -80,11 +80,15 @@ class Roda
|
|
|
80
80
|
# Regexp for valid constant names, to prevent code execution.
|
|
81
81
|
VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
|
|
82
82
|
|
|
83
|
+
def self.load_dependencies(app, opts = OPTS)
|
|
84
|
+
app.plugin :_add_script_name
|
|
85
|
+
end
|
|
86
|
+
|
|
83
87
|
# Initialize the path classes when loading the plugin. Options:
|
|
84
88
|
# :by_name :: Register classes by name, which is friendlier when reloading code (defaults to
|
|
85
89
|
# true in development mode)
|
|
86
90
|
def self.configure(app, opts=OPTS)
|
|
87
|
-
app.
|
|
91
|
+
app.instance_exec do
|
|
88
92
|
self.opts[:path_class_by_name] = opts.fetch(:by_name, ENV['RACK_ENV'] == 'development')
|
|
89
93
|
self.opts[:path_classes] ||= {}
|
|
90
94
|
self.opts[:path_class_methods] ||= {}
|
|
@@ -95,15 +99,14 @@ class Roda
|
|
|
95
99
|
end
|
|
96
100
|
|
|
97
101
|
module ClassMethods
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
+
RodaPlugins.opt_attr_reader(self, :path_classes)
|
|
103
|
+
RodaPlugins.opt_attr_reader(self, :path_class_by_name, name: :path_class_by_name?)
|
|
104
|
+
RodaPlugins.opt_attr_reader(self, :path_class_methods)
|
|
102
105
|
|
|
103
106
|
# Freeze the path classes when freezing the app.
|
|
104
107
|
def freeze
|
|
105
108
|
path_classes.freeze
|
|
106
|
-
|
|
109
|
+
path_class_methods.freeze
|
|
107
110
|
super
|
|
108
111
|
end
|
|
109
112
|
|
|
@@ -113,7 +116,7 @@ class Roda
|
|
|
113
116
|
raise RodaError, "can't provide path when calling path with a class" if path && !class_name
|
|
114
117
|
raise RodaError, "can't provide options when calling path with a class" unless opts.empty?
|
|
115
118
|
raise RodaError, "must provide a block when calling path with a class" unless block
|
|
116
|
-
if
|
|
119
|
+
if path_class_by_name?
|
|
117
120
|
if class_name
|
|
118
121
|
name = name.to_s
|
|
119
122
|
else
|
|
@@ -125,7 +128,7 @@ class Roda
|
|
|
125
128
|
name = Object.class_eval(name, __FILE__, __LINE__)
|
|
126
129
|
end
|
|
127
130
|
path_classes[name] = block
|
|
128
|
-
|
|
131
|
+
path_class_methods[name] = define_roda_method("path_#{name}", :any, &block)
|
|
129
132
|
return
|
|
130
133
|
end
|
|
131
134
|
|
|
@@ -147,7 +150,7 @@ class Roda
|
|
|
147
150
|
url = opts[:url]
|
|
148
151
|
url_only = opts[:url_only]
|
|
149
152
|
relative = opts[:relative]
|
|
150
|
-
add_script_name = opts.fetch(:add_script_name
|
|
153
|
+
add_script_name = opts.fetch(:add_script_name){add_script_name?}
|
|
151
154
|
|
|
152
155
|
if relative
|
|
153
156
|
if (url || url_only)
|
|
@@ -209,7 +212,7 @@ class Roda
|
|
|
209
212
|
# Return the block related to the given class, or nil if there is no block.
|
|
210
213
|
def path_block(klass)
|
|
211
214
|
# RODA4: Remove
|
|
212
|
-
if
|
|
215
|
+
if path_class_by_name?
|
|
213
216
|
klass = klass.name
|
|
214
217
|
end
|
|
215
218
|
path_classes[klass]
|
|
@@ -222,14 +225,13 @@ class Roda
|
|
|
222
225
|
# :add_script_name option is true, this prepends the SCRIPT_NAME to the path.
|
|
223
226
|
def path(obj, *args, &block)
|
|
224
227
|
app = self.class
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
unless meth = opts[:path_class_methods][klass]
|
|
228
|
+
klass = app.path_class_by_name? ? obj.class.name : obj.class
|
|
229
|
+
unless meth = app.path_class_methods[klass]
|
|
228
230
|
raise RodaError, "unrecognized object given to Roda#path: #{obj.inspect}"
|
|
229
231
|
end
|
|
230
232
|
|
|
231
233
|
path = send(meth, obj, *args, &block)
|
|
232
|
-
path = request.script_name.to_s + path if
|
|
234
|
+
path = request.script_name.to_s + path if app.add_script_name?
|
|
233
235
|
path
|
|
234
236
|
end
|
|
235
237
|
|