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.
- checksums.yaml +5 -5
- data/README.md +12 -8
- data/lib/rack/contrib/access.rb +8 -6
- data/lib/rack/contrib/backstage.rb +5 -3
- data/lib/rack/contrib/bounce_favicon.rb +3 -1
- data/lib/rack/contrib/callbacks.rb +2 -0
- data/lib/rack/contrib/common_cookies.rb +19 -11
- data/lib/rack/contrib/config.rb +3 -15
- data/lib/rack/contrib/cookies.rb +2 -0
- data/lib/rack/contrib/csshttprequest.rb +12 -6
- data/lib/rack/contrib/deflect.rb +35 -33
- data/lib/rack/contrib/enforce_valid_encoding.rb +3 -1
- data/lib/rack/contrib/evil.rb +2 -0
- data/lib/rack/contrib/expectation_cascade.rb +5 -3
- data/lib/rack/contrib/garbagecollector.rb +2 -0
- data/lib/rack/contrib/host_meta.rb +3 -1
- data/lib/rack/contrib/json_body_parser.rb +94 -0
- data/lib/rack/contrib/jsonp.rb +12 -7
- data/lib/rack/contrib/lazy_conditional_get.rb +13 -4
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
- data/lib/rack/contrib/locale.rb +75 -30
- data/lib/rack/contrib/mailexceptions.rb +2 -0
- data/lib/rack/contrib/nested_params.rb +3 -1
- data/lib/rack/contrib/not_found.rb +3 -1
- data/lib/rack/contrib/post_body_content_type_parser.rb +50 -5
- data/lib/rack/contrib/printout.rb +3 -1
- data/lib/rack/contrib/proctitle.rb +2 -0
- data/lib/rack/contrib/profiler.rb +44 -17
- data/lib/rack/contrib/relative_redirect.rb +11 -5
- data/lib/rack/contrib/response_cache.rb +21 -9
- data/lib/rack/contrib/response_headers.rb +8 -2
- data/lib/rack/contrib/route_exceptions.rb +2 -0
- data/lib/rack/contrib/runtime.rb +3 -30
- data/lib/rack/contrib/signals.rb +6 -0
- data/lib/rack/contrib/simple_endpoint.rb +3 -1
- data/lib/rack/contrib/static_cache.rb +18 -7
- data/lib/rack/contrib/time_zone.rb +2 -0
- data/lib/rack/contrib/try_static.rb +2 -0
- data/lib/rack/contrib/version.rb +5 -0
- data/lib/rack/contrib.rb +3 -1
- 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, {'
|
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['
|
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['
|
99
|
+
headers['rack-lazy-conditional-get'] == 'skip'
|
91
100
|
end
|
92
101
|
|
93
102
|
def stampable? headers
|
94
|
-
!headers.has_key?('
|
103
|
+
!headers.has_key?('last-modified') and headers['rack-lazy-conditional-get'] == 'yes'
|
95
104
|
end
|
96
105
|
|
97
106
|
def update_cache
|
data/lib/rack/contrib/locale.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
46
|
-
|
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 '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
|
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, {'
|
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
|
-
#
|
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 =>
|
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, { '
|
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
|
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,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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
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
|
-
|
64
|
-
|
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 = { '
|
107
|
+
headers = { 'content-type' => CONTENT_TYPES[printer.name] }
|
81
108
|
if printer == ::RubyProf::CallTreePrinter
|
82
109
|
filename = ::File.basename(env['PATH_INFO'])
|
83
|
-
headers['
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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).
|
7
|
-
#
|
8
|
-
# be served
|
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
|
-
|
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
|
-
|
48
|
-
|
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|
|
63
|
+
File.open(path, 'wb'){|f| body.each{|c| f.write(c)}}
|
53
64
|
else
|
54
|
-
@cache[path] =
|
65
|
+
@cache[path] = body
|
55
66
|
end
|
56
67
|
end
|
57
|
-
|
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.
|
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 =
|
24
|
+
headers = HEADERS_KLASS.new.merge(response[1])
|
19
25
|
@block.call(headers)
|
20
26
|
response[1] = headers
|
21
27
|
response
|
data/lib/rack/contrib/runtime.rb
CHANGED
@@ -1,31 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
|
3
|
-
|
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'
|
data/lib/rack/contrib/signals.rb
CHANGED
@@ -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)
|