rack 2.2.8 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -89
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +287 -0
  6. data/Rakefile +40 -7
  7. data/SPEC.rdoc +166 -125
  8. data/contrib/LICENSE.md +7 -0
  9. data/contrib/logo.webp +0 -0
  10. data/lib/rack/auth/abstract/handler.rb +3 -1
  11. data/lib/rack/auth/abstract/request.rb +3 -1
  12. data/lib/rack/auth/digest/md5.rb +1 -131
  13. data/lib/rack/auth/digest/nonce.rb +1 -54
  14. data/lib/rack/auth/digest/params.rb +1 -54
  15. data/lib/rack/auth/digest/request.rb +1 -43
  16. data/lib/rack/auth/digest.rb +256 -0
  17. data/lib/rack/body_proxy.rb +3 -1
  18. data/lib/rack/builder.rb +60 -42
  19. data/lib/rack/cascade.rb +2 -0
  20. data/lib/rack/chunked.rb +16 -13
  21. data/lib/rack/common_logger.rb +23 -18
  22. data/lib/rack/conditional_get.rb +18 -15
  23. data/lib/rack/constants.rb +62 -0
  24. data/lib/rack/content_length.rb +12 -16
  25. data/lib/rack/content_type.rb +8 -5
  26. data/lib/rack/deflater.rb +40 -26
  27. data/lib/rack/directory.rb +9 -3
  28. data/lib/rack/etag.rb +14 -23
  29. data/lib/rack/events.rb +4 -0
  30. data/lib/rack/file.rb +2 -0
  31. data/lib/rack/files.rb +15 -17
  32. data/lib/rack/head.rb +9 -8
  33. data/lib/rack/headers.rb +154 -0
  34. data/lib/rack/lint.rb +740 -649
  35. data/lib/rack/lock.rb +2 -5
  36. data/lib/rack/logger.rb +2 -0
  37. data/lib/rack/media_type.rb +1 -1
  38. data/lib/rack/method_override.rb +5 -1
  39. data/lib/rack/mime.rb +8 -0
  40. data/lib/rack/mock.rb +1 -271
  41. data/lib/rack/mock_request.rb +166 -0
  42. data/lib/rack/mock_response.rb +124 -0
  43. data/lib/rack/multipart/generator.rb +7 -5
  44. data/lib/rack/multipart/parser.rb +123 -79
  45. data/lib/rack/multipart/uploaded_file.rb +4 -0
  46. data/lib/rack/multipart.rb +20 -40
  47. data/lib/rack/null_logger.rb +9 -0
  48. data/lib/rack/query_parser.rb +76 -44
  49. data/lib/rack/recursive.rb +2 -0
  50. data/lib/rack/reloader.rb +0 -2
  51. data/lib/rack/request.rb +189 -91
  52. data/lib/rack/response.rb +131 -61
  53. data/lib/rack/rewindable_input.rb +24 -5
  54. data/lib/rack/runtime.rb +7 -6
  55. data/lib/rack/sendfile.rb +30 -25
  56. data/lib/rack/show_exceptions.rb +15 -2
  57. data/lib/rack/show_status.rb +17 -7
  58. data/lib/rack/static.rb +8 -8
  59. data/lib/rack/tempfile_reaper.rb +15 -4
  60. data/lib/rack/urlmap.rb +4 -2
  61. data/lib/rack/utils.rb +208 -194
  62. data/lib/rack/version.rb +9 -4
  63. data/lib/rack.rb +5 -76
  64. data/rack.gemspec +6 -6
  65. metadata +19 -31
  66. data/README.rdoc +0 -320
  67. data/bin/rackup +0 -5
  68. data/contrib/rack.png +0 -0
  69. data/contrib/rack.svg +0 -150
  70. data/contrib/rack_logo.svg +0 -164
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/handler/cgi.rb +0 -59
  73. data/lib/rack/handler/fastcgi.rb +0 -100
  74. data/lib/rack/handler/lsws.rb +0 -61
  75. data/lib/rack/handler/scgi.rb +0 -71
  76. data/lib/rack/handler/thin.rb +0 -36
  77. data/lib/rack/handler/webrick.rb +0 -129
  78. data/lib/rack/handler.rb +0 -104
  79. data/lib/rack/lobster.rb +0 -70
  80. data/lib/rack/server.rb +0 -466
  81. data/lib/rack/session/abstract/id.rb +0 -523
  82. data/lib/rack/session/cookie.rb +0 -204
  83. data/lib/rack/session/memcache.rb +0 -10
  84. data/lib/rack/session/pool.rb +0 -85
