rack 2.1.0 → 3.1.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.

Potentially problematic release.


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

Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +377 -16
  3. data/CONTRIBUTING.md +144 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +365 -0
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +2 -2
  9. data/lib/rack/auth/basic.rb +4 -7
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +34 -12
  12. data/lib/rack/builder.rb +162 -59
  13. data/lib/rack/cascade.rb +24 -10
  14. data/lib/rack/common_logger.rb +43 -28
  15. data/lib/rack/conditional_get.rb +30 -25
  16. data/lib/rack/constants.rb +66 -0
  17. data/lib/rack/content_length.rb +10 -16
  18. data/lib/rack/content_type.rb +9 -7
  19. data/lib/rack/deflater.rb +78 -50
  20. data/lib/rack/directory.rb +86 -63
  21. data/lib/rack/etag.rb +14 -22
  22. data/lib/rack/events.rb +18 -17
  23. data/lib/rack/files.rb +99 -61
  24. data/lib/rack/head.rb +8 -9
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +868 -642
  27. data/lib/rack/lock.rb +2 -6
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/media_type.rb +9 -4
  30. data/lib/rack/method_override.rb +6 -2
  31. data/lib/rack/mime.rb +14 -5
  32. data/lib/rack/mock.rb +1 -253
  33. data/lib/rack/mock_request.rb +171 -0
  34. data/lib/rack/mock_response.rb +124 -0
  35. data/lib/rack/multipart/generator.rb +15 -8
  36. data/lib/rack/multipart/parser.rb +238 -107
  37. data/lib/rack/multipart/uploaded_file.rb +17 -7
  38. data/lib/rack/multipart.rb +54 -42
  39. data/lib/rack/null_logger.rb +9 -0
  40. data/lib/rack/query_parser.rb +87 -105
  41. data/lib/rack/recursive.rb +3 -1
  42. data/lib/rack/reloader.rb +0 -4
  43. data/lib/rack/request.rb +366 -135
  44. data/lib/rack/response.rb +186 -68
  45. data/lib/rack/rewindable_input.rb +24 -6
  46. data/lib/rack/runtime.rb +8 -7
  47. data/lib/rack/sendfile.rb +29 -27
  48. data/lib/rack/show_exceptions.rb +27 -12
  49. data/lib/rack/show_status.rb +21 -13
  50. data/lib/rack/static.rb +19 -12
  51. data/lib/rack/tempfile_reaper.rb +14 -5
  52. data/lib/rack/urlmap.rb +5 -6
  53. data/lib/rack/utils.rb +274 -260
  54. data/lib/rack/version.rb +21 -0
  55. data/lib/rack.rb +18 -103
  56. metadata +25 -52
  57. data/README.rdoc +0 -262
  58. data/Rakefile +0 -123
  59. data/SPEC +0 -263
  60. data/bin/rackup +0 -5
  61. data/contrib/rack.png +0 -0
  62. data/contrib/rack.svg +0 -150
  63. data/contrib/rack_logo.svg +0 -164
  64. data/contrib/rdoc.css +0 -412
  65. data/example/lobster.ru +0 -6
  66. data/example/protectedlobster.rb +0 -16
  67. data/example/protectedlobster.ru +0 -10
  68. data/lib/rack/auth/digest/md5.rb +0 -131
  69. data/lib/rack/auth/digest/nonce.rb +0 -54
  70. data/lib/rack/auth/digest/params.rb +0 -54
  71. data/lib/rack/auth/digest/request.rb +0 -43
  72. data/lib/rack/chunked.rb +0 -92
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/file.rb +0 -8
  75. data/lib/rack/handler/cgi.rb +0 -62
  76. data/lib/rack/handler/fastcgi.rb +0 -102
  77. data/lib/rack/handler/lsws.rb +0 -63
  78. data/lib/rack/handler/scgi.rb +0 -73
  79. data/lib/rack/handler/thin.rb +0 -38
  80. data/lib/rack/handler/webrick.rb +0 -122
  81. data/lib/rack/handler.rb +0 -104
  82. data/lib/rack/lobster.rb +0 -72
  83. data/lib/rack/server.rb +0 -467
  84. data/lib/rack/session/abstract/id.rb +0 -528
  85. data/lib/rack/session/cookie.rb +0 -205
  86. data/lib/rack/session/memcache.rb +0 -10
  87. data/lib/rack/session/pool.rb +0 -85
  88. data/rack.gemspec +0 -44
