roda 3.55.0 → 3.58.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/doc/conventions.rdoc +14 -11
- data/doc/release_notes/3.56.0.txt +33 -0
- data/doc/release_notes/3.57.0.txt +34 -0
- data/doc/release_notes/3.58.0.txt +16 -0
- data/lib/roda/plugins/chunked.rb +1 -1
- data/lib/roda/plugins/common_logger.rb +12 -1
- data/lib/roda/plugins/cookies.rb +2 -0
- data/lib/roda/plugins/exception_page.rb +20 -4
- data/lib/roda/plugins/filter_common_logger.rb +46 -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/heartbeat.rb +5 -10
- data/lib/roda/plugins/json_parser.rb +6 -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/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 -5
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The hash_paths plugin allows for O(1) dispatch to multiple routes at any point
|
7
|
+
# in the routing tree. It is useful when you have a large number of specific routes
|
8
|
+
# to dispatch to at any point in the routing tree.
|
9
|
+
#
|
10
|
+
# You configure the hash paths to dispatch to using the +hash_path+ class method,
|
11
|
+
# specifying the remaining path, with a block to handle that path. Then you dispatch
|
12
|
+
# to the configured paths using +r.hash_paths+:
|
13
|
+
#
|
14
|
+
# class App < Roda
|
15
|
+
# plugin :hash_paths
|
16
|
+
#
|
17
|
+
# hash_path("/a") do |r|
|
18
|
+
# # /a path
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# hash_path("/a/b") do |r|
|
22
|
+
# # /a/b path
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# route do |r|
|
26
|
+
# r.hash_paths
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# With the above routing tree, the +r.hash_paths+ call will dispatch requests for the +/a+ and
|
31
|
+
# +/a/b+ request paths.
|
32
|
+
#
|
33
|
+
# The +hash_path+ class method supports namespaces, which allows +r.hash_paths+ to be used at
|
34
|
+
# any level of the routing tree. Here is an example that uses namespaces for sub-branches:
|
35
|
+
#
|
36
|
+
# class App < Roda
|
37
|
+
# plugin :hash_paths
|
38
|
+
#
|
39
|
+
# # Two arguments provided, so first argument is the namespace
|
40
|
+
# hash_path("/a", "/b") do |r|
|
41
|
+
# # /a/b path
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# hash_path("/a", "/c") do |r|
|
45
|
+
# # /a/c path
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# hash_path(:b, "/b") do |r|
|
49
|
+
# # /b/b path
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# hash_path(:b, "/c") do |r|
|
53
|
+
# # /b/c path
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# route do |r|
|
57
|
+
# r.on 'a' do
|
58
|
+
# # No argument given, so uses the already matched path as the namespace,
|
59
|
+
# # which is '/a' in this case.
|
60
|
+
# r.hash_paths
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# r.on 'b' do
|
64
|
+
# # uses :b as the namespace when looking up routes, as that was explicitly specified
|
65
|
+
# r.hash_paths(:b)
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# With the above routing tree, requests for the +/a+ branch will be handled by the first
|
71
|
+
# +r.hash_paths+ call, and requests for the +/b+ branch will be handled by the second
|
72
|
+
# +r.hash_paths+ call. Those will dispatch to the configured hash paths for the +/a+ and
|
73
|
+
# +:b+ namespaces.
|
74
|
+
#
|
75
|
+
# It is best for performance to explicitly specify the namespace when calling
|
76
|
+
# +r.hash_paths+.
|
77
|
+
module HashPaths
|
78
|
+
def self.configure(app)
|
79
|
+
app.opts[:hash_paths] ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
module ClassMethods
|
83
|
+
# Freeze the hash_paths metadata when freezing the app.
|
84
|
+
def freeze
|
85
|
+
opts[:hash_paths].freeze.each_value(&:freeze)
|
86
|
+
super
|
87
|
+
end
|
88
|
+
|
89
|
+
# Duplicate hash_paths metadata in subclass.
|
90
|
+
def inherited(subclass)
|
91
|
+
super
|
92
|
+
|
93
|
+
h = subclass.opts[:hash_paths]
|
94
|
+
opts[:hash_paths].each do |namespace, routes|
|
95
|
+
h[namespace] = routes.dup
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add path handler for the given namespace and path. When the
|
100
|
+
# r.hash_paths method is called, checks the matching namespace
|
101
|
+
# for the full remaining path, and dispatch to that block if
|
102
|
+
# there is one. If called without a block, removes the existing
|
103
|
+
# path handler if it exists.
|
104
|
+
def hash_path(namespace='', path, &block)
|
105
|
+
routes = opts[:hash_paths][namespace] ||= {}
|
106
|
+
if block
|
107
|
+
routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
|
108
|
+
elsif meth = routes.delete(path)
|
109
|
+
remove_method(meth)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
module RequestMethods
|
115
|
+
# Checks the matching hash_path namespace for a branch matching the
|
116
|
+
# remaining path, and dispatch to that block if there is one.
|
117
|
+
def hash_paths(namespace=matched_path)
|
118
|
+
if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
|
119
|
+
@remaining_path = ''
|
120
|
+
always{scope.send(meth, self)}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
register_plugin(:hash_paths, HashPaths)
|
127
|
+
end
|
128
|
+
end
|
@@ -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)
|
@@ -14,13 +14,6 @@ class Roda
|
|
14
14
|
#
|
15
15
|
# plugin :heartbeat, path: '/status'
|
16
16
|
module Heartbeat
|
17
|
-
# :nocov:
|
18
|
-
HEADER_CLASS = (defined?(Rack::Headers) && Rack::Headers.is_a?(Class)) ? Rack::Headers : Hash
|
19
|
-
# :nocov:
|
20
|
-
private_constant :HEADER_CLASS
|
21
|
-
|
22
|
-
HEARTBEAT_RESPONSE = [200, {'Content-Type'=>'text/plain'}.freeze, ['OK'.freeze].freeze].freeze
|
23
|
-
|
24
17
|
# Set the heartbeat path to the given path.
|
25
18
|
def self.configure(app, opts=OPTS)
|
26
19
|
app.opts[:heartbeat_path] = (opts[:path] || app.opts[:heartbeat_path] || "/heartbeat").dup.freeze
|
@@ -32,9 +25,11 @@ class Roda
|
|
32
25
|
# If the request is for a heartbeat path, return the heartbeat response.
|
33
26
|
def _roda_before_20__heartbeat
|
34
27
|
if env['PATH_INFO'] == opts[:heartbeat_path]
|
35
|
-
response =
|
36
|
-
response
|
37
|
-
|
28
|
+
response = @_response
|
29
|
+
response.status = 200
|
30
|
+
response['Content-Type'] = 'text/plain'
|
31
|
+
response.write 'OK'
|
32
|
+
throw :halt, response.finish
|
38
33
|
end
|
39
34
|
end
|
40
35
|
end
|
@@ -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.
|
@@ -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
@@ -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
|