rack 2.2.9 → 3.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +330 -88
- data/CONTRIBUTING.md +63 -55
- data/MIT-LICENSE +1 -1
- data/README.md +328 -0
- data/SPEC.rdoc +204 -131
- 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 +840 -644
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +3 -0
- 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 +161 -0
- data/lib/rack/mock_response.rb +124 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +213 -95
- 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 +260 -123
- data/lib/rack/response.rb +151 -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 +232 -233
- 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 -204
- 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
|
|
@@ -51,15 +72,16 @@ module Rack
|
|
51
72
|
if body.nil?
|
52
73
|
@body = []
|
53
74
|
@buffered = true
|
54
|
-
|
75
|
+
# Body is unspecified - it may be a buffered response, or it may be a HEAD response.
|
76
|
+
@length = nil
|
55
77
|
elsif body.respond_to?(:to_str)
|
56
78
|
@body = [body]
|
57
79
|
@buffered = true
|
58
80
|
@length = body.to_str.bytesize
|
59
81
|
else
|
60
82
|
@body = body
|
61
|
-
@buffered =
|
62
|
-
@length =
|
83
|
+
@buffered = nil # undetermined as of yet.
|
84
|
+
@length = nil
|
63
85
|
end
|
64
86
|
|
65
87
|
yield self if block_given?
|
@@ -74,20 +96,30 @@ module Rack
|
|
74
96
|
CHUNKED == get_header(TRANSFER_ENCODING)
|
75
97
|
end
|
76
98
|
|
99
|
+
def no_entity_body?
|
100
|
+
# The response body is an enumerable body and it is not allowed to have an entity body.
|
101
|
+
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
102
|
+
end
|
103
|
+
|
77
104
|
# Generate a response array consistent with the requirements of the SPEC.
|
78
105
|
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
79
106
|
# which is suitable to be returned from the middleware `#call(env)` method.
|
80
107
|
def finish(&block)
|
81
|
-
if
|
108
|
+
if no_entity_body?
|
82
109
|
delete_header CONTENT_TYPE
|
83
110
|
delete_header CONTENT_LENGTH
|
84
111
|
close
|
85
112
|
return [@status, @headers, []]
|
86
113
|
else
|
87
114
|
if block_given?
|
115
|
+
# We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
|
88
116
|
@block = block
|
89
117
|
return [@status, @headers, self]
|
90
118
|
else
|
119
|
+
# If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
|
120
|
+
if @length && !chunked?
|
121
|
+
@headers[CONTENT_LENGTH] = @length.to_s
|
122
|
+
end
|
91
123
|
return [@status, @headers, @body]
|
92
124
|
end
|
93
125
|
end
|
@@ -105,7 +137,9 @@ module Rack
|
|
105
137
|
end
|
106
138
|
end
|
107
139
|
|
108
|
-
# Append to
|
140
|
+
# Append a chunk to the response body.
|
141
|
+
#
|
142
|
+
# Converts the response into a buffered response if it wasn't already.
|
109
143
|
#
|
110
144
|
# NOTE: Do not mix #write and direct #body access!
|
111
145
|
#
|
@@ -123,10 +157,22 @@ module Rack
|
|
123
157
|
@block == nil && @body.empty?
|
124
158
|
end
|
125
159
|
|
126
|
-
def has_header?(key)
|
127
|
-
|
128
|
-
|
129
|
-
|
160
|
+
def has_header?(key)
|
161
|
+
raise ArgumentError unless key.is_a?(String)
|
162
|
+
@headers.key?(key)
|
163
|
+
end
|
164
|
+
def get_header(key)
|
165
|
+
raise ArgumentError unless key.is_a?(String)
|
166
|
+
@headers[key]
|
167
|
+
end
|
168
|
+
def set_header(key, value)
|
169
|
+
raise ArgumentError unless key.is_a?(String)
|
170
|
+
@headers[key] = value
|
171
|
+
end
|
172
|
+
def delete_header(key)
|
173
|
+
raise ArgumentError unless key.is_a?(String)
|
174
|
+
@headers.delete key
|
175
|
+
end
|
130
176
|
|
131
177
|
alias :[] :get_header
|
132
178
|
alias :[]= :set_header
|
@@ -150,31 +196,43 @@ module Rack
|
|
150
196
|
def forbidden?; status == 403; end
|
151
197
|
def not_found?; status == 404; end
|
152
198
|
def method_not_allowed?; status == 405; end
|
199
|
+
def not_acceptable?; status == 406; end
|
200
|
+
def request_timeout?; status == 408; end
|
153
201
|
def precondition_failed?; status == 412; end
|
154
202
|
def unprocessable?; status == 422; end
|
155
203
|
|
156
204
|
def redirect?; [301, 302, 303, 307, 308].include? status; end
|
157
205
|
|
158
206
|
def include?(header)
|
159
|
-
has_header?
|
207
|
+
has_header?(header)
|
160
208
|
end
|
161
209
|
|
162
210
|
# Add a header that may have multiple values.
|
163
211
|
#
|
164
212
|
# Example:
|
165
|
-
# response.add_header '
|
166
|
-
# response.add_header '
|
213
|
+
# response.add_header 'vary', 'accept-encoding'
|
214
|
+
# response.add_header 'vary', 'cookie'
|
167
215
|
#
|
168
|
-
# assert_equal '
|
216
|
+
# assert_equal 'accept-encoding,cookie', response.get_header('vary')
|
169
217
|
#
|
170
218
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
171
|
-
def add_header(key,
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
219
|
+
def add_header(key, value)
|
220
|
+
raise ArgumentError unless key.is_a?(String)
|
221
|
+
|
222
|
+
if value.nil?
|
223
|
+
return get_header(key)
|
224
|
+
end
|
225
|
+
|
226
|
+
value = value.to_s
|
227
|
+
|
228
|
+
if header = get_header(key)
|
229
|
+
if header.is_a?(Array)
|
230
|
+
header << value
|
231
|
+
else
|
232
|
+
set_header(key, [header, value])
|
233
|
+
end
|
176
234
|
else
|
177
|
-
set_header
|
235
|
+
set_header(key, value)
|
178
236
|
end
|
179
237
|
end
|
180
238
|
|
@@ -202,36 +260,39 @@ module Rack
|
|
202
260
|
end
|
203
261
|
|
204
262
|
def location
|
205
|
-
get_header "
|
263
|
+
get_header "location"
|
206
264
|
end
|
207
265
|
|
208
266
|
def location=(location)
|
209
|
-
set_header "
|
267
|
+
set_header "location", location
|
210
268
|
end
|
211
269
|
|
212
270
|
def set_cookie(key, value)
|
213
|
-
|
214
|
-
set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
|
271
|
+
add_header SET_COOKIE, Utils.set_cookie_header(key, value)
|
215
272
|
end
|
216
273
|
|
217
274
|
def delete_cookie(key, value = {})
|
218
|
-
set_header
|
275
|
+
set_header(SET_COOKIE,
|
276
|
+
Utils.delete_set_cookie_header!(
|
277
|
+
get_header(SET_COOKIE), key, value
|
278
|
+
)
|
279
|
+
)
|
219
280
|
end
|
220
281
|
|
221
282
|
def set_cookie_header
|
222
283
|
get_header SET_COOKIE
|
223
284
|
end
|
224
285
|
|
225
|
-
def set_cookie_header=(
|
226
|
-
set_header SET_COOKIE,
|
286
|
+
def set_cookie_header=(value)
|
287
|
+
set_header SET_COOKIE, value
|
227
288
|
end
|
228
289
|
|
229
290
|
def cache_control
|
230
291
|
get_header CACHE_CONTROL
|
231
292
|
end
|
232
293
|
|
233
|
-
def cache_control=(
|
234
|
-
set_header CACHE_CONTROL,
|
294
|
+
def cache_control=(value)
|
295
|
+
set_header CACHE_CONTROL, value
|
235
296
|
end
|
236
297
|
|
237
298
|
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
@@ -254,42 +315,55 @@ module Rack
|
|
254
315
|
get_header ETAG
|
255
316
|
end
|
256
317
|
|
257
|
-
def etag=(
|
258
|
-
set_header ETAG,
|
318
|
+
def etag=(value)
|
319
|
+
set_header ETAG, value
|
259
320
|
end
|
260
321
|
|
261
322
|
protected
|
262
323
|
|
324
|
+
# Convert the body of this response into an internally buffered Array if possible.
|
325
|
+
#
|
326
|
+
# `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
|
327
|
+
# * `nil` - The body has not been buffered yet.
|
328
|
+
# * `true` - The body is buffered as an Array instance.
|
329
|
+
# * `false` - The body is not buffered and cannot be buffered.
|
330
|
+
#
|
331
|
+
# @return [Boolean] whether the body is buffered as an Array instance.
|
263
332
|
def buffered_body!
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
333
|
+
if @buffered.nil?
|
334
|
+
if @body.is_a?(Array)
|
335
|
+
# The user supplied body was an array:
|
336
|
+
@body = @body.compact
|
337
|
+
@length = @body.sum{|part| part.bytesize}
|
338
|
+
@buffered = true
|
339
|
+
elsif @body.respond_to?(:each)
|
340
|
+
# Turn the user supplied body into a buffered array:
|
341
|
+
body = @body
|
342
|
+
@body = Array.new
|
343
|
+
@buffered = true
|
344
|
+
|
345
|
+
body.each do |part|
|
346
|
+
@writer.call(part.to_s)
|
347
|
+
end
|
348
|
+
|
349
|
+
body.close if body.respond_to?(:close)
|
350
|
+
else
|
351
|
+
# We don't know how to buffer the user-supplied body:
|
352
|
+
@buffered = false
|
279
353
|
end
|
280
|
-
|
281
|
-
body.close if body.respond_to?(:close)
|
282
354
|
end
|
283
355
|
|
284
|
-
@buffered
|
356
|
+
return @buffered
|
285
357
|
end
|
286
358
|
|
287
359
|
def append(chunk)
|
360
|
+
chunk = chunk.dup unless chunk.frozen?
|
288
361
|
@body << chunk
|
289
362
|
|
290
|
-
|
363
|
+
if @length
|
291
364
|
@length += chunk.bytesize
|
292
|
-
|
365
|
+
elsif @buffered
|
366
|
+
@length = chunk.bytesize
|
293
367
|
end
|
294
368
|
|
295
369
|
return chunk
|
@@ -309,10 +383,21 @@ module Rack
|
|
309
383
|
@headers = headers
|
310
384
|
end
|
311
385
|
|
312
|
-
def has_header?(key)
|
313
|
-
|
314
|
-
|
315
|
-
|
386
|
+
def has_header?(key)
|
387
|
+
headers.key?(key)
|
388
|
+
end
|
389
|
+
|
390
|
+
def get_header(key)
|
391
|
+
headers[key]
|
392
|
+
end
|
393
|
+
|
394
|
+
def set_header(key, value)
|
395
|
+
headers[key] = value
|
396
|
+
end
|
397
|
+
|
398
|
+
def delete_header(key)
|
399
|
+
headers.delete(key)
|
400
|
+
end
|
316
401
|
end
|
317
402
|
end
|
318
403
|
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>
|