data/lib/rack/response.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/request'
4
- require 'rack/utils'
5
- require 'rack/body_proxy'
6
- require 'rack/media_type'
7
3
  require 'time'
8
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+ require_relative 'media_type'
8
+ require_relative 'headers'
9
+
9
10
  module Rack
10
11
  # Rack::Response provides a convenient interface to create a Rack
11
12
  # response.
@@ -19,34 +20,66 @@ module Rack
19
20
  # +write+ are synchronous with the Rack response.
20
21
  #
21
22
  # Your application's +call+ should end returning Response#finish.
22
-
23
23
  class Response
24
- attr_accessor :length, :status, :body
25
- attr_reader :header
26
- alias headers header
24
+ def self.[](status, headers, body)
25
+ self.new(body, status, headers)
26
+ end
27
27
 
28
- CHUNKED = 'chunked'
29
28
  STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
30
29
 
31
- def initialize(body = nil, status = 200, header = {})
30
+ attr_accessor :length, :status, :body
31
+ attr_reader :headers
32
+
33
+ # Initialize the response object with the specified +body+, +status+
34
+ # and +headers+.
35
+ #
36
+ # If the +body+ is +nil+, construct an empty response object with internal
37
+ # buffering.
38
+ #
39
+ # If the +body+ responds to +to_str+, assume it's a string-like object and
40
+ # construct a buffered response object containing using that string as the
41
+ # initial contents of the buffer.
42
+ #
43
+ # Otherwise it is expected +body+ conforms to the normal requirements of a
44
+ # Rack response body, typically implementing one of +each+ (enumerable
45
+ # body) or +call+ (streaming body).
46
+ #
47
+ # The +status+ defaults to +200+ which is the "OK" HTTP status code. You
48
+ # can provide any other valid status code.
49
+ #
50
+ # The +headers+ must be a +Hash+ of key-value header pairs which conform to
51
+ # the Rack specification for response headers. The key must be a +String+
52
+ # instance and the value can be either a +String+ or +Array+ instance.
53
+ def initialize(body = nil, status = 200, headers = {})
32
54
  @status = status.to_i
33
- @header = Utils::HeaderHash.new(header)
55
+
56
+ unless headers.is_a?(Hash)
57
+ raise ArgumentError, "Headers must be a Hash!"
58
+ end
59
+
60
+ @headers = Headers.new
61
+ # Convert headers input to a plain hash with lowercase keys.
62
+ headers.each do |k, v|
63
+ @headers[k] = v
64
+ end
34
65
 
35
66
  @writer = self.method(:append)
36
67
 
37
68
  @block = nil
38
- @length = 0
39
69
 
40
70
  # Keep track of whether we have expanded the user supplied body.
41
71
  if body.nil?
42
72
  @body = []
43
73
  @buffered = true
74
+ @length = 0
44
75
  elsif body.respond_to?(:to_str)
45
76
  @body = [body]
46
77
  @buffered = true
78
+ @length = body.to_str.bytesize
47
79
  else
48
80
  @body = body
49
- @buffered = false
81
+ @buffered = nil # undetermined as of yet.
82
+ @length = 0
50
83
  end
51
84
 
52
85
  yield self if block_given?
@@ -56,23 +89,31 @@ module Rack
56
89
  self.status = status
57
90
  self.location = target
58
91
  end
59
-
60
- def chunked?
61
- CHUNKED == get_header(TRANSFER_ENCODING)
92
+
93
+ def no_entity_body?
94
+ # The response body is an enumerable body and it is not allowed to have an entity body.
95
+ @body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
62
96
  end
63
97
 
