rack-contrib 2.0.1 → 2.5.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 (41) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +12 -8
  3. data/lib/rack/contrib/access.rb +8 -6
  4. data/lib/rack/contrib/backstage.rb +5 -3
  5. data/lib/rack/contrib/bounce_favicon.rb +3 -1
  6. data/lib/rack/contrib/callbacks.rb +2 -0
  7. data/lib/rack/contrib/common_cookies.rb +19 -11
  8. data/lib/rack/contrib/config.rb +3 -15
  9. data/lib/rack/contrib/cookies.rb +2 -0
  10. data/lib/rack/contrib/csshttprequest.rb +12 -6
  11. data/lib/rack/contrib/deflect.rb +35 -33
  12. data/lib/rack/contrib/enforce_valid_encoding.rb +3 -1
  13. data/lib/rack/contrib/evil.rb +2 -0
  14. data/lib/rack/contrib/expectation_cascade.rb +5 -3
  15. data/lib/rack/contrib/garbagecollector.rb +2 -0
  16. data/lib/rack/contrib/host_meta.rb +3 -1
  17. data/lib/rack/contrib/json_body_parser.rb +94 -0
  18. data/lib/rack/contrib/jsonp.rb +12 -7
  19. data/lib/rack/contrib/lazy_conditional_get.rb +13 -4
  20. data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
  21. data/lib/rack/contrib/locale.rb +75 -30
  22. data/lib/rack/contrib/mailexceptions.rb +2 -0
  23. data/lib/rack/contrib/nested_params.rb +3 -1
  24. data/lib/rack/contrib/not_found.rb +3 -1
  25. data/lib/rack/contrib/post_body_content_type_parser.rb +50 -5
  26. data/lib/rack/contrib/printout.rb +3 -1
  27. data/lib/rack/contrib/proctitle.rb +2 -0
  28. data/lib/rack/contrib/profiler.rb +44 -17
  29. data/lib/rack/contrib/relative_redirect.rb +11 -5
  30. data/lib/rack/contrib/response_cache.rb +21 -9
  31. data/lib/rack/contrib/response_headers.rb +8 -2
  32. data/lib/rack/contrib/route_exceptions.rb +2 -0
  33. data/lib/rack/contrib/runtime.rb +3 -30
  34. data/lib/rack/contrib/signals.rb +6 -0
  35. data/lib/rack/contrib/simple_endpoint.rb +3 -1
  36. data/lib/rack/contrib/static_cache.rb +18 -7
  37. data/lib/rack/contrib/time_zone.rb +2 -0
  38. data/lib/rack/contrib/try_static.rb +2 -0
  39. data/lib/rack/contrib/version.rb +5 -0
  40. data/lib/rack/contrib.rb +3 -1
  41. metadata +13 -212
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
 
3
5
  ##
@@ -43,6 +45,10 @@ module Rack
43
45
  # know for sure that it does not modify the cached content, you can set the
44
46
  # `Rack-Lazy-Conditional-Get` on response to `skip`. This will not update the
45
47
  # global modification date.
48
+ #
49
+ # NOTE: This will not work properly in a multi-threaded environment with
50
+ # default cache object. A provided cache object should ensure thread-safety
51
+ # of the `get`/`set`/`[]`/`[]=` methods.
46
52
 
47
53
  class LazyConditionalGet
48
54
 
@@ -68,11 +74,14 @@ module Rack
68
74
 
69
75
  def call env
70
76
  if reading? env and fresh? env
