rack 2.1.3 → 2.2.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 +626 -1
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +83 -39
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +35 -6
- data/lib/rack.rb +7 -16
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +3 -3
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +77 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +25 -18
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +45 -35
- data/lib/rack/directory.rb +77 -59
- data/lib/rack/etag.rb +2 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -1
- data/lib/rack/files.rb +96 -56
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +1 -3
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +57 -14
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/mock.rb +22 -4
- data/lib/rack/multipart.rb +1 -1
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +7 -15
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/query_parser.rb +7 -8
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +182 -76
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +0 -3
- data/lib/rack/server.rb +9 -8
- data/lib/rack/session/abstract/id.rb +21 -18
- data/lib/rack/session/cookie.rb +1 -3
- data/lib/rack/session/pool.rb +1 -1
- data/lib/rack/show_exceptions.rb +6 -8
- data/lib/rack/show_status.rb +5 -7
- data/lib/rack/static.rb +13 -6
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +1 -4
- data/lib/rack/utils.rb +63 -55
- data/lib/rack/version.rb +29 -0
- data/rack.gemspec +31 -29
- metadata +14 -15
data/lib/rack/recursive.rb
CHANGED
@@ -19,7 +19,7 @@ module Rack
|
|
19
19
|
@env[PATH_INFO] = @url.path
|
20
20
|
@env[QUERY_STRING] = @url.query if @url.query
|
21
21
|
@env[HTTP_HOST] = @url.host if @url.host
|
22
|
-
@env[
|
22
|
+
@env[HTTP_PORT] = @url.port if @url.port
|
23
23
|
@env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
|
24
24
|
|
25
25
|
super "forwarding to #{url}"
|
data/lib/rack/reloader.rb
CHANGED
@@ -6,8 +6,6 @@
|
|
6
6
|
|
7
7
|
require 'pathname'
|
8
8
|
|
9
|
-
require_relative 'core_ext/regexp'
|
10
|
-
|
11
9
|
module Rack
|
12
10
|
|
13
11
|
# High performant source reloader
|
@@ -24,7 +22,7 @@ module Rack
|
|
24
22
|
# It is performing a check/reload cycle at the start of every request, but
|
25
23
|
# also respects a cool down time, during which nothing will be done.
|
26
24
|
class Reloader
|
27
|
-
using ::Rack::RegexpExtensions
|
25
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
28
26
|
|
29
27
|
def initialize(app, cooldown = 10, backend = Stat)
|
30
28
|
@app = app
|
data/lib/rack/request.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/utils'
|
4
|
-
require 'rack/media_type'
|
5
|
-
|
6
|
-
require_relative 'core_ext/regexp'
|
7
|
-
|
8
3
|
module Rack
|
9
4
|
# Rack::Request provides a convenient interface to a Rack
|
10
5
|
# environment. It is stateless, the environment +env+ passed to the
|
@@ -15,7 +10,7 @@ module Rack
|
|
15
10
|
# req.params["data"]
|
16
11
|
|
17
12
|
class Request
|
18
|
-
using ::Rack::RegexpExtensions
|
13
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
19
14
|
|
20
15
|
class << self
|
21
16
|
attr_accessor :ip_filter
|
@@ -93,7 +88,7 @@ module Rack
|
|
93
88
|
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
94
89
|
#
|
95
90
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
96
|
-
def add_header
|
91
|
+
def add_header(key, v)
|
97
92
|
if v.nil?
|
98
93
|
get_header key
|
99
94
|
elsif has_header? key
|
@@ -134,11 +129,23 @@ module Rack
|
|
134
129
|
# to include the port in a generated URI.
|
135
130
|
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
136
131
|
|
132
|
+
# The address of the client which connected to the proxy.
|
133
|
+
HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
|
134
|
+
|
135
|
+
# The contents of the host/:authority header sent to the proxy.
|
136
|
+
HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
|
137
|
+
|
138
|
+
# The value of the scheme sent to the proxy.
|
137
139
|
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
140
|
+
|
141
|
+
# The protocol used to connect to the proxy.
|
142
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
143
|
+
|
144
|
+
# The port used to connect to the proxy.
|
145
|
+
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
146
|
+
|
147
|
+
# Another way for specifing https scheme was used.
|
148
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
142
149
|
|
143
150
|
def body; get_header(RACK_INPUT) end
|
144
151
|
def script_name; get_header(SCRIPT_NAME).to_s end
|
@@ -212,19 +219,52 @@ module Rack
|
|
212
219
|
end
|
213
220
|
end
|
214
221
|
|
222
|
+
# The authority of the incoming request as defined by RFC3976.
|
223
|
+
# https://tools.ietf.org/html/rfc3986#section-3.2
|
224
|
+
#
|
225
|
+
# In HTTP/1, this is the `host` header.
|
226
|
+
# In HTTP/2, this is the `:authority` pseudo-header.
|
215
227
|
def authority
|
216
|
-
|
228
|
+
forwarded_authority || host_authority || server_authority
|
229
|
+
end
|
230
|
+
|
231
|
+
# The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
|
232
|
+
# variables.
|
233
|
+
def server_authority
|
234
|
+
host = self.server_name
|
235
|
+
port = self.server_port
|
236
|
+
|
237
|
+
if host
|
238
|
+
if port
|
239
|
+
"#{host}:#{port}"
|
240
|
+
else
|
241
|
+
host
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def server_name
|
247
|
+
get_header(SERVER_NAME)
|
248
|
+
end
|
249
|
+
|
250
|
+
def server_port
|
251
|
+
if port = get_header(SERVER_PORT)
|
252
|
+
Integer(port)
|
253
|
+
end
|
217
254
|
end
|
218
255
|
|
219
256
|
def cookies
|
220
|
-
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |
|
221
|
-
set_header(
|
257
|
+
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
|
258
|
+
set_header(key, {})
|
259
|
+
end
|
260
|
+
|
261
|
+
string = get_header(HTTP_COOKIE)
|
262
|
+
|
263
|
+
unless string == get_header(RACK_REQUEST_COOKIE_STRING)
|
264
|
+
hash.replace Utils.parse_cookies_header(string)
|
265
|
+
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
222
266
|
end
|
223
|
-
string = get_header HTTP_COOKIE
|
224
267
|
|
225
|
-
return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
|
226
|
-
hash.replace Utils.parse_cookies_header string
|
227
|
-
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
228
268
|
hash
|
229
269
|
end
|
230
270
|
|
@@ -237,52 +277,101 @@ module Rack
|
|
237
277
|
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
238
278
|
end
|
239
279
|
|
240
|
-
|
241
|
-
|
242
|
-
|
280
|
+
# The `HTTP_HOST` header.
|
281
|
+
def host_authority
|
282
|
+
get_header(HTTP_HOST)
|
283
|
+
end
|
284
|
+
|
285
|
+
def host_with_port(authority = self.authority)
|
286
|
+
host, _, port = split_authority(authority)
|
287
|
+
|
288
|
+
if port == DEFAULT_PORTS[self.scheme]
|
289
|
+
host
|
243
290
|
else
|
244
|
-
|
291
|
+
authority
|
245
292
|
end
|
246
293
|
end
|
247
294
|
|
295
|
+
# Returns a formatted host, suitable for being used in a URI.
|
248
296
|
def host
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
297
|
+
split_authority(self.authority)[0]
|
298
|
+
end
|
299
|
+
|
300
|
+
# Returns an address suitable for being to resolve to an address.
|
301
|
+
# In the case of a domain name or IPv4 address, the result is the same
|
302
|
+
# as +host+. In the case of IPv6 or future address formats, the square
|
303
|
+
# brackets are removed.
|
304
|
+
def hostname
|
305
|
+
split_authority(self.authority)[1]
|
256
306
|
end
|
257
307
|
|
258
308
|
def port
|
259
|
-
if
|
260
|
-
port.
|
261
|
-
|
262
|
-
port
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
309
|
+
if authority = self.authority
|
310
|
+
_, _, port = split_authority(self.authority)
|
311
|
+
|
312
|
+
if port
|
313
|
+
return port
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
if forwarded_port = self.forwarded_port
|
318
|
+
return forwarded_port.first
|
319
|
+
end
|
320
|
+
|
321
|
+
if scheme = self.scheme
|
322
|
+
if port = DEFAULT_PORTS[self.scheme]
|
323
|
+
return port
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
self.server_port
|
328
|
+
end
|
329
|
+
|
330
|
+
def forwarded_for
|
331
|
+
if value = get_header(HTTP_X_FORWARDED_FOR)
|
332
|
+
split_header(value).map do |authority|
|
333
|
+
split_authority(wrap_ipv6(authority))[1]
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def forwarded_port
|
339
|
+
if value = get_header(HTTP_X_FORWARDED_PORT)
|
340
|
+
split_header(value).map(&:to_i)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def forwarded_authority
|
345
|
+
if value = get_header(HTTP_X_FORWARDED_HOST)
|
346
|
+
wrap_ipv6(split_header(value).first)
|
269
347
|
end
|
270
348
|
end
|
271
349
|
|
272
350
|
def ssl?
|
273
|
-
scheme == 'https'
|
351
|
+
scheme == 'https' || scheme == 'wss'
|
274
352
|
end
|
275
353
|
|
276
354
|
def ip
|
277
|
-
|
278
|
-
|
355
|
+
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
356
|
+
external_addresses = reject_trusted_ip_addresses(remote_addresses)
|
279
357
|
|
280
|
-
|
358
|
+
unless external_addresses.empty?
|
359
|
+
return external_addresses.first
|
360
|
+
end
|
281
361
|
|
282
|
-
|
283
|
-
.
|
362
|
+
if forwarded_for = self.forwarded_for
|
363
|
+
unless forwarded_for.empty?
|
364
|
+
# The forwarded for addresses are ordered: client, proxy1, proxy2.
|
365
|
+
# So we reject all the trusted addresses (proxy*) and return the
|
366
|
+
# last client. Or if we trust everyone, we just return the first
|
367
|
+
# address.
|
368
|
+
return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
|
369
|
+
end
|
370
|
+
end
|
284
371
|
|
285
|
-
|
372
|
+
# If all the addresses are trusted, and we aren't forwarded, just return
|
373
|
+
# the first remote address, which represents the source of the request.
|
374
|
+
remote_addresses.first
|
286
375
|
end
|
287
376
|
|
288
377
|
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
@@ -323,6 +412,7 @@ module Rack
|
|
323
412
|
def form_data?
|
324
413
|
type = media_type
|
325
414
|
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
415
|
+
|
326
416
|
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
327
417
|
end
|
328
418
|
|
@@ -377,8 +467,6 @@ module Rack
|
|
377
467
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
378
468
|
def params
|
379
469
|
self.GET.merge(self.POST)
|
380
|
-
rescue EOFError
|
381
|
-
self.GET.dup
|
382
470
|
end
|
383
471
|
|
384
472
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
@@ -412,9 +500,7 @@ module Rack
|
|
412
500
|
end
|
413
501
|
|
414
502
|
def base_url
|
415
|
-
|
416
|
-
url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
|
417
|
-
url
|
503
|
+
"#{scheme}://#{host_with_port}"
|
418
504
|
end
|
419
505
|
|
420
506
|
# Tries to return a remake of the original request URL as a string.
|
@@ -471,6 +557,20 @@ module Rack
|
|
471
557
|
|
472
558
|
def default_session; {}; end
|
473
559
|
|
560
|
+
# Assist with compatibility when processing `X-Forwarded-For`.
|
561
|
+
def wrap_ipv6(host)
|
562
|
+
# Even thought IPv6 addresses should be wrapped in square brackets,
|
563
|
+
# sometimes this is not done in various legacy/underspecified headers.
|
564
|
+
# So we try to fix this situation for compatibility reasons.
|
565
|
+
|
566
|
+
# Try to detect IPv6 addresses which aren't escaped yet:
|
567
|
+
if !host.start_with?('[') && host.count(':') > 1
|
568
|
+
"[#{host}]"
|
569
|
+
else
|
570
|
+
host
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
474
574
|
def parse_http_accept_header(header)
|
475
575
|
header.to_s.split(/\s*,\s*/).map do |part|
|
476
576
|
attribute, parameters = part.split(/\s*;\s*/, 2)
|
@@ -494,27 +594,39 @@ module Rack
|
|
494
594
|
Rack::Multipart.extract_multipart(self, query_parser)
|
495
595
|
end
|
496
596
|
|
497
|
-
def
|
498
|
-
|
499
|
-
end
|
500
|
-
|
501
|
-
|
502
|
-
#
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
597
|
+
def split_header(value)
|
598
|
+
value ? value.strip.split(/[,\s]+/) : []
|
599
|
+
end
|
600
|
+
|
601
|
+
AUTHORITY = /^
|
602
|
+
# The host:
|
603
|
+
(?<host>
|
604
|
+
# An IPv6 address:
|
605
|
+
(\[(?<ip6>.*)\])
|
606
|
+
|
|
607
|
+
# An IPv4 address:
|
608
|
+
(?<ip4>[\d\.]+)
|
609
|
+
|
|
610
|
+
# A hostname:
|
611
|
+
(?<name>[a-zA-Z0-9\.\-]+)
|
612
|
+
)
|
613
|
+
# The optional port:
|
614
|
+
(:(?<port>\d+))?
|
615
|
+
$/x
|
616
|
+
|
617
|
+
private_constant :AUTHORITY
|
618
|
+
|
619
|
+
def split_authority(authority)
|
620
|
+
if match = AUTHORITY.match(authority)
|
621
|
+
if address = match[:ip6]
|
622
|
+
return match[:host], address, match[:port]&.to_i
|
623
|
+
else
|
624
|
+
return match[:host], match[:host], match[:port]&.to_i
|
625
|
+
end
|
515
626
|
end
|
516
627
|
|
517
|
-
|
628
|
+
# Give up!
|
629
|
+
return authority, authority, nil
|
518
630
|
end
|
519
631
|
|
520
632
|
def reject_trusted_ip_addresses(ip_addresses)
|
@@ -539,12 +651,6 @@ module Rack
|
|
539
651
|
end
|
540
652
|
end
|
541
653
|
end
|
542
|
-
|
543
|
-
def extract_port(uri)
|
544
|
-
if (colon_index = uri.index(':'))
|
545
|
-
uri[colon_index + 1, uri.length]
|
546
|
-
end
|
547
|
-
end
|
548
654
|
end
|
549
655
|
|
550
656
|
include Env
|
data/lib/rack/response.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
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
|
|
9
5
|
module Rack
|
@@ -19,34 +15,51 @@ module Rack
|
|
19
15
|
# +write+ are synchronous with the Rack response.
|
20
16
|
#
|
21
17
|
# Your application's +call+ should end returning Response#finish.
|
22
|
-
|
23
18
|
class Response
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
def self.[](status, headers, body)
|
20
|
+
self.new(body, status, headers)
|
21
|
+
end
|
27
22
|
|
28
23
|
CHUNKED = 'chunked'
|
29
24
|
STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
|
30
25
|
|
31
|
-
|
26
|
+
attr_accessor :length, :status, :body
|
27
|
+
attr_reader :headers
|
28
|
+
|
29
|
+
# @deprecated Use {#headers} instead.
|
30
|
+
alias header headers
|
31
|
+
|
32
|
+
# Initialize the response object with the specified body, status
|
33
|
+
# and headers.
|
34
|
+
#
|
35
|
+
# @param body [nil, #each, #to_str] the response body.
|
36
|
+
# @param status [Integer] the integer status as defined by the
|
37
|
+
# HTTP protocol RFCs.
|
38
|
+
# @param headers [#each] a list of key-value header pairs which
|
39
|
+
# conform to the HTTP protocol RFCs.
|
40
|
+
#
|
41
|
+
# Providing a body which responds to #to_str is legacy behaviour.
|
42
|
+
def initialize(body = nil, status = 200, headers = {})
|
32
43
|
@status = status.to_i
|
33
|
-
@
|
44
|
+
@headers = Utils::HeaderHash[headers]
|
34
45
|
|
35
46
|
@writer = self.method(:append)
|
36
47
|
|
37
48
|
@block = nil
|
38
|
-
@length = 0
|
39
49
|
|
40
50
|
# Keep track of whether we have expanded the user supplied body.
|
41
51
|
if body.nil?
|
42
52
|
@body = []
|
43
53
|
@buffered = true
|
54
|
+
@length = 0
|
44
55
|
elsif body.respond_to?(:to_str)
|
45
56
|
@body = [body]
|
46
57
|
@buffered = true
|
58
|
+
@length = body.to_str.bytesize
|
47
59
|
else
|
48
60
|
@body = body
|
49
61
|
@buffered = false
|
62
|
+
@length = 0
|
50
63
|
end
|
51
64
|
|
52
65
|
yield self if block_given?
|
@@ -61,18 +74,21 @@ module Rack
|
|
61
74
|
CHUNKED == get_header(TRANSFER_ENCODING)
|
62
75
|
end
|
63
76
|
|
77
|
+
# Generate a response array consistent with the requirements of the SPEC.
|
78
|
+
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
79
|
+
# which is suitable to be returned from the middleware `#call(env)` method.
|
64
80
|
def finish(&block)
|
65
81
|
if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
|
66
82
|
delete_header CONTENT_TYPE
|
67
83
|
delete_header CONTENT_LENGTH
|
68
84
|
close
|
69
|
-
[status
|
85
|
+
return [@status, @headers, []]
|
70
86
|
else
|
71
87
|
if block_given?
|
72
88
|
@block = block
|
73
|
-
[status
|
89
|
+
return [@status, @headers, self]
|
74
90
|
else
|
75
|
-
[status
|
91
|
+
return [@status, @headers, @body]
|
76
92
|
end
|
77
93
|
end
|
78
94
|
end
|
@@ -152,7 +168,7 @@ module Rack
|
|
152
168
|
# assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
|
153
169
|
#
|
154
170
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
155
|
-
def add_header
|
171
|
+
def add_header(key, v)
|
156
172
|
if v.nil?
|
157
173
|
get_header key
|
158
174
|
elsif has_header? key
|
@@ -162,10 +178,16 @@ module Rack
|
|
162
178
|
end
|
163
179
|
end
|
164
180
|
|
181
|
+
# Get the content type of the response.
|
165
182
|
def content_type
|
166
183
|
get_header CONTENT_TYPE
|
167
184
|
end
|
168
185
|
|
186
|
+
# Set the content type of the response.
|
187
|
+
def content_type=(content_type)
|
188
|
+
set_header CONTENT_TYPE, content_type
|
189
|
+
end
|
190
|
+
|
169
191
|
def media_type
|
170
192
|
MediaType.type(content_type)
|
171
193
|
end
|
@@ -200,7 +222,7 @@ module Rack
|
|
200
222
|
get_header SET_COOKIE
|
201
223
|
end
|
202
224
|
|
203
|
-
def set_cookie_header=
|
225
|
+
def set_cookie_header=(v)
|
204
226
|
set_header SET_COOKIE, v
|
205
227
|
end
|
206
228
|
|
@@ -208,15 +230,31 @@ module Rack
|
|
208
230
|
get_header CACHE_CONTROL
|
209
231
|
end
|
210
232
|
|
211
|
-
def cache_control=
|
233
|
+
def cache_control=(v)
|
212
234
|
set_header CACHE_CONTROL, v
|
213
235
|
end
|
214
236
|
|
237
|
+
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
238
|
+
def do_not_cache!
|
239
|
+
set_header CACHE_CONTROL, "no-cache, must-revalidate"
|
240
|
+
set_header EXPIRES, Time.now.httpdate
|
241
|
+
end
|
242
|
+
|
243
|
+
# Specify that the content should be cached.
|
244
|
+
# @param duration [Integer] The number of seconds until the cache expires.
|
245
|
+
# @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
|
246
|
+
def cache!(duration = 3600, directive: "public")
|
247
|
+
unless headers[CACHE_CONTROL] =~ /no-cache/
|
248
|
+
set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
|
249
|
+
set_header EXPIRES, (Time.now + duration).httpdate
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
215
253
|
def etag
|
216
254
|
get_header ETAG
|
217
255
|
end
|
218
256
|
|
219
|
-
def etag=
|
257
|
+
def etag=(v)
|
220
258
|
set_header ETAG, v
|
221
259
|
end
|
222
260
|
|
@@ -228,6 +266,9 @@ module Rack
|
|
228
266
|
if @body.is_a?(Array)
|
229
267
|
# The user supplied body was an array:
|
230
268
|
@body = @body.compact
|
269
|
+
@body.each do |part|
|
270
|
+
@length += part.to_s.bytesize
|
271
|
+
end
|
231
272
|
else
|
232
273
|
# Turn the user supplied body into a buffered array:
|
233
274
|
body = @body
|
@@ -236,6 +277,8 @@ module Rack
|
|
236
277
|
body.each do |part|
|
237
278
|
@writer.call(part.to_s)
|
238
279
|
end
|
280
|
+
|
281
|
+
body.close if body.respond_to?(:close)
|
239
282
|
end
|
240
283
|
|
241
284
|
@buffered = true
|
@@ -261,7 +304,7 @@ module Rack
|
|
261
304
|
attr_reader :headers
|
262
305
|
attr_accessor :status
|
263
306
|
|
264
|
-
def initialize
|
307
|
+
def initialize(status, headers)
|
265
308
|
@status = status
|
266
309
|
@headers = headers
|
267
310
|
end
|