98
+ # Generate a response array consistent with the requirements of the SPEC.
99
+ # @return [Array] a 3-tuple suitable of `[status, headers, body]`
100
+ # which is suitable to be returned from the middleware `#call(env)` method.
64
101
  def finish(&block)
65
- if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
102
+ if no_entity_body?
66
103
  delete_header CONTENT_TYPE
67
104
  delete_header CONTENT_LENGTH
68
105
  close
69
- [status.to_i, header, []]
106
+ return [@status, @headers, []]
70
107
  else
108
+ if @length && @length > 0
109
+ set_header CONTENT_LENGTH, @length.to_s
110
+ end
111
+
71
112
  if block_given?
72
113
  @block = block
73
- [status.to_i, header, self]
114
+ return [@status, @headers, self]
74
115
  else
75
- [status.to_i, header, @body]
116
+ return [@status, @headers, @body]
76
117
  end
77
118
  end
78
119
  end
@@ -89,7 +130,7 @@ module Rack
89
130
  end
90
131
  end
91
132
 
92
- # Append to body and update Content-Length.
133
+ # Append to body and update content-length.
93
134
  #
94
135
  # NOTE: Do not mix #write and direct #body access!
95
136
  #
@@ -107,10 +148,22 @@ module Rack
107
148
  @block == nil && @body.empty?
108
149
  end
109
150
 
110
- def has_header?(key); headers.key? key; end
111
- def get_header(key); headers[key]; end
112
- def set_header(key, v); headers[key] = v; end
113
- def delete_header(key); headers.delete key; end
151
+ def has_header?(key)
152
+ raise ArgumentError unless key.is_a?(String)
153
+ @headers.key?(key)
154
+ end
155
+ def get_header(key)
156
+ raise ArgumentError unless key.is_a?(String)
157
+ @headers[key]
158
+ end
159
+ def set_header(key, value)
160
+ raise ArgumentError unless key.is_a?(String)
161
+ @headers[key] = value
162
+ end
163
+ def delete_header(key)
164
+ raise ArgumentError unless key.is_a?(String)
165
+ @headers.delete key
166
+ end
114
167
 
115
168
  alias :[] :get_header
116
169
  alias :[]= :set_header
@@ -134,38 +187,56 @@ module Rack
134
187
  def forbidden?; status == 403; end
135
188
  def not_found?; status == 404; end
136
189
  def method_not_allowed?; status == 405; end
190
+ def not_acceptable?; status == 406; end
191
+ def request_timeout?; status == 408; end
137
192
  def precondition_failed?; status == 412; end
138
193
  def unprocessable?; status == 422; end
139
194
 
140
195
  def redirect?; [301, 302, 303, 307, 308].include? status; end
141
196
 
142
197
  def include?(header)
143
- has_header? header
198
+ has_header?(header)
144
199
  end
145
200
 
146
201
  # Add a header that may have multiple values.
147
202
  #
148
203
  # Example:
149
- # response.add_header 'Vary', 'Accept-Encoding'
150
- # response.add_header 'Vary', 'Cookie'
204
+ # response.add_header 'vary', 'accept-encoding'
205
+ # response.add_header 'vary', 'cookie'
151
206
  #
152
- # assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
207
+ # assert_equal 'accept-encoding,cookie', response.get_header('vary')
153
208
  #
154
209
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
155
- def add_header key, v
156
- if v.nil?
157
- get_header key
158
- elsif has_header? key
159
- set_header key, "#{get_header key},#{v}"
210
+ def add_header(key, value)
211
+ raise ArgumentError unless key.is_a?(String)
212
+
213
+ if value.nil?
214
+ return get_header(key)
215
+ end
216
+
217
+ value = value.to_s
218
+
219
+ if header = get_header(key)
220
+ if header.is_a?(Array)
221
+ header << value
222
+ else
223
+ set_header(key, [header, value])
224
+ end
160
225
  else
161
- set_header key, v
226
+ set_header(key, value)
162
227
  end
163
228
  end
164
229
 
230
+ # Get the content type of the response.
165
231
  def content_type
166
232
  get_header CONTENT_TYPE