71
- return [304, {'Last-Modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
77
+ return [304, {'last-modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
72
78
  end
79
+
73
80
  status, headers, body = @app.call env
81
+ headers = Rack.release < "3" ? Utils::HeaderHash.new(headers) : headers
82
+
74
83
  update_cache unless (reading?(env) or skipping?(headers))
75
- headers['Last-Modified'] = cached_value if stampable? headers
84
+ headers['last-modified'] = cached_value if stampable? headers
76
85
  [status, headers, body]
77
86
  end
78
87
 
@@ -87,11 +96,11 @@ module Rack
87
96
  end
88
97
 
89
98
  def skipping? headers
90
- headers['Rack-Lazy-Conditional-Get'] == 'skip'
99
+ headers['rack-lazy-conditional-get'] == 'skip'
91
100
  end
92
101
 
93
102
  def stampable? headers
94
- !headers.has_key?('Last-Modified') and headers['Rack-Lazy-Conditional-Get'] == 'yes'
103
+ !headers.has_key?('last-modified') and headers['rack-lazy-conditional-get'] == 'yes'
95
104
  end
96
105
 
97
106
  def update_cache
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Lighttpd sets the wrong SCRIPT_NAME and PATH_INFO if you mount your
3
5
  # FastCGI app at "/". This middleware fixes this issue.
@@ -1,49 +1,94 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'i18n'
2
4
 
3
5
  module Rack
4
6
  class Locale
7
+ HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
8
+ private_constant :HEADERS_KLASS
9
+
5
10
  def initialize(app)
6
11
  @app = app
7
12
  end
8
13
 
9
14
  def call(env)
10
- old_locale = I18n.locale
11
-
12
- begin
13
- locale = accept_locale(env) || I18n.default_locale
14
- locale = env['rack.locale'] = I18n.locale = locale.to_s
15
- status, headers, body = @app.call(env)
16
- headers['Content-Language'] = locale unless headers['Content-Language']
17
- [status, headers, body]
18
- ensure
19
- I18n.locale = old_locale
15
+ locale_to_restore = I18n.locale
16
+
17
+ locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"])
18
+ locale ||= I18n.default_locale
19
+
20
+ env['rack.locale'] = I18n.locale = locale.to_s
21
+ status, headers, body = @app.call(env)
22
+ headers = HEADERS_KLASS.new.merge(headers)
23
+
24
+ unless headers['Content-Language']
25
+ headers['Content-Language'] = locale.to_s
20
26
  end
27
+
28
+ [status, headers, body]
29
+ ensure
30
+ I18n.locale = locale_to_restore
21
31
  end
22
32
 
23
33
  private
24
34
 
25
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
26
- def accept_locale(env)
27
- accept_langs = env["HTTP_ACCEPT_LANGUAGE"]
28
- return if accept_langs.nil?
29
-
30
- languages_and_qvalues = accept_langs.split(",").map { |l|
31
- l += ';q=1.0' unless l =~ /;q=\d+(?:\.\d+)?$/
32
- l.split(';q=')
33
- }
34
-
35
- language_and_qvalue = languages_and_qvalues.sort_by { |(locale, qvalue)|
36
- qvalue.to_f
37
- }.reverse.detect { |(locale, qvalue)|
38
- if I18n.enforce_available_locales
39
- locale == '*' || I18n.available_locales.include?(locale.to_sym)
40
- else
41
- true
35
+ # Accept-Language header is covered mainly by RFC 7231
36
+ # https://tools.ietf.org/html/rfc7231
37
+ #
38
+ # Related sections:
39
+ #
40
+ # * https://tools.ietf.org/html/rfc7231#section-5.3.1
41
+ # * https://tools.ietf.org/html/rfc7231#section-5.3.5
42
+ # * https://tools.ietf.org/html/rfc4647#section-3.4
43
+ #
44
+ # There is an obsolete RFC 2616 (https://tools.ietf.org/html/rfc2616)
45
+ #
46
+ # Edge cases:
47
+ #
48
+ # * Value can be a comma separated list with optional whitespaces:
49
+ # Accept-Language: da, en-gb;q=0.8, en;q=0.7
50
+ #
51
+ # * Quality value can contain optional whitespaces as well:
52
+ # Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2
53
+ #
54
+ # * Quality prefix 'q=' can be in upper case (Q=)
55
+ #
56
+ # * Ignore case when match locale with I18n available locales
57
+ #
58
+ def user_preferred_locale(header)
59
+ return if header.nil?
60
+
61
+ locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
62
+ locale, quality = language_tag.split(/;q=/i)
63
+ quality = quality ? quality.to_f : 1.0
64
+ [locale, quality]
65
+ end.reject do |(locale, quality)|
66
+ locale == '*' || quality == 0
67
+ end.sort_by do |(_, quality)|
68
+ quality
69
+ end.map(&:first)
70
+
71
+ return if locales.empty?
72
+
73
+ if I18n.enforce_available_locales
74
+ locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } }
75
+ matched_locale = I18n.available_locales.find { |al| match?(al, locale) } if locale
76
+ if !locale && !matched_locale
77
+ matched_locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| variant_match?(al, locale) } }
78
+ matched_locale = matched_locale[0,2] if matched_locale
42
79
  end
43
- }
80
+ matched_locale
81
+ else
82
+ locales.last
83
+ end
84
+ end
85
+
86
+ def match?(s1, s2)
87
+ s1.to_s.casecmp(s2.to_s) == 0
88
+ end
44
89
 
45
- lang = language_and_qvalue && language_and_qvalue.first
46
- lang == '*' ? nil : lang
90
+ def variant_match?(s1, s2)
91
+ s1.to_s.casecmp(s2[0,2].to_s) == 0
47
92
  end
48
93
  end
49
94
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'net/smtp'
2
4
  require 'mail'
3
5
  require 'erb'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Rack
@@ -24,7 +26,7 @@ module Rack
24
26
  post_body = env[POST_BODY]
25
27
  env[FORM_INPUT] = post_body
26
28
  env[FORM_HASH] = Rack::Utils.parse_nested_query(post_body.read)
27
- post_body.rewind if post_body.respond_to?(:rewind)
29
+ post_body.rewind
28
30
  end
29
31
  @app.call(env)
30
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Rack::NotFound is a default endpoint. Optionally initialize with the
3
5
  # path to a custom 404 page, to override the standard response body.
@@ -25,7 +27,7 @@ module Rack
25
27
  end
26
28
 
27
29
  def call(env)
28
- [404, {'Content-Type' => @content_type, 'Content-Length' => @length}, [@content]]
30
+ [404, {'content-type' => @content_type, 'content-length' => @length}, [@content]]
29
31
  end
30
32
  end
31
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'json'
3
5
  rescue LoadError => e
@@ -6,10 +8,51 @@ end
6
8
 
7
9
  module Rack
8
10
 
11
+ # <b>DEPRECATED:</b> <tt>JSONBodyParser</tt> is a drop-in replacement that is faster and more configurable.
12
+ #
9
13
  # A Rack middleware for parsing POST/PUT body data when Content-Type is
10
14
  # not one of the standard supported types, like <tt>application/json</tt>.
11
15
  #
12
- # TODO: Find a better name.
16
+ # === How to use the middleware
17
+ #
18
+ # Example of simple +config.ru+ file:
19
+ #
20
+ # require 'rack'
21
+ # require 'rack/contrib'
22
+ #
23
+ # use ::Rack::PostBodyContentTypeParser
24
+ #
25
+ # app = lambda do |env|
26
+ # request = Rack::Request.new(env)
27
+ # body = "Hello #{request.params['name']}"
28
+ # [200, {'Content-Type' => 'text/plain'}, [body]]
29
+ # end
30
+ #
31
+ # run app
32
+ #
33
+ # Example with passing block argument:
34
+ #
35
+ # use ::Rack::PostBodyContentTypeParser do |body|
36
+ # { 'params' => JSON.parse(body) }
37
+ # end
38
+ #
39
+ # Example with passing proc argument:
40
+ #
41
+ # parser = ->(body) { { 'params' => JSON.parse(body) } }
42
+ # use ::Rack::PostBodyContentTypeParser, &parser
43
+ #
44
+ #
45
+ # === Failed JSON parsing
46
+ #
47
+ # Returns "400 Bad request" response if invalid JSON document was sent:
48
+ #
49
+ # Raw HTTP response:
50
+ #
51
+ # HTTP/1.1 400 Bad Request
52
+ # Content-Type: text/plain
53
+ # Content-Length: 28
54
+ #
55
+ # failed to parse body as JSON
13
56
  #
14
57
  class PostBodyContentTypeParser
15
58
 
@@ -24,14 +67,16 @@ module Rack
24
67
  #
25
68
  APPLICATION_JSON = 'application/json'.freeze
26
69
 
27
- def initialize(app)
70
+ def initialize(app, &block)
71
+ warn "[DEPRECATION] `PostBodyContentTypeParser` is deprecated. Use `JSONBodyParser` as a drop-in replacement."
28
72
  @app = app
73
+ @block = block || Proc.new { |body| JSON.parse(body, :create_additions => false) }
29
74
  end
