rack-contrib 2.0.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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