roda 3.55.0 → 3.56.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b862c4eb1dcca9fbbf0ccacc130779fdecf610a5a064ef94c17384d073e29dd
4
- data.tar.gz: 1e9a991190a6572283350bf1941fca8b83f8496bc519156d5ec3cc5d613d2407
3
+ metadata.gz: 25ca2a1c88af9f7305cdcdb21e3b5983f879386f07c296d7ea0e8f863fddfeac
4
+ data.tar.gz: 24d9dbfeaa438c859b2b3fcecd2170d55e9d4335b1b57ca06c140b900d4a8283
5
5
  SHA512:
6
- metadata.gz: 55fdc91ab21dbb1cf6884ee6b0384ef15db41f830d704ec6c445435ec76e302cb166f18c6f032390193c6d43d6922c7762d03768fd5c22188c3c7f937dc21d4a
7
- data.tar.gz: d1272f1fc0e2707d2daf590ea71a4970ae2b133f0b4ba669e60296d6900422d3746bff8722d44eb636df5aefab0bd6b51382b88de1265924c4243322646335e7
6
+ metadata.gz: 8c1b58f0a4ac491533eebb08e83c5801d095d94dbf4270d5834fb3511c82fd305244c0c02e23ba51dba1479e81b361faf556ad85698ea77ebd8a130f471b9527
7
+ data.tar.gz: b18e88e98b3c42a83f0e66891e48b03b9b641ecaf5963e5e5fd71d086c92176cefd5642464c790ed5edde30459c4a1d78b62864b1f14cd38f3bea1c9b6cefa30
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ = 3.56.0 (2022-05-13)
2
+
3
+ * Make status_303 plugin use 303 responses for HTTP/2 and higher versions (jeremyevans)
4
+
5
+ * Add RodaRequest#http_version for determining the HTTP version in use (jeremyevans)
6
+
7
+ * Do not set a body for 405 responses when using the verb methods in the not_allowed plugin (jeremyevans) (#267)
8
+
9
+ * Support status_handler method :keep_headers option in status_handler plugin (jeremyevans) (#267)
10
+
11
+ * Make not_allowed plugin have r.root return 405 responses for non-GET requests (jeremyevans) (#266)
12
+
13
+ * In Rack 3, only require the parts of rack used by Roda, instead of requiring rack itself and relying on autoload (jeremyevans)
14
+
15
+ * Add run_require_slash plugin, for skipping application dispatch for remaining paths that would violate Rack SPEC (jeremyevans)
16
+
1
17
  = 3.55.0 (2022-04-12)
2
18
 
3
19
  * Allow passing blocks to the view method in the render plugin (jeremyevans) (#262)
@@ -0,0 +1,33 @@
1
+ = New Features
2
+
3
+ * RodaRequest#http_version has been added for determining the HTTP
4
+ version the request was submitted with. This will be a string
5
+ such as "HTTP/1.0", "HTTP/1.1", "HTTP/2", etc. This will use the
6
+ SERVER_PROTOCOL and HTTP_VERSION entries from the environment to
7
+ determine which HTTP version is in use.
8
+
9
+ * The status_handler method in the status_handler plugin now supports
10
+ a :keep_headers option. The value for this option should be an
11
+ array of header names to keep. All other headers are removed. The
12
+ default behavior without the option is still to remove all headers.
13
+
14
+ * A run_require_slash plugin has been added, which will skip
15
+ dispatching to another rack application if the remaining path is not
16
+ empty and does not start with a slash.
17
+
18
+ = Other Improvements
19
+
20
+ * The status_303 plugin will use 303 as the default redirect status
21
+ for non-GET requests for HTTP/2 and higher HTTP versions. Previously,
22
+ it only used 303 for HTTP/1.1.
23
+
24
+ * The not_allowed plugin now overrides the r.root method to return
25
+ 405 responses to non-GET requests to the root.
26
+
27
+ * The not_allowed plugin no longer sets the body when returning 405
28
+ responses using methods such as r.get and r.post. Previously, the
29
+ body was unintentionally set to the same value as the Allow header.
30
+
31
+ * When using the Rack master branch (what will become Rack 3), Roda
32
+ only requires the parts of rack that it uses, instead of requiring
33
+ rack and relying on autoload to load the parts of rack in use.
@@ -226,7 +226,7 @@ class Roda
226
226
  # an overview. If a block is given, it is passed to #delay.
227
227
  def chunked(template, opts=OPTS, &block)
228
228
  unless defined?(@_chunked)
229
- @_chunked = !self.opts[:force_chunked_encoding] || env['HTTP_VERSION'] == "HTTP/1.1"
229
+ @_chunked = !self.opts[:force_chunked_encoding] || @_request.http_version == "HTTP/1.1"
230
230
  end
231
231
 
232
232
  if block
@@ -53,7 +53,7 @@ class Roda
53
53
 
54
54
  env = @_request.env
55
55
 
56
- opts[:common_logger_meth].call("#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{env["HTTP_VERSION"]}\" #{result[0]} #{((length = result[1]['Content-Length']) && (length unless length == '0')) || '-'} #{elapsed_time}\n")
56
+ opts[:common_logger_meth].call("#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{@_request.http_version}\" #{result[0]} #{((length = result[1]['Content-Length']) && (length unless length == '0')) || '-'} #{elapsed_time}\n")
57
57
  end
58
58
 
59
59
  # Create timer instance used for timing
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/utils'
4
+
3
5
  #
4
6
  class Roda
5
7
  module RodaPlugins
@@ -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
@@ -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
@@ -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
@@ -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. So if you want to be sure the headers are set even in your block,
24
- # you need to reset them in the block.
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
- opts[:status_handler][code] = [define_roda_method(:"_roda_status_handler_#{code}", arity, &block), arity]
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, arity = opts[:status_handler][result[0]]; meth) && (v = result[2]).is_a?(Array) && v.empty?
51
- res = @_response
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
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/utils'
4
+
3
5
  class Roda
4
6
  module RodaPlugins
5
7
  # The symbol_status plugin patches the +status=+ response method to
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'rack/utils'
4
+
3
5
  #
4
6
  class Roda
5
7
  module RodaPlugins
data/lib/roda/request.rb CHANGED
@@ -1,6 +1,19 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require "rack"
3
+ # :nocov:
4
+ begin
5
+ require "rack/version"
6
+ rescue LoadError
7
+ require "rack"
8
+ else
9
+ if Rack.release >= '2.3'
10
+ require "rack/request"
11
+ else
12
+ require "rack"
13
+ end
14
+ end
15
+ # :nocov:
16
+
4
17
  require_relative "cache"
5
18
 
6
19
  class Roda
@@ -116,6 +129,27 @@ class Roda
116
129
  "#<#{self.class.inspect} #{@env["REQUEST_METHOD"]} #{path}>"
117
130
  end
118
131
 
132
+ # :nocov:
133
+ if Rack.release >= '2.3'
134
+ def http_version
135
+ # Prefer SERVER_PROTOCOL as it is required in Rack 3.
136
+ # Still fall back to HTTP_VERSION if SERVER_PROTOCOL
137
+ # is not set, in case the server in use is not Rack 3
138
+ # compliant.
139
+ @env['SERVER_PROTOCOL'] || @env['HTTP_VERSION']
140
+ end
141
+ else
142
+ # :nocov:
143
+ # What HTTP version the request was submitted with.
144
+ def http_version
145
+ # Prefer HTTP_VERSION as it is backwards compatible
146
+ # with previous Roda versions. Fallback to
147
+ # SERVER_PROTOCOL for servers that do not set
148
+ # HTTP_VERSION.
149
+ @env['HTTP_VERSION'] || @env['SERVER_PROTOCOL']
150
+ end
151
+ end
152
+
119
153
  # Does a terminal match on the current path, matching only if the arguments
120
154
  # have fully matched the path. If it matches, the match block is
121
155
  # executed, and when the match block returns, the rack response is
data/lib/roda/response.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ begin
4
+ require 'rack/headers'
5
+ rescue LoadError
6
+ end
7
+
3
8
  class Roda
4
9
  # Base class used for Roda responses. The instance methods for this
5
10
  # class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 55
7
+ RodaMinorVersion = 56
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.55.0
4
+ version: 3.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-12 00:00:00.000000000 Z
11
+ date: 2022-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 5.7.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-hooks
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: minitest-global_expectations
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -213,6 +227,7 @@ extra_rdoc_files:
213
227
  - doc/release_notes/3.53.0.txt
214
228
  - doc/release_notes/3.54.0.txt
215
229
  - doc/release_notes/3.55.0.txt
230
+ - doc/release_notes/3.56.0.txt
216
231
  - doc/release_notes/3.6.0.txt
217
232
  - doc/release_notes/3.7.0.txt
218
233
  - doc/release_notes/3.8.0.txt
@@ -275,6 +290,7 @@ files:
275
290
  - doc/release_notes/3.53.0.txt
276
291
  - doc/release_notes/3.54.0.txt
277
292
  - doc/release_notes/3.55.0.txt
293
+ - doc/release_notes/3.56.0.txt
278
294
  - doc/release_notes/3.6.0.txt
279
295
  - doc/release_notes/3.7.0.txt
280
296
  - doc/release_notes/3.8.0.txt
@@ -374,6 +390,7 @@ files:
374
390
  - lib/roda/plugins/route_csrf.rb
375
391
  - lib/roda/plugins/run_append_slash.rb
376
392
  - lib/roda/plugins/run_handler.rb
393
+ - lib/roda/plugins/run_require_slash.rb
377
394
  - lib/roda/plugins/sessions.rb
378
395
  - lib/roda/plugins/shared_vars.rb
379
396
  - lib/roda/plugins/sinatra_helpers.rb
@@ -396,13 +413,13 @@ files:
396
413
  - lib/roda/response.rb
397
414
  - lib/roda/session_middleware.rb
398
415
  - lib/roda/version.rb
399
- homepage: http://roda.jeremyevans.net
416
+ homepage: https://roda.jeremyevans.net
400
417
  licenses:
401
418
  - MIT
402
419
  metadata:
403
420
  bug_tracker_uri: https://github.com/jeremyevans/roda/issues
404
- changelog_uri: http://roda.jeremyevans.net/rdoc/files/CHANGELOG.html
405
- documentation_uri: http://roda.jeremyevans.net/documentation.html
421
+ changelog_uri: https://roda.jeremyevans.net/rdoc/files/CHANGELOG.html
422
+ documentation_uri: https://roda.jeremyevans.net/documentation.html
406
423
  mailing_list_uri: https://github.com/jeremyevans/roda/discussions
407
424
  source_code_uri: https://github.com/jeremyevans/roda
408
425
  post_install_message: