roda 3.55.0 → 3.58.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 +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
|