167
233
  end
168
234
 
235
+ # Set the content type of the response.
236
+ def content_type=(content_type)
237
+ set_header CONTENT_TYPE, content_type
238
+ end
239
+
169
240
  def media_type
170
241
  MediaType.type(content_type)
171
242
  end
@@ -180,74 +251,110 @@ module Rack
180
251
  end
181
252
 
182
253
  def location
183
- get_header "Location"
254
+ get_header "location"
184
255
  end
185
256
 
186
257
  def location=(location)
187
- set_header "Location", location
258
+ set_header "location", location
188
259
  end
189
260
 
190
261
  def set_cookie(key, value)
191
- cookie_header = get_header SET_COOKIE
192
- set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
262
+ add_header SET_COOKIE, Utils.set_cookie_header(key, value)
193
263
  end
194
264
 
195
265
  def delete_cookie(key, value = {})
196
- set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
266
+ set_header(SET_COOKIE,
267
+ Utils.delete_set_cookie_header!(
268
+ get_header(SET_COOKIE), key, value
269
+ )
270
+ )
197
271
  end
198
272
 
199
273
  def set_cookie_header
200
274
  get_header SET_COOKIE
201
275
  end
202
276
 
203
- def set_cookie_header= v
204
- set_header SET_COOKIE, v
277
+ def set_cookie_header=(value)
278
+ set_header SET_COOKIE, value
205
279
  end
206
280
 
207
281
  def cache_control
208
282
  get_header CACHE_CONTROL
209
283
  end
210
284
 
211
- def cache_control= v
212
- set_header CACHE_CONTROL, v
285
+ def cache_control=(value)
286
+ set_header CACHE_CONTROL, value
287
+ end
288
+
289
+ # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
290
+ def do_not_cache!
291
+ set_header CACHE_CONTROL, "no-cache, must-revalidate"
292
+ set_header EXPIRES, Time.now.httpdate
293
+ end
294
+
295
+ # Specify that the content should be cached.
296
+ # @param duration [Integer] The number of seconds until the cache expires.
297
+ # @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
298
+ def cache!(duration = 3600, directive: "public")
299
+ unless headers[CACHE_CONTROL] =~ /no-cache/
300
+ set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
301
+ set_header EXPIRES, (Time.now + duration).httpdate
302
+ end
213
303
  end
214
304
 
215
305
  def etag
216
306
  get_header ETAG
217
307
  end
218
308
 
219
- def etag= v
220
- set_header ETAG, v
309
+ def etag=(value)
310
+ set_header ETAG, value
221
311
  end
222
312
 
223
313
  protected
224
314
 
315
+ # Convert the body of this response into an internally buffered Array if possible.
316
+ #
317
+ # `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
318
+ # * `nil` - The body has not been buffered yet.
319
+ # * `true` - The body is buffered as an Array instance.
320
+ # * `false` - The body is not buffered and cannot be buffered.
321
+ #
322
+ # @return [Boolean] whether the body is buffered as an Array instance.
225
323
  def buffered_body!
226
- return if @buffered
227
-
228
- if @body.is_a?(Array)
229
- # The user supplied body was an array:
230
- @body = @body.compact
231
- else
232
- # Turn the user supplied body into a buffered array:
233
- body = @body
234
- @body = Array.new
235
-
236
- body.each do |part|
237
- @writer.call(part.to_s)
324
+ if @buffered.nil?
325
+ if @body.is_a?(Array)
326
+ # The user supplied body was an array:
327
+ @body = @body.compact
328
+ @length = @body.sum{|part| part.bytesize}
329
+ @buffered = true
330
+ elsif @body.respond_to?(:each)
331
+ # Turn the user supplied body into a buffered array:
332
+ body = @body
333
+ @body = Array.new
334
+ @length = 0
335
+
336
+ body.each do |part|
337
+ @writer.call(part.to_s)
338
+ end
339
+
340
+ body.close if body.respond_to?(:close)
341
+
342
+ # We have converted the body into an Array:
343
+ @buffered = true
344
+ else
345
+ # We don't know how to buffer the user-supplied body:
346
+ @buffered = false
238
347
  end
