roda 3.54.0 → 3.57.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/CHANGELOG +32 -0
- data/doc/conventions.rdoc +14 -11
- data/doc/release_notes/3.55.0.txt +12 -0
- data/doc/release_notes/3.56.0.txt +33 -0
- data/doc/release_notes/3.57.0.txt +34 -0
- data/lib/roda/plugins/chunked.rb +2 -2
- data/lib/roda/plugins/common_logger.rb +12 -1
- data/lib/roda/plugins/cookies.rb +2 -0
- data/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/lib/roda/plugins/hash_branches.rb +145 -0
- data/lib/roda/plugins/hash_paths.rb +128 -0
- data/lib/roda/plugins/hash_routes.rb +13 -176
- data/lib/roda/plugins/json_parser.rb +6 -2
- data/lib/roda/plugins/middleware.rb +17 -2
- data/lib/roda/plugins/multi_public.rb +8 -0
- data/lib/roda/plugins/multi_route.rb +1 -1
- data/lib/roda/plugins/multi_view.rb +0 -4
- data/lib/roda/plugins/named_routes.rb +1 -2
- data/lib/roda/plugins/not_allowed.rb +13 -0
- data/lib/roda/plugins/public.rb +8 -0
- data/lib/roda/plugins/render.rb +5 -3
- data/lib/roda/plugins/route_csrf.rb +1 -0
- data/lib/roda/plugins/run_append_slash.rb +1 -1
- data/lib/roda/plugins/run_require_slash.rb +46 -0
- data/lib/roda/plugins/sessions.rb +1 -0
- data/lib/roda/plugins/sinatra_helpers.rb +10 -0
- data/lib/roda/plugins/static.rb +2 -0
- data/lib/roda/plugins/static_routing.rb +1 -1
- data/lib/roda/plugins/status_303.rb +6 -3
- data/lib/roda/plugins/status_handler.rb +35 -9
- data/lib/roda/plugins/symbol_status.rb +2 -0
- data/lib/roda/plugins/unescape_path.rb +2 -0
- data/lib/roda/request.rb +35 -1
- data/lib/roda/response.rb +5 -0
- data/lib/roda/version.rb +1 -1
- metadata +30 -6
|
@@ -3,58 +3,9 @@
|
|
|
3
3
|
#
|
|
4
4
|
class Roda
|
|
5
5
|
module RodaPlugins
|
|
6
|
-
# The hash_routes plugin
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# is a static string.
|
|
10
|
-
#
|
|
11
|
-
# For a basic replacement of the multi_route plugin, you can replace class level
|
|
12
|
-
# <tt>route('segment')</tt> calls with <tt>hash_branch('segment')</tt>:
|
|
13
|
-
#
|
|
14
|
-
# class App < Roda
|
|
15
|
-
# plugin :hash_routes
|
|
16
|
-
#
|
|
17
|
-
# hash_branch("a") do |r|
|
|
18
|
-
# # /a branch
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# hash_branch("b") do |r|
|
|
22
|
-
# # /b branch
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# route do |r|
|
|
26
|
-
# r.hash_branches
|
|
27
|
-
# end
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# With the above routing tree, the +r.hash_branches+ call in the main routing tree,
|
|
31
|
-
# will dispatch requests for the +/a+ and +/b+ branches of the tree to the appropriate
|
|
32
|
-
# routing blocks.
|
|
33
|
-
#
|
|
34
|
-
# In addition to supporting routing via the next segment, you can also support similar
|
|
35
|
-
# routing for entire remaining path using the +hash_path+ class method:
|
|
36
|
-
#
|
|
37
|
-
# class App < Roda
|
|
38
|
-
# plugin :hash_routes
|
|
39
|
-
#
|
|
40
|
-
# hash_path("/a") do |r|
|
|
41
|
-
# # /a path
|
|
42
|
-
# end
|
|
43
|
-
#
|
|
44
|
-
# hash_path("/a/b") do |r|
|
|
45
|
-
# # /a/b path
|
|
46
|
-
# end
|
|
47
|
-
#
|
|
48
|
-
# route do |r|
|
|
49
|
-
# r.hash_paths
|
|
50
|
-
# end
|
|
51
|
-
# end
|
|
52
|
-
#
|
|
53
|
-
# With the above routing tree, the +r.hash_paths+ call will dispatch requests for the +/a+ and
|
|
54
|
-
# +/a/b+ request paths.
|
|
55
|
-
#
|
|
56
|
-
# You can combine the two approaches, and use +r.hash_routes+ to first try routing the
|
|
57
|
-
# full path, and then try routing the next segment:
|
|
6
|
+
# The hash_routes plugin builds on top of the hash_branches and hash_paths plugins, and adds
|
|
7
|
+
# a DSL for configuring hash branches and paths. It also adds an +r.hash_routes+ method for
|
|
8
|
+
# first attempting dispatch to the configured hash_paths, then to the configured hash_branches:
|
|
58
9
|
#
|
|
59
10
|
# class App < Roda
|
|
60
11
|
# plugin :hash_routes
|
|
@@ -84,60 +35,14 @@ class Roda
|
|
|
84
35
|
# +hash_path+ block. Other requests for the +/a+ branch, and all requests for the +/b+
|
|
85
36
|
# branch will be routed to the appropriate +hash_branch+ block.
|
|
86
37
|
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# class App < Roda
|
|
91
|
-
# plugin :hash_routes
|
|
92
|
-
#
|
|
93
|
-
# # Only one argument used, so the namespace defaults to '', and the argument
|
|
94
|
-
# # specifies the route name
|
|
95
|
-
# hash_branch("a") do |r|
|
|
96
|
-
# # uses '/a' as the namespace when looking up routes,
|
|
97
|
-
# # as that part of the path has been routed now
|
|
98
|
-
# r.hash_routes
|
|
99
|
-
# end
|
|
100
|
-
#
|
|
101
|
-
# # Two arguments used, so first specifies the namespace and the second specifies
|
|
102
|
-
# # the route name
|
|
103
|
-
# hash_branch('', "b") do |r|
|
|
104
|
-
# # uses :b as the namespace when looking up routes, as that was explicitly specified
|
|
105
|
-
# r.hash_routes(:b)
|
|
106
|
-
# end
|
|
107
|
-
#
|
|
108
|
-
# hash_path("/a", "/b") do |r|
|
|
109
|
-
# # /a/b path
|
|
110
|
-
# end
|
|
111
|
-
#
|
|
112
|
-
# hash_path("/a", "/c") do |r|
|
|
113
|
-
# # /a/c path
|
|
114
|
-
# end
|
|
115
|
-
#
|
|
116
|
-
# hash_path(:b, "/b") do |r|
|
|
117
|
-
# # /b/b path
|
|
118
|
-
# end
|
|
119
|
-
#
|
|
120
|
-
# hash_path(:b, "/c") do |r|
|
|
121
|
-
# # /b/c path
|
|
122
|
-
# end
|
|
123
|
-
#
|
|
124
|
-
# route do |r|
|
|
125
|
-
# # uses '' as the namespace, as no part of the path has been routed yet
|
|
126
|
-
# r.hash_branches
|
|
127
|
-
# end
|
|
128
|
-
# end
|
|
129
|
-
#
|
|
130
|
-
# With the above routing tree, requests for the +/a+ and +/b+ branches will be
|
|
131
|
-
# dispatched to the appropriate +hash_branch+ block. Those blocks will the dispatch
|
|
132
|
-
# to the +hash_path+ blocks, with the +/a+ branch using the implicit namespace of
|
|
133
|
-
# +/a+, and the +/b+ branch using the explicit namespace of +:b+. In general, it
|
|
134
|
-
# is best for performance to explicitly specify the namespace when calling
|
|
135
|
-
# +r.hash_branches+, +r.hash_paths+, and +r.hash_routes+.
|
|
38
|
+
# It is best for performance to explicitly specify the namespace when calling
|
|
39
|
+
# +r.hash_routes+.
|
|
136
40
|
#
|
|
137
41
|
# Because specifying routes explicitly using the +hash_branch+ and +hash_path+
|
|
138
42
|
# class methods can get repetitive, the hash_routes plugin offers a DSL for DRYing
|
|
139
|
-
# the code up. This DSL is used by calling the +hash_routes+ class method.
|
|
140
|
-
#
|
|
43
|
+
# the code up. This DSL is used by calling the +hash_routes+ class method. The
|
|
44
|
+
# DSL used tries to mirror the standard Roda DSL, but it is not a normal routing
|
|
45
|
+
# tree (it's not possible to execute arbitrary code between branches during routing).
|
|
141
46
|
#
|
|
142
47
|
# class App < Roda
|
|
143
48
|
# plugin :hash_routes
|
|
@@ -264,9 +169,12 @@ class Roda
|
|
|
264
169
|
# * views
|
|
265
170
|
# * all verb methods (get, post, etc.)
|
|
266
171
|
module HashRoutes
|
|
172
|
+
def self.load_dependencies(app)
|
|
173
|
+
app.plugin :hash_branches
|
|
174
|
+
app.plugin :hash_paths
|
|
175
|
+
end
|
|
176
|
+
|
|
267
177
|
def self.configure(app)
|
|
268
|
-
app.opts[:hash_branches] ||= {}
|
|
269
|
-
app.opts[:hash_paths] ||= {}
|
|
270
178
|
app.opts[:hash_routes_methods] ||= {}
|
|
271
179
|
end
|
|
272
180
|
|
|
@@ -359,24 +267,10 @@ class Roda
|
|
|
359
267
|
module ClassMethods
|
|
360
268
|
# Freeze the hash_routes metadata when freezing the app.
|
|
361
269
|
def freeze
|
|
362
|
-
opts[:hash_branches].freeze.each_value(&:freeze)
|
|
363
|
-
opts[:hash_paths].freeze.each_value(&:freeze)
|
|
364
270
|
opts[:hash_routes_methods].freeze
|
|
365
271
|
super
|
|
366
272
|
end
|
|
367
273
|
|
|
368
|
-
# Duplicate hash_routes metadata in subclass.
|
|
369
|
-
def inherited(subclass)
|
|
370
|
-
super
|
|
371
|
-
|
|
372
|
-
[:hash_branches, :hash_paths].each do |k|
|
|
373
|
-
h = subclass.opts[k]
|
|
374
|
-
opts[k].each do |namespace, routes|
|
|
375
|
-
h[namespace] = routes.dup
|
|
376
|
-
end
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
|
|
380
274
|
# Invoke the DSL for configuring hash routes, see DSL for methods inside the
|
|
381
275
|
# block. If the block accepts an argument, yield the DSL instance. If the
|
|
382
276
|
# block does not accept an argument, instance_exec the block in the context
|
|
@@ -393,66 +287,9 @@ class Roda
|
|
|
393
287
|
|
|
394
288
|
dsl
|
|
395
289
|
end
|
|
396
|
-
|
|
397
|
-
# Add branch handler for the given namespace and segment. If called without
|
|
398
|
-
# a block, removes the existing branch handler if it exists.
|
|
399
|
-
def hash_branch(namespace='', segment, &block)
|
|
400
|
-
segment = "/#{segment}"
|
|
401
|
-
routes = opts[:hash_branches][namespace] ||= {}
|
|
402
|
-
if block
|
|
403
|
-
routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
|
|
404
|
-
elsif meth = routes[segment]
|
|
405
|
-
routes.delete(segment)
|
|
406
|
-
remove_method(meth)
|
|
407
|
-
end
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
# Add path handler for the given namespace and path. When the
|
|
411
|
-
# r.hash_paths method is called, checks the matching namespace
|
|
412
|
-
# for the full remaining path, and dispatch to that block if
|
|
413
|
-
# there is one. If called without a block, removes the existing
|
|
414
|
-
# path handler if it exists.
|
|
415
|
-
def hash_path(namespace='', path, &block)
|
|
416
|
-
routes = opts[:hash_paths][namespace] ||= {}
|
|
417
|
-
if block
|
|
418
|
-
routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
|
|
419
|
-
elsif meth = routes[path]
|
|
420
|
-
routes.delete(path)
|
|
421
|
-
remove_method(meth)
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
290
|
end
|
|
425
291
|
|
|
426
292
|
module RequestMethods
|
|
427
|
-
# Checks the matching hash_branch namespace for a branch matching the next
|
|
428
|
-
# segment in the remaining path, and dispatch to that block if there is one.
|
|
429
|
-
def hash_branches(namespace=matched_path)
|
|
430
|
-
rp = @remaining_path
|
|
431
|
-
|
|
432
|
-
return unless rp.getbyte(0) == 47 # "/"
|
|
433
|
-
|
|
434
|
-
if routes = roda_class.opts[:hash_branches][namespace]
|
|
435
|
-
if segment_end = rp.index('/', 1)
|
|
436
|
-
if meth = routes[rp[0, segment_end]]
|
|
437
|
-
@remaining_path = rp[segment_end, 100000000]
|
|
438
|
-
always{scope.send(meth, self)}
|
|
439
|
-
end
|
|
440
|
-
elsif meth = routes[rp]
|
|
441
|
-
@remaining_path = ''
|
|
442
|
-
always{scope.send(meth, self)}
|
|
443
|
-
end
|
|
444
|
-
end
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
# Checks the matching hash_path namespace for a branch matching the
|
|
448
|
-
# remaining path, and dispatch to that block if there is one.
|
|
449
|
-
def hash_paths(namespace=matched_path)
|
|
450
|
-
if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
|
|
451
|
-
@remaining_path = ''
|
|
452
|
-
always{scope.send(meth, self)}
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
|
|
456
293
|
# Check for matches in both the hash_path and hash_branch namespaces for
|
|
457
294
|
# a matching remaining path or next segment in the remaining path, respectively.
|
|
458
295
|
def hash_routes(namespace=matched_path)
|
|
@@ -4,12 +4,16 @@ require 'json'
|
|
|
4
4
|
|
|
5
5
|
class Roda
|
|
6
6
|
module RodaPlugins
|
|
7
|
-
# The json_parser plugin parses request bodies in
|
|
7
|
+
# The json_parser plugin parses request bodies in JSON format
|
|
8
8
|
# if the request's content type specifies json. This is mostly
|
|
9
9
|
# designed for use with JSON API sites.
|
|
10
10
|
#
|
|
11
11
|
# This only parses the request body as JSON if the Content-Type
|
|
12
12
|
# header for the request includes "json".
|
|
13
|
+
#
|
|
14
|
+
# The parsed JSON body will be available in +r.POST+, just as a
|
|
15
|
+
# parsed HTML form body would be. It will also be available in
|
|
16
|
+
# +r.params+ (which merges +r.GET+ with +r.POST+).
|
|
13
17
|
module JsonParser
|
|
14
18
|
DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]}
|
|
15
19
|
|
|
@@ -25,7 +29,7 @@ class Roda
|
|
|
25
29
|
# object as the second argument, so the parser needs
|
|
26
30
|
# to respond to +call(str, request)+.
|
|
27
31
|
# :wrap :: Whether to wrap uploaded JSON data in a hash with a "_json"
|
|
28
|
-
# key. Without this, calls to r.params will fail if a non-Hash
|
|
32
|
+
# key. Without this, calls to +r.params+ will fail if a non-Hash
|
|
29
33
|
# (such as an array) is uploaded in JSON format. A value of
|
|
30
34
|
# :always will wrap all values, and a value of :unless_hash will
|
|
31
35
|
# only wrap values that are not already hashes.
|
|
@@ -73,11 +73,16 @@ class Roda
|
|
|
73
73
|
# and rack response for all requests passing through the middleware,
|
|
74
74
|
# after either the middleware or next app handles the request
|
|
75
75
|
# and returns a response.
|
|
76
|
+
# :forward_response_headers :: Whether changes to the response headers made inside
|
|
77
|
+
# the middleware's route block should be applied to the
|
|
78
|
+
# final response when the request is forwarded to the app.
|
|
79
|
+
# Defaults to false.
|
|
76
80
|
def self.configure(app, opts={}, &block)
|
|
77
81
|
app.opts[:middleware_env_var] = opts[:env_var] if opts.has_key?(:env_var)
|
|
78
82
|
app.opts[:middleware_env_var] ||= 'roda.forward_next'
|
|
79
83
|
app.opts[:middleware_configure] = block if block
|
|
80
84
|
app.opts[:middleware_handle_result] = opts[:handle_result]
|
|
85
|
+
app.opts[:middleware_forward_response_headers] = opts[:forward_response_headers]
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
# Forwarder instances are what is actually used as middleware.
|
|
@@ -108,6 +113,10 @@ class Roda
|
|
|
108
113
|
|
|
109
114
|
if call_next
|
|
110
115
|
res = @app.call(env)
|
|
116
|
+
|
|
117
|
+
if modified_headers = env.delete('roda.response_headers')
|
|
118
|
+
res[1] = modified_headers.merge(res[1])
|
|
119
|
+
end
|
|
111
120
|
end
|
|
112
121
|
|
|
113
122
|
if handle_result = @mid.opts[:middleware_handle_result]
|
|
@@ -135,7 +144,10 @@ class Roda
|
|
|
135
144
|
def call(&block)
|
|
136
145
|
super do |r|
|
|
137
146
|
res = instance_exec(r, &block) # call Fallback
|
|
138
|
-
|
|
147
|
+
if r.forward_next
|
|
148
|
+
r.env['roda.response_headers'] = response.headers if opts[:middleware_forward_response_headers]
|
|
149
|
+
throw :next, true
|
|
150
|
+
end
|
|
139
151
|
res
|
|
140
152
|
end
|
|
141
153
|
end
|
|
@@ -144,7 +156,10 @@ class Roda
|
|
|
144
156
|
# that the next middleware is called.
|
|
145
157
|
def _roda_run_main_route(r)
|
|
146
158
|
res = super
|
|
147
|
-
|
|
159
|
+
if r.forward_next
|
|
160
|
+
r.env['roda.response_headers'] = response.headers if opts[:middleware_forward_response_headers]
|
|
161
|
+
throw :next, true
|
|
162
|
+
end
|
|
148
163
|
res
|
|
149
164
|
end
|
|
150
165
|
end
|
|
@@ -8,7 +8,7 @@ class Roda
|
|
|
8
8
|
# which will check # if the first segment in the path matches a named route,
|
|
9
9
|
# and dispatch to that named route.
|
|
10
10
|
#
|
|
11
|
-
# The
|
|
11
|
+
# The hash_branches plugin offers a +r.hash_branches+ method that is similar to
|
|
12
12
|
# and performs better than the +r.multi_route+ method, and it is recommended
|
|
13
13
|
# to consider using that instead of this plugin.
|
|
14
14
|
#
|
|
@@ -16,10 +16,6 @@ class Roda
|
|
|
16
16
|
# +multi_view_compile+ class method that will take an array of view template
|
|
17
17
|
# names and construct a regexp that can be passed to +r.multi_view+.
|
|
18
18
|
#
|
|
19
|
-
# The hash_routes plugin offers a views method that is similar to and performs
|
|
20
|
-
# better than the +r.multi_view+ method, and it is recommended to consider
|
|
21
|
-
# using that instead of this plugin.
|
|
22
|
-
#
|
|
23
19
|
# Example:
|
|
24
20
|
#
|
|
25
21
|
# plugin :multi_view
|
|
@@ -145,8 +145,7 @@ class Roda
|
|
|
145
145
|
routes = opts[:namespaced_routes][namespace] ||= {}
|
|
146
146
|
if block
|
|
147
147
|
routes[name] = define_roda_method(routes[name] || "named_routes_#{namespace}_#{name}", 1, &convert_route_block(block))
|
|
148
|
-
elsif meth = routes
|
|
149
|
-
routes.delete(name)
|
|
148
|
+
elsif meth = routes.delete(name)
|
|
150
149
|
remove_method(meth)
|
|
151
150
|
end
|
|
152
151
|
else
|
|
@@ -17,6 +17,9 @@ class Roda
|
|
|
17
17
|
# will return a 200 response for <tt>GET /</tt> and a 405
|
|
18
18
|
# response for <tt>POST /</tt>.
|
|
19
19
|
#
|
|
20
|
+
# This plugin changes the +r.root+ method to return a 405 status
|
|
21
|
+
# for non-GET requests to +/+.
|
|
22
|
+
#
|
|
20
23
|
# This plugin also changes the +r.is+ method so that if you use
|
|
21
24
|
# a verb method inside +r.is+, it returns a 405 status if none
|
|
22
25
|
# of the verb methods match. So this code:
|
|
@@ -100,6 +103,15 @@ class Roda
|
|
|
100
103
|
end
|
|
101
104
|
end
|
|
102
105
|
|
|
106
|
+
# Treat +r.root+ similar to <tt>r.get ''</tt>, using a 405
|
|
107
|
+
# response for non-GET requests.
|
|
108
|
+
def root
|
|
109
|
+
super
|
|
110
|
+
if @remaining_path == "/" && !is_get?
|
|
111
|
+
always{method_not_allowed("GET")}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
103
115
|
# Setup methods for all verbs. If inside an is block and not given
|
|
104
116
|
# arguments, record the verb used. If given an argument, add an is
|
|
105
117
|
# check with the arguments.
|
|
@@ -129,6 +141,7 @@ class Roda
|
|
|
129
141
|
res = response
|
|
130
142
|
res.status = 405
|
|
131
143
|
res['Allow'] = verbs
|
|
144
|
+
nil
|
|
132
145
|
end
|
|
133
146
|
end
|
|
134
147
|
end
|
data/lib/roda/plugins/public.rb
CHANGED
data/lib/roda/plugins/render.rb
CHANGED
|
@@ -495,8 +495,10 @@ class Roda
|
|
|
495
495
|
|
|
496
496
|
# Render the given template. If there is a default layout
|
|
497
497
|
# for the class, take the result of the template rendering
|
|
498
|
-
# and render it inside the layout.
|
|
499
|
-
|
|
498
|
+
# and render it inside the layout. Blocks passed to view
|
|
499
|
+
# are passed to render when rendering the template.
|
|
500
|
+
# See Render for details.
|
|
501
|
+
def view(template, opts = (content = _optimized_view_content(template) unless defined?(yield); OPTS), &block)
|
|
500
502
|
if content
|
|
501
503
|
# First, check if the optimized layout method has already been created,
|
|
502
504
|
# and use it if so. This way avoids the extra conditional and local variable
|
|
@@ -516,7 +518,7 @@ class Roda
|
|
|
516
518
|
end
|
|
517
519
|
else
|
|
518
520
|
opts = parse_template_opts(template, opts)
|
|
519
|
-
content = opts[:content] || render_template(opts)
|
|
521
|
+
content = opts[:content] || render_template(opts, &block)
|
|
520
522
|
end
|
|
521
523
|
|
|
522
524
|
if layout_opts = view_layout_opts(opts)
|
|
@@ -34,7 +34,7 @@ class Roda
|
|
|
34
34
|
# path internally, or a redirect is issued when configured with
|
|
35
35
|
# <tt>use_redirects: true</tt>.
|
|
36
36
|
def run(*)
|
|
37
|
-
if remaining_path.empty?
|
|
37
|
+
if @remaining_path.empty?
|
|
38
38
|
if scope.opts[:run_append_slash_redirect]
|
|
39
39
|
redirect("#{path}/")
|
|
40
40
|
else
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The run_require_slash plugin makes +r.run+ a no-op if the remaining
|
|
7
|
+
# path is not empty and does not start with +/+. The Rack SPEC requires that
|
|
8
|
+
# +PATH_INFO+ start with a slash if not empty, so this plugin prevents
|
|
9
|
+
# dispatching to the application with an environment that would violate the
|
|
10
|
+
# Rack SPEC.
|
|
11
|
+
#
|
|
12
|
+
# You are unlikely to want to use this plugin unless are consuming partial
|
|
13
|
+
# segments of the request path, or using the match_affix plugin to change
|
|
14
|
+
# how routing is done:
|
|
15
|
+
#
|
|
16
|
+
# plugin :match_affix, "", /(\/|\z)/
|
|
17
|
+
# route do |r|
|
|
18
|
+
# r.on "/a" do
|
|
19
|
+
# r.on "b" do
|
|
20
|
+
# r.run App
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # with run_require_slash:
|
|
26
|
+
# # GET /a/b/e => App not dispatched to
|
|
27
|
+
# # GET /a/b => App gets "" as PATH_INFO
|
|
28
|
+
#
|
|
29
|
+
# # with run_require_slash:
|
|
30
|
+
# # GET /a/b/e => App gets "e" as PATH_INFO, violating rack SPEC
|
|
31
|
+
# # GET /a/b => App gets "" as PATH_INFO
|
|
32
|
+
module RunRequireSlash
|
|
33
|
+
module RequestMethods
|
|
34
|
+
# Calls the given rack app only if the remaining patch is empty or
|
|
35
|
+
# starts with a slash.
|
|
36
|
+
def run(*)
|
|
37
|
+
if @remaining_path.empty? || @remaining_path.start_with?('/')
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
register_plugin(:run_require_slash, RunRequireSlash)
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/roda/plugins/static.rb
CHANGED
|
@@ -17,10 +17,13 @@ class Roda
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def default_redirect_status
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
return super if is_get?
|
|
21
|
+
|
|
22
|
+
case http_version
|
|
23
|
+
when 'HTTP/1.0', 'HTTP/0.9', nil
|
|
23
24
|
super
|
|
25
|
+
else
|
|
26
|
+
303
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
end
|
|
@@ -15,24 +15,53 @@ class Roda
|
|
|
15
15
|
# status_handler(403) do
|
|
16
16
|
# "You are forbidden from seeing that!"
|
|
17
17
|
# end
|
|
18
|
+
#
|
|
18
19
|
# status_handler(404) do
|
|
19
20
|
# "Where did it go?"
|
|
20
21
|
# end
|
|
21
22
|
#
|
|
23
|
+
# status_handler(405, keep_headers: ['Accept']) do
|
|
24
|
+
# "Use a different method!"
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
22
27
|
# Before a block is called, any existing headers on the response will be
|
|
23
|
-
# cleared
|
|
24
|
-
#
|
|
28
|
+
# cleared, unless the +:keep_headers+ option is used. If the +:keep_headers+
|
|
29
|
+
# option is used, the value should be an array, and only the headers listed
|
|
30
|
+
# in the array will be kept.
|
|
25
31
|
module StatusHandler
|
|
32
|
+
CLEAR_HEADERS = :clear.to_proc
|
|
33
|
+
private_constant :CLEAR_HEADERS
|
|
34
|
+
|
|
26
35
|
def self.configure(app)
|
|
27
36
|
app.opts[:status_handler] ||= {}
|
|
28
37
|
end
|
|
29
38
|
|
|
30
39
|
module ClassMethods
|
|
31
40
|
# Install the given block as a status handler for the given HTTP response code.
|
|
32
|
-
def status_handler(code, &block)
|
|
41
|
+
def status_handler(code, opts=OPTS, &block)
|
|
33
42
|
# For backwards compatibility, pass request argument if block accepts argument
|
|
34
43
|
arity = block.arity == 0 ? 0 : 1
|
|
35
|
-
|
|
44
|
+
handle_headers = case keep_headers = opts[:keep_headers]
|
|
45
|
+
when nil, false
|
|
46
|
+
CLEAR_HEADERS
|
|
47
|
+
when Array
|
|
48
|
+
# :nocov:
|
|
49
|
+
if Rack.release >= '2.3'
|
|
50
|
+
keep_headers = keep_headers.map(&:downcase)
|
|
51
|
+
end
|
|
52
|
+
# :nocov:
|
|
53
|
+
lambda{|headers| headers.delete_if{|k,_| !keep_headers.include?(k)}}
|
|
54
|
+
else
|
|
55
|
+
raise RodaError, "Invalid :keep_headers option"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
meth = define_roda_method(:"_roda_status_handler__#{code}", arity, &block)
|
|
59
|
+
self.opts[:status_handler][code] = define_roda_method(:"_roda_status_handler_#{code}", 1) do |result|
|
|
60
|
+
res = @_response
|
|
61
|
+
res.status = result[0]
|
|
62
|
+
handle_headers.call(res.headers)
|
|
63
|
+
result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
|
|
64
|
+
end
|
|
36
65
|
end
|
|
37
66
|
|
|
38
67
|
# Freeze the hash of status handlers so that there can be no thread safety issues at runtime.
|
|
@@ -47,11 +76,8 @@ class Roda
|
|
|
47
76
|
|
|
48
77
|
# If routing returns a response we have a handler for, call that handler.
|
|
49
78
|
def _roda_after_20__status_handler(result)
|
|
50
|
-
if result && (meth
|
|
51
|
-
|
|
52
|
-
res.headers.clear
|
|
53
|
-
res.status = result[0]
|
|
54
|
-
result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
|
|
79
|
+
if result && (meth = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
|
|
80
|
+
send(meth, result)
|
|
55
81
|
end
|
|
56
82
|
end
|
|
57
83
|
end
|