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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +377 -16
- data/CONTRIBUTING.md +144 -0
- data/MIT-LICENSE +1 -1
- data/README.md +328 -0
- data/SPEC.rdoc +365 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -2
- data/lib/rack/auth/basic.rb +4 -7
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +34 -12
- data/lib/rack/builder.rb +162 -59
- data/lib/rack/cascade.rb +24 -10
- data/lib/rack/common_logger.rb +43 -28
- data/lib/rack/conditional_get.rb +30 -25
- data/lib/rack/constants.rb +66 -0
- data/lib/rack/content_length.rb +10 -16
- data/lib/rack/content_type.rb +9 -7
- data/lib/rack/deflater.rb +78 -50
- data/lib/rack/directory.rb +86 -63
- data/lib/rack/etag.rb +14 -22
- data/lib/rack/events.rb +18 -17
- data/lib/rack/files.rb +99 -61
- data/lib/rack/head.rb +8 -9
- data/lib/rack/headers.rb +238 -0
- data/lib/rack/lint.rb +868 -642
- data/lib/rack/lock.rb +2 -6
- data/lib/rack/logger.rb +3 -0
- data/lib/rack/media_type.rb +9 -4
- data/lib/rack/method_override.rb +6 -2
- data/lib/rack/mime.rb +14 -5
- data/lib/rack/mock.rb +1 -253
- data/lib/rack/mock_request.rb +171 -0
- data/lib/rack/mock_response.rb +124 -0
- data/lib/rack/multipart/generator.rb +15 -8
- data/lib/rack/multipart/parser.rb +238 -107
- data/lib/rack/multipart/uploaded_file.rb +17 -7
- data/lib/rack/multipart.rb +54 -42
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +87 -105
- data/lib/rack/recursive.rb +3 -1
- data/lib/rack/reloader.rb +0 -4
- data/lib/rack/request.rb +366 -135
- data/lib/rack/response.rb +186 -68
- data/lib/rack/rewindable_input.rb +24 -6
- data/lib/rack/runtime.rb +8 -7
- data/lib/rack/sendfile.rb +29 -27
- data/lib/rack/show_exceptions.rb +27 -12
- data/lib/rack/show_status.rb +21 -13
- data/lib/rack/static.rb +19 -12
- data/lib/rack/tempfile_reaper.rb +14 -5
- data/lib/rack/urlmap.rb +5 -6
- data/lib/rack/utils.rb +274 -260
- data/lib/rack/version.rb +21 -0
- data/lib/rack.rb +18 -103
- metadata +25 -52
- data/README.rdoc +0 -262
- data/Rakefile +0 -123
- data/SPEC +0 -263
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/contrib/rdoc.css +0 -412
- data/example/lobster.ru +0 -6
- data/example/protectedlobster.rb +0 -16
- data/example/protectedlobster.ru +0 -10
- data/lib/rack/auth/digest/md5.rb +0 -131
- data/lib/rack/auth/digest/nonce.rb +0 -54
- data/lib/rack/auth/digest/params.rb +0 -54
- data/lib/rack/auth/digest/request.rb +0 -43
- data/lib/rack/chunked.rb +0 -92
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/file.rb +0 -8
- data/lib/rack/handler/cgi.rb +0 -62
- data/lib/rack/handler/fastcgi.rb +0 -102
- data/lib/rack/handler/lsws.rb +0 -63
- data/lib/rack/handler/scgi.rb +0 -73
- data/lib/rack/handler/thin.rb +0 -38
- data/lib/rack/handler/webrick.rb +0 -122
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -72
- data/lib/rack/server.rb +0 -467
- data/lib/rack/session/abstract/id.rb +0 -528
- data/lib/rack/session/cookie.rb +0 -205
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
- 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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
61
|
-
|
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
|
102
|
+
if no_entity_body?
|
66
103
|
delete_header CONTENT_TYPE
|
67
104
|
delete_header CONTENT_LENGTH
|
68
105
|
close
|
69
|
-
[status
|
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
|
114
|
+
return [@status, @headers, self]
|
74
115
|
else
|
75
|
-
[status
|
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
|
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)
|
111
|
-
|
112
|
-
|
113
|
-
|
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?
|
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 '
|
150
|
-
# response.add_header '
|
204
|
+
# response.add_header 'vary', 'accept-encoding'
|
205
|
+
# response.add_header 'vary', 'cookie'
|
151
206
|
#
|
152
|
-
# assert_equal '
|
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
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
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 "
|
254
|
+
get_header "location"
|
184
255
|
end
|
185
256
|
|
186
257
|
def location=(location)
|
187
|
-
set_header "
|
258
|
+
set_header "location", location
|
188
259
|
end
|
189
260
|
|
190
261
|
def set_cookie(key, value)
|
191
|
-
|
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
|
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=
|
204
|
-
set_header SET_COOKIE,
|
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=
|
212
|
-
set_header CACHE_CONTROL,
|
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=
|
220
|
-
set_header ETAG,
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
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
|
-
|
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
|
371
|
+
def initialize(status, headers)
|
265
372
|
@status = status
|
266
373
|
@headers = headers
|
267
374
|
end
|
268
375
|
|
269
|
-
def has_header?(key)
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
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)
|
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
|
-
|
3
|
+
require_relative 'utils'
|
4
4
|
|
5
5
|
module Rack
|
6
|
-
# Sets an "
|
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 = "
|
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
|
-
|
24
|
+
_, headers, _ = response = @app.call(env)
|
25
|
+
|
25
26
|
request_time = Utils.clock_time - start_time
|
26
27
|
|
27
|
-
unless headers.
|
28
|
+
unless headers.key?(@header_name)
|
28
29
|
headers[@header_name] = FORMAT_STRING % request_time
|
29
30
|
end
|
30
31
|
|
31
|
-
|
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
|
-
|
4
|
-
|
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
|
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
|
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
|
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
|
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
|
32
|
-
#
|
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
|
47
|
-
# proxy_set_header
|
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
|
53
|
-
# The
|
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
|
63
|
-
# time, although only recent version support
|
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
|
-
# "
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
|
114
|
+
_, headers, body = response = @app.call(env)
|
115
|
+
|
114
116
|
if body.respond_to?(:to_path)
|
115
117
|
case type = variation(env)
|
116
|
-
when
|
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
|
-
|
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 "
|
129
|
+
env[RACK_ERRORS].puts "x-accel-mapping header missing"
|
128
130
|
end
|
129
|
-
when
|
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
|
-
|
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
|
-
|
144
|
+
response
|
143
145
|
end
|
144
146
|
|
145
147
|
private
|