rack 1.5.5 → 1.6.0.beta
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/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/test/spec_content_length.rb
CHANGED
@@ -62,7 +62,9 @@ describe Rack::ContentLength do
|
|
62
62
|
end.new(%w[one two three])
|
63
63
|
|
64
64
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
65
|
-
content_length(app).call(request)
|
65
|
+
response = content_length(app).call(request)
|
66
|
+
body.closed.should.equal nil
|
67
|
+
response[2].close
|
66
68
|
body.closed.should.equal true
|
67
69
|
end
|
68
70
|
|
data/test/spec_deflater.rb
CHANGED
@@ -6,199 +6,334 @@ require 'rack/mock'
|
|
6
6
|
require 'zlib'
|
7
7
|
|
8
8
|
describe Rack::Deflater do
|
9
|
-
def deflater(app)
|
10
|
-
Rack::Lint.new Rack::Deflater.new(app)
|
11
|
-
end
|
12
9
|
|
13
|
-
def build_response(status, body, accept_encoding,
|
14
|
-
body = [body]
|
10
|
+
def build_response(status, body, accept_encoding, options = {})
|
11
|
+
body = [body] if body.respond_to? :to_str
|
15
12
|
app = lambda do |env|
|
16
|
-
res = [status, {}, body]
|
17
|
-
res[1][
|
13
|
+
res = [status, options['response_headers'] || {}, body]
|
14
|
+
res[1]['Content-Type'] = 'text/plain' unless res[0] == 304
|
18
15
|
res
|
19
16
|
end
|
20
|
-
request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
|
21
|
-
response = deflater(app).call(request)
|
22
17
|
|
23
|
-
|
24
|
-
|
18
|
+
request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding))
|
19
|
+
deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {})
|
25
20
|
|
26
|
-
|
27
|
-
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
28
|
-
inflater.inflate(buf) << inflater.finish
|
21
|
+
deflater.call(request)
|
29
22
|
end
|
30
23
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
24
|
+
##
|
25
|
+
# Constructs response object and verifies if it yields right results
|
26
|
+
#
|
27
|
+
# [expected_status] expected response status, e.g. 200, 304
|
28
|
+
# [expected_body] expected response body
|
29
|
+
# [accept_encoing] what Accept-Encoding header to send and expect, e.g.
|
30
|
+
# 'deflate' - accepts and expects deflate encoding in response
|
31
|
+
# { 'gzip' => nil } - accepts gzip but expects no encoding in response
|
32
|
+
# [options] hash of request options, i.e.
|
33
|
+
# 'app_status' - what status dummy app should return (may be changed by deflater at some point)
|
34
|
+
# 'app_body' - what body dummy app should return (may be changed by deflater at some point)
|
35
|
+
# 'request_headers' - extra reqest headers to be sent
|
36
|
+
# 'response_headers' - extra response headers to be returned
|
37
|
+
# 'deflater_options' - options passed to deflater middleware
|
38
|
+
# [block] useful for doing some extra verification
|
39
|
+
def verify(expected_status, expected_body, accept_encoding, options = {}, &block)
|
40
|
+
accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash)
|
41
|
+
[accept_encoding.keys.first, accept_encoding.values.first]
|
42
|
+
else
|
43
|
+
[accept_encoding, accept_encoding.dup]
|
44
|
+
end
|
51
45
|
|
52
|
-
|
46
|
+
# build response
|
47
|
+
status, headers, body = build_response(
|
48
|
+
options['app_status'] || expected_status,
|
49
|
+
options['app_body'] || expected_body,
|
50
|
+
accept_encoding,
|
51
|
+
options
|
52
|
+
)
|
53
|
+
|
54
|
+
# verify status
|
55
|
+
status.should.equal(expected_status)
|
56
|
+
|
57
|
+
# verify body
|
58
|
+
unless options['skip_body_verify']
|
59
|
+
body_text = ''
|
60
|
+
body.each { |part| body_text << part }
|
61
|
+
|
62
|
+
deflated_body = case expected_encoding
|
63
|
+
when 'deflate'
|
64
|
+
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
65
|
+
inflater.inflate(body_text) << inflater.finish
|
66
|
+
when 'gzip'
|
67
|
+
io = StringIO.new(body_text)
|
68
|
+
gz = Zlib::GzipReader.new(io)
|
69
|
+
tmp = gz.read
|
70
|
+
gz.close
|
71
|
+
tmp
|
72
|
+
else
|
73
|
+
body_text
|
74
|
+
end
|
75
|
+
|
76
|
+
deflated_body.should.equal(expected_body)
|
77
|
+
end
|
53
78
|
|
54
|
-
response
|
55
|
-
|
56
|
-
"Content-Encoding" => "deflate",
|
57
|
-
"Vary" => "Accept-Encoding",
|
58
|
-
"Content-Type" => "text/plain"
|
59
|
-
})
|
60
|
-
buf = []
|
61
|
-
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
62
|
-
response[2].each { |part| buf << inflater.inflate(part) }
|
63
|
-
buf << inflater.finish
|
64
|
-
buf.delete_if { |part| part.empty? }
|
65
|
-
buf.join.should.equal("foobar")
|
79
|
+
# yield full response verification
|
80
|
+
yield(status, headers, body) if block_given?
|
66
81
|
end
|
67
82
|
|
68
|
-
|
69
|
-
|
70
|
-
|
83
|
+
should 'be able to deflate bodies that respond to each' do
|
84
|
+
app_body = Object.new
|
85
|
+
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
71
86
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
response[2].each { |part| buf << part }
|
80
|
-
inflate(buf).should.equal("Hello world!")
|
87
|
+
verify(200, 'foobar', 'deflate', { 'app_body' => app_body }) do |status, headers, body|
|
88
|
+
headers.should.equal({
|
89
|
+
'Content-Encoding' => 'deflate',
|
90
|
+
'Vary' => 'Accept-Encoding',
|
91
|
+
'Content-Type' => 'text/plain'
|
92
|
+
})
|
93
|
+
end
|
81
94
|
end
|
82
95
|
|
83
|
-
should
|
84
|
-
|
85
|
-
class <<
|
96
|
+
should 'flush deflated chunks to the client as they become ready' do
|
97
|
+
app_body = Object.new
|
98
|
+
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
86
99
|
|
87
|
-
|
100
|
+
verify(200, app_body, 'deflate', { 'skip_body_verify' => true }) do |status, headers, body|
|
101
|
+
headers.should.equal({
|
102
|
+
'Content-Encoding' => 'deflate',
|
103
|
+
'Vary' => 'Accept-Encoding',
|
104
|
+
'Content-Type' => 'text/plain'
|
105
|
+
})
|
88
106
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
"Content-Type" => "text/plain"
|
94
|
-
})
|
107
|
+
buf = []
|
108
|
+
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
109
|
+
body.each { |part| buf << inflater.inflate(part) }
|
110
|
+
buf << inflater.finish
|
95
111
|
|
96
|
-
|
97
|
-
|
98
|
-
io = StringIO.new(buf)
|
99
|
-
gz = Zlib::GzipReader.new(io)
|
100
|
-
gz.read.should.equal("foobar")
|
101
|
-
gz.close
|
112
|
+
buf.delete_if { |part| part.empty? }.join.should.equal('foobar')
|
113
|
+
end
|
102
114
|
end
|
103
115
|
|
104
|
-
|
105
|
-
|
106
|
-
|
116
|
+
# TODO: This is really just a special case of the above...
|
117
|
+
should 'be able to deflate String bodies' do
|
118
|
+
verify(200, 'Hello world!', 'deflate') do |status, headers, body|
|
119
|
+
headers.should.equal({
|
120
|
+
'Content-Encoding' => 'deflate',
|
121
|
+
'Vary' => 'Accept-Encoding',
|
122
|
+
'Content-Type' => 'text/plain'
|
123
|
+
})
|
124
|
+
end
|
125
|
+
end
|
107
126
|
|
108
|
-
|
127
|
+
should 'be able to gzip bodies that respond to each' do
|
128
|
+
app_body = Object.new
|
129
|
+
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
109
130
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
118
|
-
response[2].each { |part| buf << inflater.inflate(part) }
|
119
|
-
buf << inflater.finish
|
120
|
-
buf.delete_if { |part| part.empty? }
|
121
|
-
buf.join.should.equal("foobar")
|
131
|
+
verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body|
|
132
|
+
headers.should.equal({
|
133
|
+
'Content-Encoding' => 'gzip',
|
134
|
+
'Vary' => 'Accept-Encoding',
|
135
|
+
'Content-Type' => 'text/plain'
|
136
|
+
})
|
137
|
+
end
|
122
138
|
end
|
123
139
|
|
124
|
-
should
|
125
|
-
|
140
|
+
should 'flush gzipped chunks to the client as they become ready' do
|
141
|
+
app_body = Object.new
|
142
|
+
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
143
|
+
|
144
|
+
verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body|
|
145
|
+
headers.should.equal({
|
146
|
+
'Content-Encoding' => 'gzip',
|
147
|
+
'Vary' => 'Accept-Encoding',
|
148
|
+
'Content-Type' => 'text/plain'
|
149
|
+
})
|
150
|
+
|
151
|
+
buf = []
|
152
|
+
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
153
|
+
body.each { |part| buf << inflater.inflate(part) }
|
154
|
+
buf << inflater.finish
|
126
155
|
|
127
|
-
|
128
|
-
|
129
|
-
response[2].to_enum.to_a.should.equal(["Hello world!"])
|
156
|
+
buf.delete_if { |part| part.empty? }.join.should.equal('foobar')
|
157
|
+
end
|
130
158
|
end
|
131
159
|
|
132
|
-
should
|
133
|
-
|
160
|
+
should 'be able to fallback to no deflation' do
|
161
|
+
verify(200, 'Hello world!', 'superzip') do |status, headers, body|
|
162
|
+
headers.should.equal({
|
163
|
+
'Vary' => 'Accept-Encoding',
|
164
|
+
'Content-Type' => 'text/plain'
|
165
|
+
})
|
166
|
+
end
|
167
|
+
end
|
134
168
|
|
135
|
-
|
136
|
-
|
137
|
-
|
169
|
+
should 'be able to skip when there is no response entity body' do
|
170
|
+
verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body|
|
171
|
+
headers.should.equal({})
|
172
|
+
end
|
138
173
|
end
|
139
174
|
|
140
|
-
should
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
175
|
+
should 'handle the lack of an acceptable encoding' do
|
176
|
+
app_body = 'Hello world!'
|
177
|
+
not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.'
|
178
|
+
not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.'
|
179
|
+
options1 = {
|
180
|
+
'app_status' => 200,
|
181
|
+
'app_body' => app_body,
|
182
|
+
'request_headers' => {
|
183
|
+
'PATH_INFO' => '/'
|
184
|
+
}
|
185
|
+
}
|
186
|
+
options2 = {
|
187
|
+
'app_status' => 200,
|
188
|
+
'app_body' => app_body,
|
189
|
+
'request_headers' => {
|
190
|
+
'PATH_INFO' => '/foo/bar'
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body|
|
195
|
+
headers.should.equal({
|
196
|
+
'Content-Type' => 'text/plain',
|
197
|
+
'Content-Length' => not_found_body1.length.to_s
|
198
|
+
})
|
199
|
+
end
|
145
200
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
201
|
+
verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body|
|
202
|
+
headers.should.equal({
|
203
|
+
'Content-Type' => 'text/plain',
|
204
|
+
'Content-Length' => not_found_body2.length.to_s
|
205
|
+
})
|
206
|
+
end
|
150
207
|
end
|
151
208
|
|
152
|
-
should
|
209
|
+
should 'handle gzip response with Last-Modified header' do
|
153
210
|
last_modified = Time.now.httpdate
|
211
|
+
options = {
|
212
|
+
'response_headers' => {
|
213
|
+
'Content-Type' => 'text/plain',
|
214
|
+
'Last-Modified' => last_modified
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
verify(200, 'Hello World!', 'gzip', options) do |status, headers, body|
|
219
|
+
headers.should.equal({
|
220
|
+
'Content-Encoding' => 'gzip',
|
221
|
+
'Vary' => 'Accept-Encoding',
|
222
|
+
'Last-Modified' => last_modified,
|
223
|
+
'Content-Type' => 'text/plain'
|
224
|
+
})
|
225
|
+
end
|
226
|
+
end
|
154
227
|
|
155
|
-
|
156
|
-
|
157
|
-
|
228
|
+
should 'do nothing when no-transform Cache-Control directive present' do
|
229
|
+
options = {
|
230
|
+
'response_headers' => {
|
231
|
+
'Content-Type' => 'text/plain',
|
232
|
+
'Cache-Control' => 'no-transform'
|
233
|
+
}
|
234
|
+
}
|
235
|
+
verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body|
|
236
|
+
headers.should.not.include 'Content-Encoding'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
should 'do nothing when Content-Encoding already present' do
|
241
|
+
options = {
|
242
|
+
'response_headers' => {
|
243
|
+
'Content-Type' => 'text/plain',
|
244
|
+
'Content-Encoding' => 'gzip'
|
245
|
+
}
|
246
|
+
}
|
247
|
+
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
248
|
+
end
|
158
249
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
}
|
250
|
+
should 'deflate when Content-Encoding is identity' do
|
251
|
+
options = {
|
252
|
+
'response_headers' => {
|
253
|
+
'Content-Type' => 'text/plain',
|
254
|
+
'Content-Encoding' => 'identity'
|
255
|
+
}
|
256
|
+
}
|
257
|
+
verify(200, 'Hello World!', 'deflate', options)
|
258
|
+
end
|
166
259
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
260
|
+
should "deflate if content-type matches :include" do
|
261
|
+
options = {
|
262
|
+
'response_headers' => {
|
263
|
+
'Content-Type' => 'text/plain'
|
264
|
+
},
|
265
|
+
'deflater_options' => {
|
266
|
+
:include => %w(text/plain)
|
267
|
+
}
|
268
|
+
}
|
269
|
+
verify(200, 'Hello World!', 'gzip', options)
|
173
270
|
end
|
174
271
|
|
175
|
-
should "
|
176
|
-
|
177
|
-
|
178
|
-
|
272
|
+
should "deflate if content-type is included it :include" do
|
273
|
+
options = {
|
274
|
+
'response_headers' => {
|
275
|
+
'Content-Type' => 'text/plain; charset=us-ascii'
|
276
|
+
},
|
277
|
+
'deflater_options' => {
|
278
|
+
:include => %w(text/plain)
|
279
|
+
}
|
280
|
+
}
|
281
|
+
verify(200, 'Hello World!', 'gzip', options)
|
282
|
+
end
|
179
283
|
|
180
|
-
|
181
|
-
|
182
|
-
|
284
|
+
should "not deflate if content-type is not set but given in :include" do
|
285
|
+
options = {
|
286
|
+
'deflater_options' => {
|
287
|
+
:include => %w(text/plain)
|
288
|
+
}
|
289
|
+
}
|
290
|
+
verify(304, 'Hello World!', { 'gzip' => nil }, options)
|
183
291
|
end
|
184
292
|
|
185
|
-
should "
|
186
|
-
|
187
|
-
|
188
|
-
|
293
|
+
should "not deflate if content-type do not match :include" do
|
294
|
+
options = {
|
295
|
+
'response_headers' => {
|
296
|
+
'Content-Type' => 'text/plain'
|
297
|
+
},
|
298
|
+
'deflater_options' => {
|
299
|
+
:include => %w(text/json)
|
300
|
+
}
|
301
|
+
}
|
302
|
+
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
303
|
+
end
|
189
304
|
|
190
|
-
|
191
|
-
|
305
|
+
should "deflate response if :if lambda evaluates to true" do
|
306
|
+
options = {
|
307
|
+
'deflater_options' => {
|
308
|
+
:if => lambda { |env, status, headers, body| true }
|
309
|
+
}
|
310
|
+
}
|
311
|
+
verify(200, 'Hello World!', 'deflate', options)
|
192
312
|
end
|
193
313
|
|
194
|
-
should "deflate
|
195
|
-
|
196
|
-
|
197
|
-
|
314
|
+
should "not deflate if :if lambda evaluates to false" do
|
315
|
+
options = {
|
316
|
+
'deflater_options' => {
|
317
|
+
:if => lambda { |env, status, headers, body| false }
|
318
|
+
}
|
319
|
+
}
|
320
|
+
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
321
|
+
end
|
198
322
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
323
|
+
should "check for Content-Length via :if" do
|
324
|
+
body = 'Hello World!'
|
325
|
+
body_len = body.length
|
326
|
+
options = {
|
327
|
+
'response_headers' => {
|
328
|
+
'Content-Length' => body_len.to_s
|
329
|
+
},
|
330
|
+
'deflater_options' => {
|
331
|
+
:if => lambda { |env, status, headers, body|
|
332
|
+
headers['Content-Length'].to_i >= body_len
|
333
|
+
}
|
334
|
+
}
|
335
|
+
}
|
336
|
+
|
337
|
+
verify(200, body, 'gzip', options)
|
203
338
|
end
|
204
339
|
end
|