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