@@ -1,54 +1 @@
1
- # frozen_string_literal: true
2
-
3
- module Rack
4
- module Auth
5
- module Digest
6
- class Params < Hash
7
-
8
- def self.parse(str)
9
- Params[*split_header_value(str).map do |param|
10
- k, v = param.split('=', 2)
11
- [k, dequote(v)]
12
- end.flatten]
13
- end
14
-
15
- def self.dequote(str) # From WEBrick::HTTPUtils
16
- ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
17
- ret.gsub!(/\\(.)/, "\\1")
18
- ret
19
- end
20
-
21
- def self.split_header_value(str)
22
- str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
23
- end
24
-
25
- def initialize
26
- super()
27
-
28
- yield self if block_given?
29
- end
30
-
31
- def [](k)
32
- super k.to_s
33
- end
34
-
35
- def []=(k, v)
36
- super k.to_s, v.to_s
37
- end
38
-
39
- UNQUOTED = ['nc', 'stale']
40
-
41
- def to_s
42
- map do |k, v|
43
- "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
44
- end.join(', ')
45
- end
46
-
47
- def quote(str) # From WEBrick::HTTPUtils
48
- '"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
49
- end
50
-
51
- end
52
- end
53
- end
54
- end
1
+ require_relative '../digest'
@@ -1,43 +1 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../abstract/request'
4
- require_relative 'params'
5
- require_relative 'nonce'
6
-
7
- module Rack
8
- module Auth
9
- module Digest
10
- class Request < Auth::AbstractRequest
11
- def method
12
- @env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD]
13
- end
14
-
15
- def digest?
16
- "digest" == scheme
17
- end
18
-
19
- def correct_uri?
20
- request.fullpath == uri
21
- end
22
-
23
- def nonce
24
- @nonce ||= Nonce.parse(params['nonce'])
25
- end
26
-
27
- def params
28
- @params ||= Params.parse(parts.last)
29
- end
30
-
31
- def respond_to?(sym, *)
32
- super or params.has_key? sym.to_s
33
- end
34
-
35
- def method_missing(sym, *args)
36
- return super unless params.has_key?(key = sym.to_s)
37
- return params[key] if args.size == 0
38
- raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
39
- end
40
- end
41
- end
42
- end
43
- end
1
+ require_relative '../digest'
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'abstract/handler'
4
+ require_relative 'abstract/request'
5
+ require 'digest/md5'
6
+ require 'base64'
7
+
8
+ module Rack
9
+ warn "Rack::Auth::Digest is deprecated and will be removed in Rack 3.1", uplevel: 1
10
+
11
+ module Auth
12
+ module Digest
13
+ # Rack::Auth::Digest::Nonce is the default nonce generator for the
14
+ # Rack::Auth::Digest::MD5 authentication handler.
15
+ #
16
+ # +private_key+ needs to set to a constant string.
17
+ #
18
+ # +time_limit+ can be optionally set to an integer (number of seconds),
19
+ # to limit the validity of the generated nonces.
20
+
21
+ class Nonce
22
+
23
+ class << self
24
+ attr_accessor :private_key, :time_limit
25
+ end
26
+
27
+ def self.parse(string)
28
+ new(*Base64.decode64(string).split(' ', 2))
29
+ end
30
+
31
+ def initialize(timestamp = Time.now, given_digest = nil)
32
+ @timestamp, @given_digest = timestamp.to_i, given_digest
33
+ end
34
+
35
+ def to_s
36
+ Base64.encode64("#{@timestamp} #{digest}").strip
37
+ end
38
+
39
+ def digest
40
+ ::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}")
41
+ end
42
+
43
+ def valid?
44
+ digest == @given_digest
45
+ end
46
+
47
+ def stale?
48
+ !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
49
+ end
50
+
51
+ def fresh?
52
+ !stale?
53
+ end
54
+
55
+ end
56
+
57
+ class Params < Hash
58
+
59
+ def self.parse(str)
60
+ Params[*split_header_value(str).map do |param|
61
+ k, v = param.split('=', 2)
62
+ [k, dequote(v)]
63
+ end.flatten]
64
+ end
65
+
66
+ def self.dequote(str) # From WEBrick::HTTPUtils
67
+ ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
68
+ ret.gsub!(/\\(.)/, "\\1")
69
+ ret
70
+ end
71
+
72
+ def self.split_header_value(str)
73
+ str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
74
+ end
75
+
76
+ def initialize
77
+ super()
78
+
79
+ yield self if block_given?
80
+ end
81
+
82
+ def [](k)
83
+ super k.to_s
84
+ end
85
+
86
+ def []=(k, v)
87
+ super k.to_s, v.to_s
88
+ end
89
+
90
+ UNQUOTED = ['nc', 'stale']
91
+
92
+ def to_s
93
+ map do |k, v|
94
+ "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
95
+ end.join(', ')
96
+ end
97
+
98
+ def quote(str) # From WEBrick::HTTPUtils
99
+ '"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
100
+ end
101
+
102
+ end
103
+
104
+ class Request < Auth::AbstractRequest
105
+ def method
106
+ @env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD]
107
+ end
108
+
109
+ def digest?
110
+ "digest" == scheme
111
+ end
112
+
113
+ def correct_uri?
114
+ request.fullpath == uri
115
+ end
116
+
117
+ def nonce
118
+ @nonce ||= Nonce.parse(params['nonce'])
119
+ end
120
+
121
+ def params
122
+ @params ||= Params.parse(parts.last)
123
+ end
124
+
125
+ def respond_to?(sym, *)
126
+ super or params.has_key? sym.to_s
127
+ end
128
+
129
+ def method_missing(sym, *args)
130
+ return super unless params.has_key?(key = sym.to_s)
131
+ return params[key] if args.size == 0
132
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
133
+ end
134
+ end
135
+
136
+ # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
137
+ # HTTP Digest Authentication, as per RFC 2617.
138
+ #
139
+ # Initialize with the [Rack] application that you want protecting,
140
+ # and a block that looks up a plaintext password for a given username.
141
+ #
142
+ # +opaque+ needs to be set to a constant base64/hexadecimal string.
143
+ #
144
+ class MD5 < AbstractHandler
145
+
146
+ attr_accessor :opaque
147
+
148
+ attr_writer :passwords_hashed
149
+
150
+ def initialize(app, realm = nil, opaque = nil, &authenticator)
151
+ @passwords_hashed = nil
152
+ if opaque.nil? and realm.respond_to? :values_at
153
+ realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
154
+ end
155
+ super(app, realm, &authenticator)
156
+ @opaque = opaque
157
+ end
158
+
159
+ def passwords_hashed?
160
+ !!@passwords_hashed
161
+ end
162
+
163
+ def call(env)
164
+ auth = Request.new(env)
165
+
166
+ unless auth.provided?
167
+ return unauthorized
168
+ end
169
+
170
+ if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
171
+ return bad_request
172
+ end
173
+
174
+ if valid?(auth)
175
+ if auth.nonce.stale?
176
+ return unauthorized(challenge(stale: true))
177
+ else
178
+ env['REMOTE_USER'] = auth.username
179
+
180
+ return @app.call(env)
181
+ end
182
+ end
183
+
184
+ unauthorized
185
+ end
186
+
187
+
188
+ private
189
+
190
+ QOP = 'auth'
191
+
192
+ def params(hash = {})
193
+ Params.new do |params|
194
+ params['realm'] = realm
195
+ params['nonce'] = Nonce.new.to_s
196
+ params['opaque'] = H(opaque)
197
+ params['qop'] = QOP
198
+
199
+ hash.each { |k, v| params[k] = v }
200
+ end
201
+ end
202
+
203
+ def challenge(hash = {})
204
+ "Digest #{params(hash)}"
205
+ end
206
+
207
+ def valid?(auth)
208
+ valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
209
+ end
210
+
211
+ def valid_qop?(auth)
212
+ QOP == auth.qop
213
+ end
214
+
215
+ def valid_opaque?(auth)
216
+ H(opaque) == auth.opaque
217
+ end
218
+
219
+ def valid_nonce?(auth)
220
+ auth.nonce.valid?
221
+ end
222
+
223
+ def valid_digest?(auth)
224
+ pw = @authenticator.call(auth.username)
225
+ pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response)
226
+ end
227
+
228
+ def md5(data)
229
+ ::Digest::MD5.hexdigest(data)
230
+ end
231
+
232
+ alias :H :md5
233
+
234
+ def KD(secret, data)
235
+ H "#{secret}:#{data}"
236
+ end
237
+
238
+ def A1(auth, password)
239
+ "#{auth.username}:#{auth.realm}:#{password}"
240
+ end
241
+
242
+ def A2(auth)
243
+ "#{auth.method}:#{auth.uri}"
244
+ end
245
+
246
+ def digest(auth, password)
247
+ password_hash = passwords_hashed? ? password : H(A1(auth, password))
248
+
249
+ KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
250
+ end
251
+
252
+ end
253
+ end
254
+ end
255
+ end
256
+
@@ -24,7 +24,7 @@ module Rack
24
24
  return if @closed
