rack 1.5.5 → 1.6.0.beta
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/KNOWN-ISSUES +14 -0
- data/README.rdoc +10 -6
- data/Rakefile +3 -4
- data/SPEC +59 -23
- data/lib/rack.rb +2 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/backports/uri/common_18.rb +1 -1
- data/lib/rack/builder.rb +19 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/chunked.rb +12 -1
- data/lib/rack/commonlogger.rb +13 -5
- data/lib/rack/conditionalget.rb +14 -2
- data/lib/rack/content_length.rb +5 -1
- data/lib/rack/deflater.rb +52 -13
- data/lib/rack/directory.rb +8 -2
- data/lib/rack/etag.rb +14 -6
- data/lib/rack/file.rb +10 -14
- data/lib/rack/handler.rb +2 -0
- data/lib/rack/handler/fastcgi.rb +4 -1
- data/lib/rack/handler/mongrel.rb +8 -2
- data/lib/rack/handler/scgi.rb +4 -1
- data/lib/rack/handler/thin.rb +8 -2
- data/lib/rack/handler/webrick.rb +46 -6
- data/lib/rack/head.rb +7 -2
- data/lib/rack/lint.rb +73 -25
- data/lib/rack/lobster.rb +8 -3
- data/lib/rack/methodoverride.rb +14 -3
- data/lib/rack/mime.rb +1 -15
- data/lib/rack/mock.rb +15 -7
- data/lib/rack/multipart.rb +2 -2
- data/lib/rack/multipart/parser.rb +107 -53
- data/lib/rack/multipart/uploaded_file.rb +2 -2
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/request.rb +38 -24
- data/lib/rack/response.rb +5 -0
- data/lib/rack/sendfile.rb +10 -5
- data/lib/rack/server.rb +45 -17
- data/lib/rack/session/abstract/id.rb +7 -6
- data/lib/rack/session/cookie.rb +17 -7
- data/lib/rack/session/memcache.rb +4 -4
- data/lib/rack/session/pool.rb +3 -6
- data/lib/rack/showexceptions.rb +20 -11
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/static.rb +27 -30
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +17 -3
- data/lib/rack/utils.rb +78 -47
- data/lib/rack/utils/okjson.rb +90 -91
- data/rack.gemspec +3 -3
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/spec_builder.rb +13 -4
- data/test/spec_chunked.rb +16 -0
- data/test/spec_commonlogger.rb +36 -0
- data/test/spec_content_length.rb +3 -1
- data/test/spec_deflater.rb +283 -148
- data/test/spec_etag.rb +11 -2
- data/test/spec_file.rb +11 -3
- data/test/spec_head.rb +2 -0
- data/test/spec_lobster.rb +1 -1
- data/test/spec_mock.rb +8 -0
- data/test/spec_multipart.rb +111 -49
- data/test/spec_request.rb +109 -25
- data/test/spec_response.rb +30 -0
- data/test/spec_server.rb +20 -5
- data/test/spec_session_cookie.rb +45 -2
- data/test/spec_session_memcache.rb +1 -1
- data/test/spec_showexceptions.rb +29 -36
- data/test/spec_showstatus.rb +19 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/spec_urlmap.rb +23 -0
- data/test/spec_utils.rb +60 -10
- data/test/spec_webrick.rb +41 -0
- metadata +12 -9
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
data/lib/rack/content_length.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rack/utils'
|
2
|
+
require 'rack/body_proxy'
|
2
3
|
|
3
4
|
module Rack
|
4
5
|
|
@@ -22,7 +23,10 @@ module Rack
|
|
22
23
|
obody = body
|
23
24
|
body, length = [], 0
|
24
25
|
obody.each { |part| body << part; length += bytesize(part) }
|
25
|
-
|
26
|
+
|
27
|
+
body = BodyProxy.new(body) do
|
28
|
+
obody.close if obody.respond_to?(:close)
|
29
|
+
end
|
26
30
|
|
27
31
|
headers['Content-Length'] = length.to_s
|
28
32
|
end
|
data/lib/rack/deflater.rb
CHANGED
@@ -17,19 +17,26 @@ module Rack
|
|
17
17
|
# directive of 'no-transform' is present, or when the response status
|
18
18
|
# code is one that doesn't allow an entity body.
|
19
19
|
class Deflater
|
20
|
-
|
20
|
+
##
|
21
|
+
# Creates Rack::Deflater middleware.
|
22
|
+
#
|
23
|
+
# [app] rack app instance
|
24
|
+
# [options] hash of deflater options, i.e.
|
25
|
+
# 'if' - a lambda enabling / disabling deflation based on returned boolean value
|
26
|
+
# e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 }
|
27
|
+
# 'include' - a list of content types that should be compressed
|
28
|
+
def initialize(app, options = {})
|
21
29
|
@app = app
|
30
|
+
|
31
|
+
@condition = options[:if]
|
32
|
+
@compressible_types = options[:include]
|
22
33
|
end
|
23
34
|
|
24
35
|
def call(env)
|
25
36
|
status, headers, body = @app.call(env)
|
26
37
|
headers = Utils::HeaderHash.new(headers)
|
27
38
|
|
28
|
-
|
29
|
-
# no-transform set.
|
30
|
-
if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
31
|
-
headers['Cache-Control'].to_s =~ /\bno-transform\b/ ||
|
32
|
-
(headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
|
39
|
+
unless should_deflate?(env, status, headers, body)
|
33
40
|
return [status, headers, body]
|
34
41
|
end
|
35
42
|
|
@@ -58,9 +65,9 @@ module Rack
|
|
58
65
|
when "identity"
|
59
66
|
[status, headers, body]
|
60
67
|
when nil
|
61
|
-
body.close if body.respond_to?(:close)
|
62
68
|
message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
|
63
|
-
[
|
69
|
+
bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
|
70
|
+
[406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, bp]
|
64
71
|
end
|
65
72
|
end
|
66
73
|
|
@@ -68,6 +75,7 @@ module Rack
|
|
68
75
|
def initialize(body, mtime)
|
69
76
|
@body = body
|
70
77
|
@mtime = mtime
|
78
|
+
@closed = false
|
71
79
|
end
|
72
80
|
|
73
81
|
def each(&block)
|
@@ -79,7 +87,6 @@ module Rack
|
|
79
87
|
gzip.flush
|
80
88
|
}
|
81
89
|
ensure
|
82
|
-
@body.close if @body.respond_to?(:close)
|
83
90
|
gzip.close
|
84
91
|
@writer = nil
|
85
92
|
end
|
@@ -87,6 +94,12 @@ module Rack
|
|
87
94
|
def write(data)
|
88
95
|
@writer.call(data)
|
89
96
|
end
|
97
|
+
|
98
|
+
def close
|
99
|
+
return if @closed
|
100
|
+
@closed = true
|
101
|
+
@body.close if @body.respond_to?(:close)
|
102
|
+
end
|
90
103
|
end
|
91
104
|
|
92
105
|
class DeflateStream
|
@@ -100,17 +113,43 @@ module Rack
|
|
100
113
|
|
101
114
|
def initialize(body)
|
102
115
|
@body = body
|
116
|
+
@closed = false
|
103
117
|
end
|
104
118
|
|
105
119
|
def each
|
106
|
-
|
107
|
-
@body.each { |part| yield
|
108
|
-
yield
|
120
|
+
deflator = ::Zlib::Deflate.new(*DEFLATE_ARGS)
|
121
|
+
@body.each { |part| yield deflator.deflate(part, Zlib::SYNC_FLUSH) }
|
122
|
+
yield deflator.finish
|
109
123
|
nil
|
110
124
|
ensure
|
125
|
+
deflator.close
|
126
|
+
end
|
127
|
+
|
128
|
+
def close
|
129
|
+
return if @closed
|
130
|
+
@closed = true
|
111
131
|
@body.close if @body.respond_to?(:close)
|
112
|
-
deflater.close
|
113
132
|
end
|
114
133
|
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def should_deflate?(env, status, headers, body)
|
138
|
+
# Skip compressing empty entity body responses and responses with
|
139
|
+
# no-transform set.
|
140
|
+
if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
141
|
+
headers['Cache-Control'].to_s =~ /\bno-transform\b/ ||
|
142
|
+
(headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
|
143
|
+
return false
|
144
|
+
end
|
145
|
+
|
146
|
+
# Skip if @compressible_types are given and does not include request's content type
|
147
|
+
return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
|
148
|
+
|
149
|
+
# Skip if @condition lambda is given and evaluates to false
|
150
|
+
return false if @condition && !@condition.call(env, status, headers, body)
|
151
|
+
|
152
|
+
true
|
153
|
+
end
|
115
154
|
end
|
116
155
|
end
|
data/lib/rack/directory.rb
CHANGED
@@ -135,8 +135,8 @@ table { width:100%%; }
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def each
|
138
|
-
show_path = @path.sub(/^#{@root}/,'')
|
139
|
-
files = @files.map{|f| DIR_FILE % f }*"\n"
|
138
|
+
show_path = Rack::Utils.escape_html(@path.sub(/^#{@root}/,''))
|
139
|
+
files = @files.map{|f| DIR_FILE % DIR_FILE_escape(*f) }*"\n"
|
140
140
|
page = DIR_PAGE % [ show_path, show_path , files ]
|
141
141
|
page.each_line{|l| yield l }
|
142
142
|
end
|
@@ -157,5 +157,11 @@ table { width:100%%; }
|
|
157
157
|
|
158
158
|
int.to_s + 'B'
|
159
159
|
end
|
160
|
+
|
161
|
+
private
|
162
|
+
# Assumes url is already escaped.
|
163
|
+
def DIR_FILE_escape url, *html
|
164
|
+
[url, *html.map { |e| Utils.escape_html(e) }]
|
165
|
+
end
|
160
166
|
end
|
161
167
|
end
|
data/lib/rack/etag.rb
CHANGED
@@ -23,8 +23,12 @@ module Rack
|
|
23
23
|
status, headers, body = @app.call(env)
|
24
24
|
|
25
25
|
if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
|
26
|
-
|
27
|
-
|
26
|
+
original_body = body
|
27
|
+
digest, new_body = digest_body(body)
|
28
|
+
body = Rack::BodyProxy.new(new_body) do
|
29
|
+
original_body.close if original_body.respond_to?(:close)
|
30
|
+
end
|
31
|
+
headers['ETag'] = %(W/"#{digest}") if digest
|
28
32
|
end
|
29
33
|
|
30
34
|
unless headers['Cache-Control']
|
@@ -55,10 +59,14 @@ module Rack
|
|
55
59
|
|
56
60
|
def digest_body(body)
|
57
61
|
parts = []
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
digest = nil
|
63
|
+
|
64
|
+
body.each do |part|
|
65
|
+
parts << part
|
66
|
+
(digest ||= Digest::MD5.new) << part unless part.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
[digest && digest.hexdigest, parts]
|
62
70
|
end
|
63
71
|
end
|
64
72
|
end
|
data/lib/rack/file.rb
CHANGED
@@ -12,8 +12,8 @@ module Rack
|
|
12
12
|
# like sendfile on the +path+.
|
13
13
|
|
14
14
|
class File
|
15
|
-
|
16
|
-
|
15
|
+
ALLOWED_VERBS = %w[GET HEAD OPTIONS]
|
16
|
+
ALLOW_HEADER = ALLOWED_VERBS.join(', ')
|
17
17
|
|
18
18
|
attr_accessor :root
|
19
19
|
attr_accessor :path
|
@@ -35,20 +35,13 @@ module Rack
|
|
35
35
|
|
36
36
|
def _call(env)
|
37
37
|
unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
|
38
|
-
return fail(405, "Method Not Allowed")
|
38
|
+
return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
|
39
39
|
end
|
40
40
|
|
41
41
|
path_info = Utils.unescape(env["PATH_INFO"])
|
42
|
-
|
42
|
+
clean_path_info = Utils.clean_path_info(path_info)
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
parts.each do |part|
|
47
|
-
next if part.empty? || part == '.'
|
48
|
-
part == '..' ? clean.pop : clean << part
|
49
|
-
end
|
50
|
-
|
51
|
-
@path = F.join(@root, *clean)
|
44
|
+
@path = F.join(@root, clean_path_info)
|
52
45
|
|
53
46
|
available = begin
|
54
47
|
F.file?(@path) && F.readable?(@path)
|
@@ -64,6 +57,9 @@ module Rack
|
|
64
57
|
end
|
65
58
|
|
66
59
|
def serving(env)
|
60
|
+
if env["REQUEST_METHOD"] == "OPTIONS"
|
61
|
+
return [200, {'Allow' => ALLOW_HEADER, 'Content-Length' => '0'}, []]
|
62
|
+
end
|
67
63
|
last_modified = F.mtime(@path).httpdate
|
68
64
|
return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
|
69
65
|
|
@@ -121,7 +117,7 @@ module Rack
|
|
121
117
|
|
122
118
|
private
|
123
119
|
|
124
|
-
def fail(status, body)
|
120
|
+
def fail(status, body, headers = {})
|
125
121
|
body += "\n"
|
126
122
|
[
|
127
123
|
status,
|
@@ -129,7 +125,7 @@ module Rack
|
|
129
125
|
"Content-Type" => "text/plain",
|
130
126
|
"Content-Length" => body.size.to_s,
|
131
127
|
"X-Cascade" => "pass"
|
132
|
-
},
|
128
|
+
}.merge!(headers),
|
133
129
|
[body]
|
134
130
|
]
|
135
131
|
end
|
data/lib/rack/handler.rb
CHANGED
data/lib/rack/handler/fastcgi.rb
CHANGED
@@ -30,8 +30,11 @@ module Rack
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def self.valid_options
|
33
|
+
environment = ENV['RACK_ENV'] || 'development'
|
34
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
35
|
+
|
33
36
|
{
|
34
|
-
"Host=HOST" => "Hostname to listen on (default:
|
37
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
35
38
|
"Port=PORT" => "Port to listen on (default: 8080)",
|
36
39
|
"File=PATH" => "Creates a Domain socket at PATH instead of a TCP socket. Ignores Host and Port if set.",
|
37
40
|
}
|
data/lib/rack/handler/mongrel.rb
CHANGED
@@ -7,8 +7,11 @@ module Rack
|
|
7
7
|
module Handler
|
8
8
|
class Mongrel < ::Mongrel::HttpHandler
|
9
9
|
def self.run(app, options={})
|
10
|
+
environment = ENV['RACK_ENV'] || 'development'
|
11
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
12
|
+
|
10
13
|
server = ::Mongrel::HttpServer.new(
|
11
|
-
options[:Host] ||
|
14
|
+
options[:Host] || default_host,
|
12
15
|
options[:Port] || 8080,
|
13
16
|
options[:num_processors] || 950,
|
14
17
|
options[:throttle] || 0,
|
@@ -39,8 +42,11 @@ module Rack
|
|
39
42
|
end
|
40
43
|
|
41
44
|
def self.valid_options
|
45
|
+
environment = ENV['RACK_ENV'] || 'development'
|
46
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
47
|
+
|
42
48
|
{
|
43
|
-
"Host=HOST" => "Hostname to listen on (default:
|
49
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
44
50
|
"Port=PORT" => "Port to listen on (default: 8080)",
|
45
51
|
"Processors=N" => "Number of concurrent processors to accept (default: 950)",
|
46
52
|
"Timeout=N" => "Time before a request is dropped for inactivity (default: 60)",
|
data/lib/rack/handler/scgi.rb
CHANGED
@@ -17,8 +17,11 @@ module Rack
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.valid_options
|
20
|
+
environment = ENV['RACK_ENV'] || 'development'
|
21
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
22
|
+
|
20
23
|
{
|
21
|
-
"Host=HOST" => "Hostname to listen on (default:
|
24
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
22
25
|
"Port=PORT" => "Port to listen on (default: 8080)",
|
23
26
|
}
|
24
27
|
end
|
data/lib/rack/handler/thin.rb
CHANGED
@@ -6,7 +6,10 @@ module Rack
|
|
6
6
|
module Handler
|
7
7
|
class Thin
|
8
8
|
def self.run(app, options={})
|
9
|
-
|
9
|
+
environment = ENV['RACK_ENV'] || 'development'
|
10
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
11
|
+
|
12
|
+
host = options.delete(:Host) || default_host
|
10
13
|
port = options.delete(:Port) || 8080
|
11
14
|
args = [host, port, app, options]
|
12
15
|
# Thin versions below 0.8.0 do not support additional options
|
@@ -17,8 +20,11 @@ module Rack
|
|
17
20
|
end
|
18
21
|
|
19
22
|
def self.valid_options
|
23
|
+
environment = ENV['RACK_ENV'] || 'development'
|
24
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
25
|
+
|
20
26
|
{
|
21
|
-
"Host=HOST" => "Hostname to listen on (default:
|
27
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
22
28
|
"Port=PORT" => "Port to listen on (default: 8080)",
|
23
29
|
}
|
24
30
|
end
|
data/lib/rack/handler/webrick.rb
CHANGED
@@ -2,12 +2,33 @@ require 'webrick'
|
|
2
2
|
require 'stringio'
|
3
3
|
require 'rack/content_length'
|
4
4
|
|
5
|
+
# This monkey patch allows for applications to perform their own chunking
|
6
|
+
# through WEBrick::HTTPResponse iff rack is set to true.
|
7
|
+
class WEBrick::HTTPResponse
|
8
|
+
attr_accessor :rack
|
9
|
+
|
10
|
+
alias _rack_setup_header setup_header
|
11
|
+
def setup_header
|
12
|
+
app_chunking = rack && @header['transfer-encoding'] == 'chunked'
|
13
|
+
|
14
|
+
@chunked = app_chunking if app_chunking
|
15
|
+
|
16
|
+
_rack_setup_header
|
17
|
+
|
18
|
+
@chunked = false if app_chunking
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
5
22
|
module Rack
|
6
23
|
module Handler
|
7
24
|
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
|
8
25
|
def self.run(app, options={})
|
9
|
-
|
26
|
+
environment = ENV['RACK_ENV'] || 'development'
|
27
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
28
|
+
|
29
|
+
options[:BindAddress] = options.delete(:Host) || default_host
|
10
30
|
options[:Port] ||= 8080
|
31
|
+
options[:OutputBufferSize] = 5
|
11
32
|
@server = ::WEBrick::HTTPServer.new(options)
|
12
33
|
@server.mount "/", Rack::Handler::WEBrick, app
|
13
34
|
yield @server if block_given?
|
@@ -15,8 +36,11 @@ module Rack
|
|
15
36
|
end
|
16
37
|
|
17
38
|
def self.valid_options
|
39
|
+
environment = ENV['RACK_ENV'] || 'development'
|
40
|
+
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
41
|
+
|
18
42
|
{
|
19
|
-
"Host=HOST" => "Hostname to listen on (default:
|
43
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
20
44
|
"Port=PORT" => "Port to listen on (default: 8080)",
|
21
45
|
}
|
22
46
|
end
|
@@ -32,6 +56,7 @@ module Rack
|
|
32
56
|
end
|
33
57
|
|
34
58
|
def service(req, res)
|
59
|
+
res.rack = true
|
35
60
|
env = req.meta_vars
|
36
61
|
env.delete_if { |k, v| v.nil? }
|
37
62
|
|
@@ -46,7 +71,11 @@ module Rack
|
|
46
71
|
"rack.multiprocess" => false,
|
47
72
|
"rack.run_once" => false,
|
48
73
|
|
49
|
-
"rack.url_scheme" => ["yes", "on", "1"].include?(
|
74
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http",
|
75
|
+
|
76
|
+
"rack.hijack?" => true,
|
77
|
+
"rack.hijack" => lambda { raise NotImplementedError, "only partial hijack is supported."},
|
78
|
+
"rack.hijack_io" => nil,
|
50
79
|
})
|
51
80
|
|
52
81
|
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
@@ -61,6 +90,8 @@ module Rack
|
|
61
90
|
begin
|
62
91
|
res.status = status.to_i
|
63
92
|
headers.each { |k, vs|
|
93
|
+
next if k.downcase == "rack.hijack"
|
94
|
+
|
64
95
|
if k.downcase == "set-cookie"
|
65
96
|
res.cookies.concat vs.split("\n")
|
66
97
|
else
|
@@ -69,9 +100,18 @@ module Rack
|
|
69
100
|
res[k] = vs.split("\n").join(", ")
|
70
101
|
end
|
71
102
|
}
|
72
|
-
|
73
|
-
|
74
|
-
|
103
|
+
|
104
|
+
io_lambda = headers["rack.hijack"]
|
105
|
+
if io_lambda
|
106
|
+
rd, wr = IO.pipe
|
107
|
+
res.body = rd
|
108
|
+
res.chunked = true
|
109
|
+
io_lambda.call wr
|
110
|
+
else
|
111
|
+
body.each { |part|
|
112
|
+
res.body << part
|
113
|
+
}
|
114
|
+
end
|
75
115
|
ensure
|
76
116
|
body.close if body.respond_to? :close
|
77
117
|
end
|
data/lib/rack/head.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rack/body_proxy'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
class Head
|
@@ -11,8 +13,11 @@ class Head
|
|
11
13
|
status, headers, body = @app.call(env)
|
12
14
|
|
13
15
|
if env["REQUEST_METHOD"] == "HEAD"
|
14
|
-
|
15
|
-
|
16
|
+
[
|
17
|
+
status, headers, Rack::BodyProxy.new([]) do
|
18
|
+
body.close if body.respond_to? :close
|
19
|
+
end
|
20
|
+
]
|
16
21
|
else
|
17
22
|
[status, headers, body]
|
18
23
|
end
|