239
348
  end
240
349
 
241
- @buffered = true
350
+ return @buffered
242
351
  end
243
352
 
244
353
  def append(chunk)
354
+ chunk = chunk.dup unless chunk.frozen?
245
355
  @body << chunk
246
356
 
247
- unless chunked?
248
- @length += chunk.bytesize
249
- set_header(CONTENT_LENGTH, @length.to_s)
250
- end
357
+ @length += chunk.bytesize
251
358
 
252
359
  return chunk
253
360
  end
@@ -261,15 +368,26 @@ module Rack
261
368
  attr_reader :headers
262
369
  attr_accessor :status
263
370
 
264
- def initialize status, headers
371
+ def initialize(status, headers)
265
372
  @status = status
266
373
  @headers = headers
267
374
  end
268
375
 
269
- def has_header?(key); headers.key? key; end
270
- def get_header(key); headers[key]; end
271
- def set_header(key, v); headers[key] = v; end
272
- def delete_header(key); headers.delete key; end
376
+ def has_header?(key)
377
+ headers.key?(key)
378
+ end
379
+
380
+ def get_header(key)
381
+ headers[key]
382
+ end
383
+
384
+ def set_header(key, value)
385
+ headers[key] = value
386
+ end
387
+
388
+ def delete_header(key)
389
+ headers.delete(key)
390
+ end
273
391
  end
274
392
  end
275
393
  end
@@ -2,19 +2,30 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'tempfile'
5
- require 'rack/utils'
5
+
6
+ require_relative 'constants'
6
7
 
7
8
  module Rack
8
9
  # Class which can make any IO object rewindable, including non-rewindable ones. It does
9
10
  # this by buffering the data into a tempfile, which is rewindable.
10
11
  #
11
- # rack.input is required to be rewindable, so if your input stream IO is non-rewindable
12
- # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
13
- # to easily make it rewindable.
14
- #
15
12
  # Don't forget to call #close when you're done. This frees up temporary resources that
16
13
  # RewindableInput uses, though it does *not* close the original IO object.
17
14
  class RewindableInput
15
+ # Makes rack.input rewindable, for compatibility with applications and middleware
16
+ # designed for earlier versions of Rack (where rack.input was required to be
17
+ # rewindable).
18
+ class Middleware
19
+ def initialize(app)
20
+ @app = app
21
+ end
22
+
23
+ def call(env)
24
+ env[RACK_INPUT] = RewindableInput.new(env[RACK_INPUT])
25
+ @app.call(env)
26
+ end
27
+ end
28
+
18
29
  def initialize(io)
19
30
  @io = io
20
31
  @rewindable_io = nil
@@ -41,6 +52,11 @@ module Rack
41
52
  @rewindable_io.rewind
42
53
  end
43
54
 
55
+ def size
56
+ make_rewindable unless @rewindable_io
57
+ @rewindable_io.size
58
+ end
59
+
44
60
  # Closes this RewindableInput object without closing the originally
45
61
  # wrapped IO object. Cleans up any temporary resources that this RewindableInput
46
62
  # has created.
@@ -67,12 +83,14 @@ module Rack
67
83
  # access it because we have the file handle open.
68
84
  @rewindable_io = Tempfile.new('RackRewindableInput')
69
85
  @rewindable_io.chmod(0000)
70
- @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
86
+ @rewindable_io.set_encoding(Encoding::BINARY)
71
87
  @rewindable_io.binmode
88
+ # :nocov:
72
89
  if filesystem_has_posix_semantics?
73
90
  raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
74
91
  @unlinked = true
75
92
  end
93
+ # :nocov:
76
94
 
77
95
  buffer = "".dup
78
96
  while @io.read(1024 * 4, buffer)
data/lib/rack/runtime.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/utils'
3
+ require_relative 'utils'
4
4
 
5
5
  module Rack
6
- # Sets an "X-Runtime" response header, indicating the response
6
+ # Sets an "x-runtime" response header, indicating the response
7
7
  # time of the request, in seconds