25
25
  @closed = true
26
26
  begin
27
- @body.close if @body.respond_to? :close
27
+ @body.close if @body.respond_to?(:close)
28
28
  ensure
29
29
  @block.call
30
30
  end
@@ -40,6 +40,8 @@ module Rack
40
40
  def method_missing(method_name, *args, &block)
41
41
  @body.__send__(method_name, *args, &block)
42
42
  end
43
+ # :nocov:
43
44
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
45
+ # :nocov:
44
46
  end
45
47
  end
data/lib/rack/builder.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'urlmap'
4
+
3
5
  module Rack
4
- # Rack::Builder implements a small DSL to iteratively construct Rack
5
- # applications.
6
+ # Rack::Builder provides a domain-specific language (DSL) to construct Rack
7
+ # applications. It is primarily used to parse +config.ru+ files which
8
+ # instantiate several middleware and a final application which are hosted
9
+ # by a Rack-compatible web server.
6
10
  #
7
11
  # Example:
8
12
  #
@@ -29,7 +33,6 @@ module Rack
29
33
  #
30
34
  # +use+ adds middleware to the stack, +run+ dispatches to an application.
31
35
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
32
-
33
36
  class Builder
34
37
 
35
38
  # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
@@ -39,13 +42,11 @@ module Rack
39
42
  #