30
75
 
31
76
  def call(env)
32
77
  if Rack::Request.new(env).media_type == APPLICATION_JSON && (body = env[POST_BODY].read).length != 0
33
- env[POST_BODY].rewind # somebody might try to read this stream
34
- env.update(FORM_HASH => JSON.parse(body, :create_additions => false), FORM_INPUT => env[POST_BODY])
78
+ env[POST_BODY].rewind if env[POST_BODY].respond_to?(:rewind) # somebody might try to read this stream
79
+ env.update(FORM_HASH => @block.call(body), FORM_INPUT => env[POST_BODY])
35
80
  end
36
81
  @app.call(env)
37
82
  rescue JSON::ParserError
@@ -39,7 +84,7 @@ module Rack
39
84
  end
40
85
 
41
86
  def bad_request(body = 'Bad Request')
42
- [ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.size.to_s }, [body] ]
87
+ [ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
43
88
  end
44
89
  end
45
90
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  #prints the environment and request for simple debugging
3
5
  class Printout
@@ -6,7 +8,7 @@ module Rack
6
8
  end
7
9
 
8
10
  def call(env)
9
- # See http://rack.rubyforge.org/doc/SPEC.html for details
11
+ # See https://github.com/rack/rack/blob/main/SPEC.rdoc for details
10
12
  puts "**********\n Environment\n **************"
11
13
  puts env.inspect
12
14
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Middleware to update the process title ($0) with information about the
3
5
  # current request. Based loosely on:
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ruby-prof'
2
4
 
3
5
  module Rack
4
6
  # Set the profile=process_time query parameter to download a
5
7
  # calltree profile of the request.
6
8
  #
7
- # Pass the :printer option to pick a different result format.
9
+ # Pass the :printer option to pick a different result format. Note that
10
+ # some printers (such as CallTreePrinter) have broken the
11
+ # `AbstractPrinter` API, and thus will not work. Bug reports to
12
+ # `ruby-prof`, please, not us.
8
13
  #
9
14
  # You can cause every request to be run multiple times by passing the
10
15
  # `:times` option to the `use Rack::Profiler` call. You can also run a
@@ -27,10 +32,14 @@ module Rack
27
32
  # option defaulting to :call_stack.
28
33
  def initialize(app, options = {})
29
34
  @app = app
35
+ @profile = nil
30
36
  @printer = parse_printer(options[:printer] || DEFAULT_PRINTER)
31
37
  @times = (options[:times] || 1).to_i
38
+ @maximum_runs = options.fetch(:maximum_runs, 10)
32
39
  end
33
40
 
41
+ attr :maximum_runs
42
+
34
43
  def call(env)
35
44
  if mode = profiling?(env)
36
45
  profile(env, mode)
@@ -41,28 +50,46 @@ module Rack
41
50
 
42
51
  private
43
52
  def profiling?(env)
44
- unless ::RubyProf.running?
45
- request = Rack::Request.new(env.clone)
46
- if mode = request.params.delete('profile')
47
- if ::RubyProf.const_defined?(mode.upcase)
48
- mode
49
- else
50
- env['rack.errors'].write "Invalid RubyProf measure_mode: " +
51
- "#{mode}. Use one of #{MODES.to_a.join(', ')}"
52
- false
53
- end
53
+ return if @profile && @profile.running?
54
+
55
+ request = Rack::Request.new(env.clone)
56
+ if mode = request.params.delete('profile')
57
+ if ::RubyProf.const_defined?(mode.upcase)
58
+ mode
59
+ else
60
+ env['rack.errors'].write "Invalid RubyProf measure_mode: " +
61
+ "#{mode}. Use one of #{MODES.to_a.join(', ')}"
62
+ false
54
63
  end
55
64
  end
56
65
  end
57
66
 
67
+ # How many times to run the request within the profiler.
68
+ # If the profiler_runs query parameter is set, use that.
69
+ # Otherwise, use the :times option passed to `#initialize`.
70
+ # If the profiler_runs query parameter is greater than the
71
+ # :maximum option passed to `#initialize`, use the :maximum
72
+ # option.
73
+ def runs(request)
74
+ if profiler_runs = request.params['profiler_runs']
75
+ profiler_runs = profiler_runs.to_i
76
+ if profiler_runs > @maximum_runs
77
+ return @maximum_runs
78
+ else
79
+ return profiler_runs
80
+ end
81
+ else
82
+ return @times
83
+ end
84
+ end
85
+
58
86
  def profile(env, mode)
59
- ::RubyProf.measure_mode = ::RubyProf.const_get(mode.upcase)
87
+ @profile = ::RubyProf::Profile.new(measure_mode: ::RubyProf.const_get(mode.upcase))
60
88
 
61
89
  GC.enable_stats if GC.respond_to?(:enable_stats)
62
90
  request = Rack::Request.new(env.clone)
63
- runs = (request.params['profiler_runs'] || @times).to_i
64
- result = ::RubyProf.profile do
65
- runs.times { @app.call(env) }
91
+ result = @profile.profile do
92
+ runs(request).times { @app.call(env) }
66
93
  end
67
94
  GC.disable_stats if GC.respond_to?(:disable_stats)
68
95
 
@@ -77,10 +104,10 @@ module Rack
77
104
  end
78
105
 
79
106
  def headers(printer, env, mode)
80
- headers = { 'Content-Type' => CONTENT_TYPES[printer.name] }
107
+ headers = { 'content-type' => CONTENT_TYPES[printer.name] }
81
108
  if printer == ::RubyProf::CallTreePrinter
82
109
  filename = ::File.basename(env['PATH_INFO'])
83
- headers['Content-Disposition'] =
110
+ headers['content-disposition'] =
84
111
  %(attachment; filename="#{filename}.#{mode}.tree")
85
112
  end
86
113
  headers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
 
3
5
  # Rack::RelativeRedirect is a simple middleware that converts relative paths in
@@ -30,15 +32,19 @@ class Rack::RelativeRedirect
30
32
  # and use that to make the Location header an absolute url. If the Location
31
33
  # does not start with a slash, make location relative to the path requested.
32
34
  def call(env)
33
- res = @app.call(env)
34
- if [301,302,303, 307,308].include?(res[0]) and loc = res[1]['Location'] and !%r{\Ahttps?://}o.match(loc)
35
- absolute = @absolute_proc.call(env, res)
36
- res[1]['Location'] = if %r{\A/}.match(loc)
35
+ status, headers, body = @app.call(env)
36
+ headers_klass = Rack.release < "3" ? Rack::Utils::HeaderHash : Rack::Headers
37
+ headers = headers_klass.new.merge(headers)
38
+
39
+ if [301,302,303, 307,308].include?(status) and loc = headers['Location'] and !%r{\Ahttps?://}o.match(loc)
40
+ absolute = @absolute_proc.call(env, [status, headers, body])
41
+ headers['Location'] = if %r{\A/}.match(loc)
37
42
  "#{absolute}#{loc}"
38
43
  else
39
44
  "#{absolute}#{File.dirname(Rack::Utils.unescape(env['PATH_INFO']))}/#{loc}"
40
45
  end
41
46
  end
42
- res
47
+
48
+ [status, headers, body]
43
49
  end
44
50
  end
@@ -1,11 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
  require 'rack'
3
5
 
4
6
  # Rack::ResponseCache is a Rack middleware that caches responses for successful
5
7
  # GET requests with no query string to disk or any ruby object that has an
6
- # []= method (so it works with memcached). When caching to disk, it works similar to
7
- # Rails' page caching, allowing you to cache dynamic pages to static files that can
8
- # be served directly by a front end webserver.
8
+ # []= method (so it works with memcached). As with Rails' page caching, this
9
+ # middleware only writes to the cache -- it never reads. The logic of whether a
10
+ # cached response should be served is left either to your web server, via
11
+ # something like the <tt>try_files</tt> directive in nginx, or to your
12
+ # cache-reading middleware of choice, mounted before Rack::ResponseCache in the
13
+ # stack.
9
14
  class Rack::ResponseCache
10
15
  # The default proc used if a block is not provided to .new
11
16
  # It unescapes the PATH_INFO of the environment, and makes sure that it doesn't
@@ -15,7 +20,10 @@ class Rack::ResponseCache
15
20
  # of the path to index.html.
16
21
  DEFAULT_PATH_PROC = proc do |env, res|
17
22
  path = Rack::Utils.unescape(env['PATH_INFO'])
18
- if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(res[1]['Content-Type'])
23
+ headers = res[1]
24
+ content_type = headers['Content-Type']
25
+
26
+ if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(content_type)
19
27
  type = match[1]
20
28
  path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
21
29
  path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
@@ -44,16 +52,20 @@ class Rack::ResponseCache
44
52
  # If the cache is a string, create any necessary middle directories, and cache the file in the appropriate
45
53
  # subdirectory of cache. Otherwise, cache the body of the response as the value with the path as the key.
46
54
  def call(env)
47
- res = @app.call(env)
48
- if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and res[0] == 200 and path = @path_proc.call(env, res)
55
+ status, headers, body = @app.call(env)
56
+ headers_klass = Rack.release < "3" ? Rack::Utils::HeaderHash : Rack::Headers
57
+ headers = headers_klass.new.merge(headers)
58
+
59
+ if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and status == 200 and path = @path_proc.call(env, [status, headers, body])
49
60
  if @cache.is_a?(String)
50
61
  path = File.join(@cache, path) if @cache
51
62
  FileUtils.mkdir_p(File.dirname(path))
52
- File.open(path, 'wb'){|f| res[2].each{|c| f.write(c)}}
63
+ File.open(path, 'wb'){|f| body.each{|c| f.write(c)}}
53
64
  else
54
- @cache[path] = res[2]
65
+ @cache[path] = body
55
66
  end
56
67
  end
57
- res
68
+
69
+ [status, headers, body]
58
70
  end
59
71
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Allows you to tap into the response headers. Yields a Rack::Utils::HeaderHash
3
- # of current response headers to the block. Example:
5
+ # (Rack 2) or a Rack::Headers (Rack 3) of current response headers to the block.
6
+ # Example:
4
7
  #
5
8
  # use Rack::ResponseHeaders do |headers|
6
9
  # headers['X-Foo'] = 'bar'
@@ -8,6 +11,9 @@ module Rack
8
11
  # end
9
12
  #
10
13
  class ResponseHeaders
14
+ HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
15
+ private_constant :HEADERS_KLASS
16
+
11
17
  def initialize(app, &block)
12
18
  @app = app
13
19
  @block = block
@@ -15,7 +21,7 @@ module Rack
15
21
 
16
22
  def call(env)
17
23
  response = @app.call(env)
18
- headers = Utils::HeaderHash.new(response[1])
24
+ headers = HEADERS_KLASS.new.merge(response[1])
19
25
  @block.call(headers)
20
26
  response[1] = headers
21
27
  response
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class RouteExceptions
3
5
  ROUTES = [
@@ -1,31 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
- module Rack
3
- # Sets an "X-Runtime" response header, indicating the response
4
- # time of the request, in seconds
5
- #
6
- # You can put it right before the application to see the processing
7
- # time, or before all the other middlewares to include time for them,
8
- # too.
9
- class Runtime
10
- def initialize(app, name = nil)
11
- @app = app
12
- @header_name = "X-Runtime"
13
- @header_name << "-#{name}" if name
14
- end
15
-
16
- def call(env)
17
- start_time = Time.now
18
- status, headers, body = @app.call(env)
19
- request_time = Time.now - start_time
20
-
21
- if !headers.has_key?(@header_name)
22
- headers[@header_name] = "%0.6f" % request_time
23
- end
24
-
25
- [status, headers, body]
26
- end
27
- end
28
- end
29
-
30
-
31
-
3
+ require 'rack'
4
+ require 'rack/runtime'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Installs signal handlers that are safely processed after a request
3
5
  #
@@ -24,6 +26,10 @@ module Rack
24
26
  @body.each(&block)
25
27
  @callback.call
26
28
  end
29
+
30
+ def close
31
+ @body.close if @body.respond_to?(:close)
32
+ end
27
33
  end
28
34
 
29
35
  def initialize(app, &block)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  # Create simple endpoints with routing rules, similar to Sinatra actions.
3
5
  #
@@ -78,4 +80,4 @@ module Rack
78
80
  @verbs.empty? || @verbs.include?(method)
79
81
  end
80
82
  end
81
- end
83
+ end