8
8
  #
9
9
  # You can put it right before the application to see the processing
@@ -11,24 +11,25 @@ module Rack
11
11
  # too.
12
12
  class Runtime
13
13
  FORMAT_STRING = "%0.6f" # :nodoc:
14
- HEADER_NAME = "X-Runtime" # :nodoc:
14
+ HEADER_NAME = "x-runtime" # :nodoc:
15
15
 
16
16
  def initialize(app, name = nil)
17
17
  @app = app
18
18
  @header_name = HEADER_NAME
19
- @header_name += "-#{name}" if name
19
+ @header_name += "-#{name.to_s.downcase}" if name
20
20
  end
21
21
 
22
22
  def call(env)
23
23
  start_time = Utils.clock_time
24
- status, headers, body = @app.call(env)
24
+ _, headers, _ = response = @app.call(env)
25
+
25
26
  request_time = Utils.clock_time - start_time
26
27
 
27
- unless headers.has_key?(@header_name)
28
+ unless headers.key?(@header_name)
28
29
  headers[@header_name] = FORMAT_STRING % request_time
29
30
  end
30
31
 
31
- [status, headers, body]
32
+ response
32
33
  end
33
34
  end
34
35
  end
data/lib/rack/sendfile.rb CHANGED
@@ -1,35 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/files'
4
- require 'rack/body_proxy'
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'body_proxy'
5
6
 
6
7
  module Rack
7
8
 
8
9
  # = Sendfile
9
10
  #
10
11
  # The Sendfile middleware intercepts responses whose body is being
11
- # served from a file and replaces it with a server specific X-Sendfile
12
+ # served from a file and replaces it with a server specific x-sendfile
12
13
  # header. The web server is then responsible for writing the file contents
13
14
  # to the client. This can dramatically reduce the amount of work required
14
15
  # by the Ruby backend and takes advantage of the web server's optimized file
15
16
  # delivery code.
16
17
  #
17
18
  # In order to take advantage of this middleware, the response body must
18
- # respond to +to_path+ and the request must include an X-Sendfile-Type
19
+ # respond to +to_path+ and the request must include an x-sendfile-type
19
20
  # header. Rack::Files and other components implement +to_path+ so there's
20
- # rarely anything you need to do in your application. The X-Sendfile-Type
21
+ # rarely anything you need to do in your application. The x-sendfile-type
21
22
  # header is typically set in your web servers configuration. The following
22
23
  # sections attempt to document
23
24
  #
24
25
  # === Nginx
25
26
  #
26
- # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
27
+ # Nginx supports the x-accel-redirect header. This is similar to x-sendfile
27
28
  # but requires parts of the filesystem to be mapped into a private URL
28
29
  # hierarchy.
29
30
  #
30
31
  # The following example shows the Nginx configuration required to create
31
- # a private "/files/" area, enable X-Accel-Redirect, and pass the special
32
- # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
32
+ # a private "/files/" area, enable x-accel-redirect, and pass the special
33
+ # x-sendfile-type and x-accel-mapping headers to the backend:
33
34
  #
34
35
  # location ~ /files/(.*) {
35
36
  # internal;
@@ -43,14 +44,14 @@ module Rack
43
44
  # proxy_set_header X-Real-IP $remote_addr;
44
45
  # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45
46
  #
46
- # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
47
- # proxy_set_header X-Accel-Mapping /var/www/=/files/;
47
+ # proxy_set_header x-sendfile-type x-accel-redirect;
48
+ # proxy_set_header x-accel-mapping /var/www/=/files/;
48
49
  #
49
50
  # proxy_pass http://127.0.0.1:8080/;
50
51
  # }
51
52
  #
52
- # Note that the X-Sendfile-Type header must be set exactly as shown above.
53
- # The X-Accel-Mapping header should specify the location on the file system,
53
+ # Note that the x-sendfile-type header must be set exactly as shown above.
54
+ # The x-accel-mapping header should specify the location on the file system,
54
55
  # followed by an equals sign (=), followed name of the private URL pattern