40
43
  # If the config file ends in +.ru+, it is treated as a
41
44
  # rackup file and the contents will be treated as if
42
- # specified inside a Rack::Builder block, using the given
43
- # options.
45
+ # specified inside a Rack::Builder block.
44
46
  #
45
47
  # If the config file does not end in +.ru+, it is
46
48
  # required and Rack will use the basename of the file
47
49
  # to guess which constant will be the Rack application to run.
48
- # The options given will be ignored in this case.
49
50
  #
50
51
  # Examples:
51
52
  #
@@ -61,23 +62,18 @@ module Rack
61
62
  # # requires ./my_app.rb, which should be in the
62
63
  # # process's current directory. After requiring,
63
64
  # # assumes MyApp constant contains Rack application
64
- def self.parse_file(config, opts = Server::Options.new)
65
- if config.end_with?('.ru')
66
- return self.load_file(config, opts)
65
+ def self.parse_file(path)
66
+ if path.end_with?('.ru')
67
+ return self.load_file(path)
67
68
  else
68
- require config
69
- app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
70
- return app, {}
69
+ require path
70
+ return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
71
71
  end
72
72
  end
73
73
 
74
74
  # Load the given file as a rackup file, treating the
75
75
  # contents as if specified inside a Rack::Builder block.
76
76
  #
