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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/doc/conventions.rdoc +14 -11
  4. data/doc/release_notes/3.56.0.txt +33 -0
  5. data/doc/release_notes/3.57.0.txt +34 -0
  6. data/doc/release_notes/3.58.0.txt +16 -0
  7. data/lib/roda/plugins/chunked.rb +1 -1
  8. data/lib/roda/plugins/common_logger.rb +12 -1
  9. data/lib/roda/plugins/cookies.rb +2 -0
  10. data/lib/roda/plugins/exception_page.rb +20 -4
  11. data/lib/roda/plugins/filter_common_logger.rb +46 -0
  12. data/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
  13. data/lib/roda/plugins/hash_branches.rb +145 -0
  14. data/lib/roda/plugins/hash_paths.rb +128 -0
  15. data/lib/roda/plugins/hash_routes.rb +13 -176
  16. data/lib/roda/plugins/heartbeat.rb +5 -10
  17. data/lib/roda/plugins/json_parser.rb +6 -2
  18. data/lib/roda/plugins/multi_public.rb +8 -0
  19. data/lib/roda/plugins/multi_route.rb +1 -1
  20. data/lib/roda/plugins/multi_view.rb +0 -4
  21. data/lib/roda/plugins/named_routes.rb +1 -2
  22. data/lib/roda/plugins/not_allowed.rb +13 -0
  23. data/lib/roda/plugins/public.rb +8 -0
  24. data/lib/roda/plugins/route_csrf.rb +1 -0
  25. data/lib/roda/plugins/run_append_slash.rb +1 -1
  26. data/lib/roda/plugins/run_require_slash.rb +46 -0
  27. data/lib/roda/plugins/sessions.rb +1 -0
  28. data/lib/roda/plugins/sinatra_helpers.rb +10 -0
  29. data/lib/roda/plugins/static.rb +2 -0
  30. data/lib/roda/plugins/static_routing.rb +1 -1
  31. data/lib/roda/plugins/status_303.rb +6 -3
  32. data/lib/roda/plugins/status_handler.rb +35 -9
  33. data/lib/roda/plugins/symbol_status.rb +2 -0
  34. data/lib/roda/plugins/unescape_path.rb +2 -0
  35. data/lib/roda/request.rb +35 -1
  36. data/lib/roda/response.rb +5 -0
  37. data/lib/roda/version.rb +1 -1
  38. 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 combines the O(1) dispatching speed of the static_routing plugin with
7
- # the flexibility of the multi_route plugin. For any point in the routing tree,
8
- # it allows you dispatch to multiple routes where the next segment or the remaining path
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
- # Both +hash_branch+ and +hash_path+ support namespaces, which allows them to be used at
88
- # any level of the routing tree. Here is an example that uses namespaces for sub-branches:
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. Below
140
- # is a translation of the previous example to using the +hash_routes+ DSL:
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 = HEARTBEAT_RESPONSE.dup
36
- response[1] = HEADER_CLASS[response[1]]
37
- throw :halt, response
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 json format
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.
@@ -1,5 +1,13 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ begin
4
+ require 'rack/files'
5
+ rescue LoadError
6
+ # :nocov:
7
+ require 'rack/file'
8
+ # :nocov:
9
+ end
10
+
3
11
  #
4
12
  class Roda
5
13
  module RodaPlugins
@@ -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 hash_routes plugin offers a +r.hash_routes+ method that is similar to
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[name]
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
@@ -2,6 +2,14 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
+ begin
6
+ require 'rack/files'
7
+ rescue LoadError
8
+ # :nocov:
9
+ require 'rack/file'
10
+ # :nocov:
11
+ end
12
+
5
13
  #
6
14
  class Roda
7
15
  module RodaPlugins
@@ -4,6 +4,7 @@ require 'base64'
4
4
  require 'openssl'
5
5
  require 'securerandom'
6
6
  require 'uri'
7
+ require 'rack/utils'
7
8
 
8
9
  class Roda
9
10
  module RodaPlugins
@@ -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
@@ -14,6 +14,7 @@ require 'base64'
14
14
  require 'json'
15
15
  require 'securerandom'
16
16
  require 'zlib'
17
+ require 'rack/utils'
17
18
 
18
19
  class Roda
19
20
  module RodaPlugins
@@ -1,5 +1,15 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/mime'
4
+ begin
5
+ require 'rack/files'
6
+ rescue LoadError
7
+ # :nocov:
8
+ require 'rack/file'
9
+ # :nocov:
10
+ end
11
+
12
+
3
13
  #
4
14
  class Roda
5
15
  module RodaPlugins
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/static'
4
+
3
5
  #
4
6
  class Roda
5
7
  module RodaPlugins
@@ -50,7 +50,7 @@ class Roda
50
50
  # while still handling the request methods differently.
51
51
  module StaticRouting
52
52
  def self.load_dependencies(app)
53
- app.plugin :hash_routes
53
+ app.plugin :hash_paths
54
54
  end
55
55
 
56
56
  module ClassMethods
@@ -17,10 +17,13 @@ class Roda
17
17
  private
18
18
 
19
19
  def default_redirect_status
20
- if env['HTTP_VERSION'] == 'HTTP/1.1' && !is_get?
21
- 303
22
- else
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