55
56
  # that it maps to. The middleware performs a simple substitution on the
56
57
  # resulting path.
@@ -59,8 +60,8 @@ module Rack
59
60
  #
60
61
  # === lighttpd
61
62
  #
62
- # Lighttpd has supported some variation of the X-Sendfile header for some
63
- # time, although only recent version support X-Sendfile in a reverse proxy
63
+ # Lighttpd has supported some variation of the x-sendfile header for some
64
+ # time, although only recent version support x-sendfile in a reverse proxy
64
65
  # configuration.
65
66
  #
66
67
  # $HTTP["host"] == "example.com" {
@@ -74,7 +75,7 @@ module Rack
74
75
  #
75
76
  # proxy-core.allow-x-sendfile = "enable"
76
77
  # proxy-core.rewrite-request = (
77
- # "X-Sendfile-Type" => (".*" => "X-Sendfile")
78
+ # "x-sendfile-type" => (".*" => "x-sendfile")
78
79
  # )
79
80
  # }
80
81
  #
@@ -82,21 +83,21 @@ module Rack
82
83
  #
83
84
  # === Apache
84
85
  #
85
- # X-Sendfile is supported under Apache 2.x using a separate module:
86
+ # x-sendfile is supported under Apache 2.x using a separate module:
86
87
  #
87
88
  # https://tn123.org/mod_xsendfile/
88
89
  #
89
90
  # Once the module is compiled and installed, you can enable it using
90
91
  # XSendFile config directive:
91
92
  #
92
- # RequestHeader Set X-Sendfile-Type X-Sendfile
93
+ # RequestHeader Set x-sendfile-type x-sendfile
93
94
  # ProxyPassReverse / http://localhost:8001/
94
95
  # XSendFile on
95
96
  #
96
97
  # === Mapping parameter
97
98
  #
98
99
  # The third parameter allows for an overriding extension of the
99
- # X-Accel-Mapping header. Mappings should be provided in tuples of internal to
100
+ # x-accel-mapping header. Mappings should be provided in tuples of internal to
100
101
  # external. The internal values may contain regular expression syntax, they
101
102
  # will be matched with case indifference.
102
103
 
@@ -110,28 +111,29 @@ module Rack
110
111
  end
111
112
 
112
113
  def call(env)
113
- status, headers, body = @app.call(env)
114
+ _, headers, body = response = @app.call(env)
115
+
114
116
  if body.respond_to?(:to_path)
115
117
  case type = variation(env)
116
- when 'X-Accel-Redirect'
118
+ when /x-accel-redirect/i
117
119
  path = ::File.expand_path(body.to_path)
118
120
  if url = map_accel_path(env, path)
119
121
  headers[CONTENT_LENGTH] = '0'
120
122
  # '?' must be percent-encoded because it is not query string but a part of path
121
- headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
123
+ headers[type.downcase] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
122
124
  obody = body
123
- body = Rack::BodyProxy.new([]) do
125
+ response[2] = Rack::BodyProxy.new([]) do
124
126
  obody.close if obody.respond_to?(:close)
125
127
  end
126
128
  else
127
- env[RACK_ERRORS].puts "X-Accel-Mapping header missing"
129
+ env[RACK_ERRORS].puts "x-accel-mapping header missing"
128
130
  end
129
- when 'X-Sendfile', 'X-Lighttpd-Send-File'
131
+ when /x-sendfile|x-lighttpd-send-file/i
130
132
  path = ::File.expand_path(body.to_path)
131
133
  headers[CONTENT_LENGTH] = '0'
132
- headers[type] = path
134
+ headers[type.downcase] = path
133
135
  obody = body
134
- body = Rack::BodyProxy.new([]) do
136
+ response[2] = Rack::BodyProxy.new([]) do
135
137
  obody.close if obody.respond_to?(:close)
136
138
  end
137
139
  when '', nil
@@ -139,7 +141,7 @@ module Rack
139
141
  env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
140
142
  end
141
143
  end
142
- [status, headers, body]
144
+ response
143
145
  end
144
146
 
145
147
  private