77
- # Treats the first comment at the beginning of a line
78
- # that starts with a backslash as options similar to
79
- # options passed on a rackup command line.
80
- #
81
77
  # Ignores content in the file after +__END__+, so that
82
78
  # use of +__END__+ will not result in a syntax error.
83
79
  #
@@ -85,26 +81,20 @@ module Rack
85
81
  #
86
82
  # $ cat config.ru
87
83
  #
88
- # #\ -p 9393
89
- #
90
84
  # use Rack::ContentLength
91
85
  # require './app.rb'
92
86
  # run App
93
- def self.load_file(path, opts = Server::Options.new)
94
- options = {}
95
-
96
- cfgfile = ::File.read(path)
97
- cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
87
+ def self.load_file(path)
88
+ config = ::File.read(path)
89
+ config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8
98
90
 
99
- if cfgfile[/^#\\(.*)/] && opts
100
- warn "Parsing options from the first comment line is deprecated!"
101
- options = opts.parse! $1.split(/\s+/)
91
+ if config[/^#\\(.*)/]
92
+ fail "Parsing options from the first comment line is no longer supported: #{path}"
102
93
  end
103
94
 
104
- cfgfile.sub!(/^__END__\n.*\Z/m, '')
105
- app = new_from_string cfgfile, path
95
+ config.sub!(/^__END__\n.*\Z/m, '')
106
96
 
107
- return app, options
97
+ return new_from_string(config, path)
108
98
  end
109
99
 
110
100
  # Evaluate the given +builder_script+ string in the context of
@@ -114,14 +104,20 @@ module Rack
114
104
  # We cannot use instance_eval(String) as that would resolve constants differently.
115
105
  binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
116
106
  eval builder_script, binding, file
117
- builder.to_app
107
+
108
+ return builder.to_app
118
109
  end
119
110
 
120
111
  # Initialize a new Rack::Builder instance. +default_app+ specifies the
121
112
  # default application if +run+ is not called later. If a block
122
- # is given, it is evaluted in the context of the instance.
113
+ # is given, it is evaluated in the context of the instance.
123
114
  def initialize(default_app = nil, &block)
124
- @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
115
+ @use = []
116
+ @map = nil
117
+ @run = default_app
118
+ @warmup = nil
119
+ @freeze_app = false
120
+
125
121
  instance_eval(&block) if block_given?
126
122
  end
127
123
 
@@ -145,7 +141,7 @@ module Rack
145
141
  # end
146
142
  #
147
143
  # use Middleware
148
- # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
144
+ # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
149
145
  #
150
146
  # All requests through to this application will first be processed by the middleware class.
151
147
  # The +call+ method in this example sets an additional environment key which then can be
@@ -157,24 +153,46 @@ module Rack
157
153
  end
158
154
  @use << proc { |app| middleware.new(app, *args, &block) }
159
155
  end
156
+ # :nocov:
160
157
  ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
158
+ # :nocov:
161
159
 
162
- # Takes an argument that is an object that responds to #call and returns a Rack response.
163
- # The simplest form of this is a lambda object:
160
+ # Takes a block or argument that is an object that responds to #call and
161
+ # returns a Rack response.
162
+ #
163
+ # You can use a block:
164
+ #
165
+ # run do |env|
166
+ # [200, { "content-type" => "text/plain" }, ["Hello World!"]]
167
+ # end
164
168
  #
165
- # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
169
+ # You can also provide a lambda:
166
170
  #
167
- # However this could also be a class:
171
+ # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
172
+ #
173
+ # You can also provide a class instance:
168
174
  #
169
175
  # class Heartbeat
170
- # def self.call(env)
171
- # [200, { "Content-Type" => "text/plain" }, ["OK"]]
176
+ # def call(env)
177
+ # [200, { "content-type" => "text/plain" }, ["OK"]]
172
178
  # end
173
179
  # end
174
180
  #
175
- # run Heartbeat
176
- def run(app)
177
- @run = app
181
+ # run Heartbeat.new
182
+ #
183
+ # It could also be a module:
184
+ #
185
+ # module HelloWorld
186
+ # def call(env)
187
+ # [200, { "content-type" => "text/plain" }, ["Hello World"]]
188
+ # end
189
+ # end
190
+ #
191
+ # run HelloWorld
192
+ def run(app = nil, &block)
193
+ raise ArgumentError, "Both app and block given!" if app && block_given?
194
+
195
+ @run = app || block
178
196
  end
179
197
 
180
198
  # Takes a lambda or block that is used to warm-up the application. This block is called
data/lib/rack/cascade.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+
3
5
  module Rack
4
6
  # Rack::Cascade tries a request on several apps, and returns the
5
7
  # first response that is not 404 or 405 (or in a list of configured
data/lib/rack/chunked.rb CHANGED
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  module Rack
7
+ warn "Rack::Chunked is deprecated and will be removed in Rack 3.1", uplevel: 1
4
8
 
5
9
  # Middleware that applies chunked transfer encoding to response bodies
6
- # when the response does not include a Content-Length header.
10
+ # when the response does not include a content-length header.
7
11
  #
8
- # This supports the Trailer response header to allow the use of trailing
12
+ # This supports the trailer response header to allow the use of trailing
9
13
  # headers in the chunked encoding. However, using this requires you manually
10
14
  # specify a response body that supports a +trailers+ method. Example:
11
15
  #
12
- # [200, { 'Trailer' => 'Expires'}, ["Hello", "World"]]
16
+ # [200, { 'trailer' => 'expires'}, ["Hello", "World"]]
13
17
  # # error raised
14
18
  #
15
19
  # body = ["Hello", "World"]
16
20
  # def body.trailers
17
- # { 'Expires' => Time.now.to_s }
21
+ # { 'expires' => Time.now.to_s }
18
22
  # end
19
- # [200, { 'Trailer' => 'Expires'}, body]
23
+ # [200, { 'trailer' => 'expires'}, body]
20
24
  # # No exception raised
21
25
  class Chunked
22
26
  include Rack::Utils
@@ -92,11 +96,10 @@ module Rack
92
96
  end
93
97
 
94
98
  # If the rack app returns a response that should have a body,
95
- # but does not have Content-Length or Transfer-Encoding headers,
96
- # modify the response to use chunked Transfer-Encoding.
99
+ # but does not have content-length or transfer-encoding headers,
100
+ # modify the response to use chunked transfer-encoding.
97
101
  def call(env)
98
- status, headers, body = @app.call(env)
99
- headers = HeaderHash[headers]
102
+ status, headers, body = response = @app.call(env)
100
103
 
101
104
  if chunkable_version?(env[SERVER_PROTOCOL]) &&
102
105
  !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
@@ -104,14 +107,14 @@ module Rack
104
107
  !headers[TRANSFER_ENCODING]
105
108
 
106
109
  headers[TRANSFER_ENCODING] = 'chunked'
107
- if headers['Trailer']
108
- body = TrailerBody.new(body)
110
+ if headers['trailer']
111
+ response[2] = TrailerBody.new(body)
109
112
  else
110
- body = Body.new(body)
113
+ response[2] = Body.new(body)
111
114
  end
112
115
  end
113
116
 
114
- [status, headers, body]
117
+ response
115
118
  end
116
119
  end
117
120
  end