rack 2.0.6 → 2.1.0
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/{HISTORY.md → CHANGELOG.md} +220 -155
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +38 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -8
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +28 -17
- data/lib/rack/directory.rb +17 -14
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +5 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +14 -11
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +55 -52
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/multipart.rb +5 -3
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +80 -27
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -16
- data/lib/rack/session/abstract/id.rb +104 -21
- data/lib/rack/session/cookie.rb +21 -11
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +17 -8
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +55 -70
- data/lib/rack.rb +63 -60
- data/rack.gemspec +17 -7
- metadata +30 -171
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1398
- data/test/spec_response.rb +0 -510
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -320
- data/test/spec_session_pool.rb +0 -210
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,103 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'time'
|
3
|
-
require 'rack/conditional_get'
|
4
|
-
require 'rack/mock'
|
5
|
-
|
6
|
-
describe Rack::ConditionalGet do
|
7
|
-
def conditional_get(app)
|
8
|
-
Rack::Lint.new Rack::ConditionalGet.new(app)
|
9
|
-
end
|
10
|
-
|
11
|
-
it "set a 304 status and truncate body when If-Modified-Since hits" do
|
12
|
-
timestamp = Time.now.httpdate
|
13
|
-
app = conditional_get(lambda { |env|
|
14
|
-
[200, {'Last-Modified'=>timestamp}, ['TEST']] })
|
15
|
-
|
16
|
-
response = Rack::MockRequest.new(app).
|
17
|
-
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)
|
18
|
-
|
19
|
-
response.status.must_equal 304
|
20
|
-
response.body.must_be :empty?
|
21
|
-
end
|
22
|
-
|
23
|
-
it "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
|
24
|
-
app = conditional_get(lambda { |env|
|
25
|
-
[200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
|
26
|
-
|
27
|
-
response = Rack::MockRequest.new(app).
|
28
|
-
get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
|
29
|
-
|
30
|
-
response.status.must_equal 304
|
31
|
-
response.body.must_be :empty?
|
32
|
-
end
|
33
|
-
|
34
|
-
it "set a 304 status and truncate body when If-None-Match hits" do
|
35
|
-
app = conditional_get(lambda { |env|
|
36
|
-
[200, {'ETag'=>'1234'}, ['TEST']] })
|
37
|
-
|
38
|
-
response = Rack::MockRequest.new(app).
|
39
|
-
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
40
|
-
|
41
|
-
response.status.must_equal 304
|
42
|
-
response.body.must_be :empty?
|
43
|
-
end
|
44
|
-
|
45
|
-
it "not set a 304 status if If-Modified-Since hits but Etag does not" do
|
46
|
-
timestamp = Time.now.httpdate
|
47
|
-
app = conditional_get(lambda { |env|
|
48
|
-
[200, {'Last-Modified'=>timestamp, 'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
|
49
|
-
|
50
|
-
response = Rack::MockRequest.new(app).
|
51
|
-
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
|
52
|
-
|
53
|
-
response.status.must_equal 200
|
54
|
-
response.body.must_equal 'TEST'
|
55
|
-
end
|
56
|
-
|
57
|
-
it "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
|
58
|
-
timestamp = Time.now.httpdate
|
59
|
-
app = conditional_get(lambda { |env|
|
60
|
-
[200, {'Last-Modified'=>timestamp, 'ETag'=>'1234'}, ['TEST']] })
|
61
|
-
|
62
|
-
response = Rack::MockRequest.new(app).
|
63
|
-
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234')
|
64
|
-
|
65
|
-
response.status.must_equal 304
|
66
|
-
response.body.must_be :empty?
|
67
|
-
end
|
68
|
-
|
69
|
-
it "not affect non-GET/HEAD requests" do
|
70
|
-
app = conditional_get(lambda { |env|
|
71
|
-
[200, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
|
72
|
-
|
73
|
-
response = Rack::MockRequest.new(app).
|
74
|
-
post("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
75
|
-
|
76
|
-
response.status.must_equal 200
|
77
|
-
response.body.must_equal 'TEST'
|
78
|
-
end
|
79
|
-
|
80
|
-
it "not affect non-200 requests" do
|
81
|
-
app = conditional_get(lambda { |env|
|
82
|
-
[302, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
|
83
|
-
|
84
|
-
response = Rack::MockRequest.new(app).
|
85
|
-
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
86
|
-
|
87
|
-
response.status.must_equal 302
|
88
|
-
response.body.must_equal 'TEST'
|
89
|
-
end
|
90
|
-
|
91
|
-
it "not affect requests with malformed HTTP_IF_NONE_MATCH" do
|
92
|
-
bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
|
93
|
-
app = conditional_get(lambda { |env|
|
94
|
-
[200,{'Last-Modified'=>(Time.now - 3600).httpdate, 'Content-Type' => 'text/plain'}, ['TEST']] })
|
95
|
-
|
96
|
-
response = Rack::MockRequest.new(app).
|
97
|
-
get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp)
|
98
|
-
|
99
|
-
response.status.must_equal 200
|
100
|
-
response.body.must_equal 'TEST'
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
data/test/spec_config.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'rack/builder'
|
3
|
-
require 'rack/config'
|
4
|
-
require 'rack/content_length'
|
5
|
-
require 'rack/lint'
|
6
|
-
require 'rack/mock'
|
7
|
-
|
8
|
-
describe Rack::Config do
|
9
|
-
it "accept a block that modifies the environment" do
|
10
|
-
app = Rack::Builder.new do
|
11
|
-
use Rack::Lint
|
12
|
-
use Rack::Config do |env|
|
13
|
-
env['greeting'] = 'hello'
|
14
|
-
end
|
15
|
-
run lambda { |env|
|
16
|
-
[200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
|
-
response = Rack::MockRequest.new(app).get('/')
|
21
|
-
response.body.must_equal 'hello'
|
22
|
-
end
|
23
|
-
end
|
data/test/spec_content_length.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'rack/content_length'
|
3
|
-
require 'rack/lint'
|
4
|
-
require 'rack/mock'
|
5
|
-
|
6
|
-
describe Rack::ContentLength do
|
7
|
-
def content_length(app)
|
8
|
-
Rack::Lint.new Rack::ContentLength.new(app)
|
9
|
-
end
|
10
|
-
|
11
|
-
def request
|
12
|
-
Rack::MockRequest.env_for
|
13
|
-
end
|
14
|
-
|
15
|
-
it "set Content-Length on Array bodies if none is set" do
|
16
|
-
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
17
|
-
response = content_length(app).call(request)
|
18
|
-
response[1]['Content-Length'].must_equal '13'
|
19
|
-
end
|
20
|
-
|
21
|
-
it "not set Content-Length on variable length bodies" do
|
22
|
-
body = lambda { "Hello World!" }
|
23
|
-
def body.each ; yield call ; end
|
24
|
-
|
25
|
-
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
26
|
-
response = content_length(app).call(request)
|
27
|
-
response[1]['Content-Length'].must_be_nil
|
28
|
-
end
|
29
|
-
|
30
|
-
it "not change Content-Length if it is already set" do
|
31
|
-
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
|
32
|
-
response = content_length(app).call(request)
|
33
|
-
response[1]['Content-Length'].must_equal '1'
|
34
|
-
end
|
35
|
-
|
36
|
-
it "not set Content-Length on 304 responses" do
|
37
|
-
app = lambda { |env| [304, {}, []] }
|
38
|
-
response = content_length(app).call(request)
|
39
|
-
response[1]['Content-Length'].must_be_nil
|
40
|
-
end
|
41
|
-
|
42
|
-
it "not set Content-Length when Transfer-Encoding is chunked" do
|
43
|
-
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Transfer-Encoding' => 'chunked'}, []] }
|
44
|
-
response = content_length(app).call(request)
|
45
|
-
response[1]['Content-Length'].must_be_nil
|
46
|
-
end
|
47
|
-
|
48
|
-
# Using "Connection: close" for this is fairly contended. It might be useful
|
49
|
-
# to have some other way to signal this.
|
50
|
-
#
|
51
|
-
# should "not force a Content-Length when Connection:close" do
|
52
|
-
# app = lambda { |env| [200, {'Connection' => 'close'}, []] }
|
53
|
-
# response = content_length(app).call({})
|
54
|
-
# response[1]['Content-Length'].must_be_nil
|
55
|
-
# end
|
56
|
-
|
57
|
-
it "close bodies that need to be closed" do
|
58
|
-
body = Struct.new(:body) do
|
59
|
-
attr_reader :closed
|
60
|
-
def each; body.join; end
|
61
|
-
def close; @closed = true; end
|
62
|
-
def to_ary; end
|
63
|
-
end.new(%w[one two three])
|
64
|
-
|
65
|
-
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
66
|
-
response = content_length(app).call(request)
|
67
|
-
body.closed.must_be_nil
|
68
|
-
response[2].close
|
69
|
-
body.closed.must_equal true
|
70
|
-
end
|
71
|
-
|
72
|
-
it "support single-execute bodies" do
|
73
|
-
body = Struct.new(:body) do
|
74
|
-
def each
|
75
|
-
yield body.shift until body.empty?
|
76
|
-
end
|
77
|
-
def to_ary; end
|
78
|
-
end.new(%w[one two three])
|
79
|
-
|
80
|
-
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
81
|
-
response = content_length(app).call(request)
|
82
|
-
expected = %w[one two three]
|
83
|
-
response[1]['Content-Length'].must_equal expected.join.size.to_s
|
84
|
-
response[2].to_enum.to_a.must_equal expected
|
85
|
-
end
|
86
|
-
end
|
data/test/spec_content_type.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'rack/content_type'
|
3
|
-
require 'rack/lint'
|
4
|
-
require 'rack/mock'
|
5
|
-
|
6
|
-
describe Rack::ContentType do
|
7
|
-
def content_type(app, *args)
|
8
|
-
Rack::Lint.new Rack::ContentType.new(app, *args)
|
9
|
-
end
|
10
|
-
|
11
|
-
def request
|
12
|
-
Rack::MockRequest.env_for
|
13
|
-
end
|
14
|
-
|
15
|
-
it "set Content-Type to default text/html if none is set" do
|
16
|
-
app = lambda { |env| [200, {}, "Hello, World!"] }
|
17
|
-
headers = content_type(app).call(request)[1]
|
18
|
-
headers['Content-Type'].must_equal 'text/html'
|
19
|
-
end
|
20
|
-
|
21
|
-
it "set Content-Type to chosen default if none is set" do
|
22
|
-
app = lambda { |env| [200, {}, "Hello, World!"] }
|
23
|
-
headers =
|
24
|
-
content_type(app, 'application/octet-stream').call(request)[1]
|
25
|
-
headers['Content-Type'].must_equal 'application/octet-stream'
|
26
|
-
end
|
27
|
-
|
28
|
-
it "not change Content-Type if it is already set" do
|
29
|
-
app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
|
30
|
-
headers = content_type(app).call(request)[1]
|
31
|
-
headers['Content-Type'].must_equal 'foo/bar'
|
32
|
-
end
|
33
|
-
|
34
|
-
it "detect Content-Type case insensitive" do
|
35
|
-
app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
|
36
|
-
headers = content_type(app).call(request)[1]
|
37
|
-
headers.to_a.select { |k,v| k.downcase == "content-type" }.
|
38
|
-
must_equal [["CONTENT-Type","foo/bar"]]
|
39
|
-
end
|
40
|
-
|
41
|
-
it "not set Content-Type on 304 responses" do
|
42
|
-
app = lambda { |env| [304, {}, []] }
|
43
|
-
response = content_type(app, "text/html").call(request)
|
44
|
-
response[1]['Content-Type'].must_be_nil
|
45
|
-
end
|
46
|
-
end
|
data/test/spec_deflater.rb
DELETED
@@ -1,375 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'stringio'
|
3
|
-
require 'time' # for Time#httpdate
|
4
|
-
require 'rack/deflater'
|
5
|
-
require 'rack/lint'
|
6
|
-
require 'rack/mock'
|
7
|
-
require 'zlib'
|
8
|
-
|
9
|
-
describe Rack::Deflater do
|
10
|
-
|
11
|
-
def build_response(status, body, accept_encoding, options = {})
|
12
|
-
body = [body] if body.respond_to? :to_str
|
13
|
-
app = lambda do |env|
|
14
|
-
res = [status, options['response_headers'] || {}, body]
|
15
|
-
res[1]['Content-Type'] = 'text/plain' unless res[0] == 304
|
16
|
-
res
|
17
|
-
end
|
18
|
-
|
19
|
-
request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding))
|
20
|
-
deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {})
|
21
|
-
|
22
|
-
deflater.call(request)
|
23
|
-
end
|
24
|
-
|
25
|
-
##
|
26
|
-
# Constructs response object and verifies if it yields right results
|
27
|
-
#
|
28
|
-
# [expected_status] expected response status, e.g. 200, 304
|
29
|
-
# [expected_body] expected response body
|
30
|
-
# [accept_encoing] what Accept-Encoding header to send and expect, e.g.
|
31
|
-
# 'deflate' - accepts and expects deflate encoding in response
|
32
|
-
# { 'gzip' => nil } - accepts gzip but expects no encoding in response
|
33
|
-
# [options] hash of request options, i.e.
|
34
|
-
# 'app_status' - what status dummy app should return (may be changed by deflater at some point)
|
35
|
-
# 'app_body' - what body dummy app should return (may be changed by deflater at some point)
|
36
|
-
# 'request_headers' - extra request headers to be sent
|
37
|
-
# 'response_headers' - extra response headers to be returned
|
38
|
-
# 'deflater_options' - options passed to deflater middleware
|
39
|
-
# [block] useful for doing some extra verification
|
40
|
-
def verify(expected_status, expected_body, accept_encoding, options = {}, &block)
|
41
|
-
accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash)
|
42
|
-
[accept_encoding.keys.first, accept_encoding.values.first]
|
43
|
-
else
|
44
|
-
[accept_encoding, accept_encoding.dup]
|
45
|
-
end
|
46
|
-
|
47
|
-
# build response
|
48
|
-
status, headers, body = build_response(
|
49
|
-
options['app_status'] || expected_status,
|
50
|
-
options['app_body'] || expected_body,
|
51
|
-
accept_encoding,
|
52
|
-
options
|
53
|
-
)
|
54
|
-
|
55
|
-
# verify status
|
56
|
-
status.must_equal expected_status
|
57
|
-
|
58
|
-
# verify body
|
59
|
-
unless options['skip_body_verify']
|
60
|
-
body_text = ''
|
61
|
-
body.each { |part| body_text << part }
|
62
|
-
|
63
|
-
deflated_body = case expected_encoding
|
64
|
-
when 'deflate'
|
65
|
-
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
66
|
-
inflater.inflate(body_text) << inflater.finish
|
67
|
-
when 'gzip'
|
68
|
-
io = StringIO.new(body_text)
|
69
|
-
gz = Zlib::GzipReader.new(io)
|
70
|
-
tmp = gz.read
|
71
|
-
gz.close
|
72
|
-
tmp
|
73
|
-
else
|
74
|
-
body_text
|
75
|
-
end
|
76
|
-
|
77
|
-
deflated_body.must_equal expected_body
|
78
|
-
end
|
79
|
-
|
80
|
-
# yield full response verification
|
81
|
-
yield(status, headers, body) if block_given?
|
82
|
-
end
|
83
|
-
|
84
|
-
# automatic gzip detection (streamable)
|
85
|
-
def auto_inflater
|
86
|
-
Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
|
87
|
-
end
|
88
|
-
|
89
|
-
def deflate_or_gzip
|
90
|
-
{'deflate, gzip' => 'gzip'}
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'be able to deflate bodies that respond to each' do
|
94
|
-
app_body = Object.new
|
95
|
-
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
96
|
-
|
97
|
-
verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body|
|
98
|
-
headers.must_equal({
|
99
|
-
'Content-Encoding' => 'gzip',
|
100
|
-
'Vary' => 'Accept-Encoding',
|
101
|
-
'Content-Type' => 'text/plain'
|
102
|
-
})
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'flush deflated chunks to the client as they become ready' do
|
107
|
-
app_body = Object.new
|
108
|
-
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
109
|
-
|
110
|
-
verify(200, app_body, deflate_or_gzip, { 'skip_body_verify' => true }) do |status, headers, body|
|
111
|
-
headers.must_equal({
|
112
|
-
'Content-Encoding' => 'gzip',
|
113
|
-
'Vary' => 'Accept-Encoding',
|
114
|
-
'Content-Type' => 'text/plain'
|
115
|
-
})
|
116
|
-
|
117
|
-
buf = []
|
118
|
-
inflater = auto_inflater
|
119
|
-
body.each { |part| buf << inflater.inflate(part) }
|
120
|
-
buf << inflater.finish
|
121
|
-
|
122
|
-
buf.delete_if { |part| part.empty? }.join.must_equal 'foobar'
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'does not raise when a client aborts reading' do
|
127
|
-
app_body = Object.new
|
128
|
-
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
129
|
-
opts = { 'skip_body_verify' => true }
|
130
|
-
verify(200, app_body, 'gzip', opts) do |status, headers, body|
|
131
|
-
headers.must_equal({
|
132
|
-
'Content-Encoding' => 'gzip',
|
133
|
-
'Vary' => 'Accept-Encoding',
|
134
|
-
'Content-Type' => 'text/plain'
|
135
|
-
})
|
136
|
-
|
137
|
-
buf = []
|
138
|
-
inflater = auto_inflater
|
139
|
-
FakeDisconnect = Class.new(RuntimeError)
|
140
|
-
assert_raises(FakeDisconnect, "not Zlib::DataError not raised") do
|
141
|
-
body.each do |part|
|
142
|
-
tmp = inflater.inflate(part)
|
143
|
-
buf << tmp if tmp.bytesize > 0
|
144
|
-
raise FakeDisconnect
|
145
|
-
end
|
146
|
-
end
|
147
|
-
inflater.finish
|
148
|
-
buf.must_equal(%w(foo))
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# TODO: This is really just a special case of the above...
|
153
|
-
it 'be able to deflate String bodies' do
|
154
|
-
verify(200, 'Hello world!', deflate_or_gzip) do |status, headers, body|
|
155
|
-
headers.must_equal({
|
156
|
-
'Content-Encoding' => 'gzip',
|
157
|
-
'Vary' => 'Accept-Encoding',
|
158
|
-
'Content-Type' => 'text/plain'
|
159
|
-
})
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
it 'be able to gzip bodies that respond to each' do
|
164
|
-
app_body = Object.new
|
165
|
-
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
166
|
-
|
167
|
-
verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body|
|
168
|
-
headers.must_equal({
|
169
|
-
'Content-Encoding' => 'gzip',
|
170
|
-
'Vary' => 'Accept-Encoding',
|
171
|
-
'Content-Type' => 'text/plain'
|
172
|
-
})
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
it 'flush gzipped chunks to the client as they become ready' do
|
177
|
-
app_body = Object.new
|
178
|
-
class << app_body; def each; yield('foo'); yield('bar'); end; end
|
179
|
-
|
180
|
-
verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body|
|
181
|
-
headers.must_equal({
|
182
|
-
'Content-Encoding' => 'gzip',
|
183
|
-
'Vary' => 'Accept-Encoding',
|
184
|
-
'Content-Type' => 'text/plain'
|
185
|
-
})
|
186
|
-
|
187
|
-
buf = []
|
188
|
-
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
189
|
-
body.each { |part| buf << inflater.inflate(part) }
|
190
|
-
buf << inflater.finish
|
191
|
-
|
192
|
-
buf.delete_if { |part| part.empty? }.join.must_equal 'foobar'
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
it 'be able to fallback to no deflation' do
|
197
|
-
verify(200, 'Hello world!', 'superzip') do |status, headers, body|
|
198
|
-
headers.must_equal({
|
199
|
-
'Vary' => 'Accept-Encoding',
|
200
|
-
'Content-Type' => 'text/plain'
|
201
|
-
})
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
it 'be able to skip when there is no response entity body' do
|
206
|
-
verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body|
|
207
|
-
headers.must_equal({})
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
it 'handle the lack of an acceptable encoding' do
|
212
|
-
app_body = 'Hello world!'
|
213
|
-
not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.'
|
214
|
-
not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.'
|
215
|
-
options1 = {
|
216
|
-
'app_status' => 200,
|
217
|
-
'app_body' => app_body,
|
218
|
-
'request_headers' => {
|
219
|
-
'PATH_INFO' => '/'
|
220
|
-
}
|
221
|
-
}
|
222
|
-
options2 = {
|
223
|
-
'app_status' => 200,
|
224
|
-
'app_body' => app_body,
|
225
|
-
'request_headers' => {
|
226
|
-
'PATH_INFO' => '/foo/bar'
|
227
|
-
}
|
228
|
-
}
|
229
|
-
|
230
|
-
verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body|
|
231
|
-
headers.must_equal({
|
232
|
-
'Content-Type' => 'text/plain',
|
233
|
-
'Content-Length' => not_found_body1.length.to_s
|
234
|
-
})
|
235
|
-
end
|
236
|
-
|
237
|
-
verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body|
|
238
|
-
headers.must_equal({
|
239
|
-
'Content-Type' => 'text/plain',
|
240
|
-
'Content-Length' => not_found_body2.length.to_s
|
241
|
-
})
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
it 'handle gzip response with Last-Modified header' do
|
246
|
-
last_modified = Time.now.httpdate
|
247
|
-
options = {
|
248
|
-
'response_headers' => {
|
249
|
-
'Content-Type' => 'text/plain',
|
250
|
-
'Last-Modified' => last_modified
|
251
|
-
}
|
252
|
-
}
|
253
|
-
|
254
|
-
verify(200, 'Hello World!', 'gzip', options) do |status, headers, body|
|
255
|
-
headers.must_equal({
|
256
|
-
'Content-Encoding' => 'gzip',
|
257
|
-
'Vary' => 'Accept-Encoding',
|
258
|
-
'Last-Modified' => last_modified,
|
259
|
-
'Content-Type' => 'text/plain'
|
260
|
-
})
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
it 'do nothing when no-transform Cache-Control directive present' do
|
265
|
-
options = {
|
266
|
-
'response_headers' => {
|
267
|
-
'Content-Type' => 'text/plain',
|
268
|
-
'Cache-Control' => 'no-transform'
|
269
|
-
}
|
270
|
-
}
|
271
|
-
verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body|
|
272
|
-
headers.wont_include 'Content-Encoding'
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
it 'do nothing when Content-Encoding already present' do
|
277
|
-
options = {
|
278
|
-
'response_headers' => {
|
279
|
-
'Content-Type' => 'text/plain',
|
280
|
-
'Content-Encoding' => 'gzip'
|
281
|
-
}
|
282
|
-
}
|
283
|
-
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
284
|
-
end
|
285
|
-
|
286
|
-
it 'deflate when Content-Encoding is identity' do
|
287
|
-
options = {
|
288
|
-
'response_headers' => {
|
289
|
-
'Content-Type' => 'text/plain',
|
290
|
-
'Content-Encoding' => 'identity'
|
291
|
-
}
|
292
|
-
}
|
293
|
-
verify(200, 'Hello World!', deflate_or_gzip, options)
|
294
|
-
end
|
295
|
-
|
296
|
-
it "deflate if content-type matches :include" do
|
297
|
-
options = {
|
298
|
-
'response_headers' => {
|
299
|
-
'Content-Type' => 'text/plain'
|
300
|
-
},
|
301
|
-
'deflater_options' => {
|
302
|
-
:include => %w(text/plain)
|
303
|
-
}
|
304
|
-
}
|
305
|
-
verify(200, 'Hello World!', 'gzip', options)
|
306
|
-
end
|
307
|
-
|
308
|
-
it "deflate if content-type is included it :include" do
|
309
|
-
options = {
|
310
|
-
'response_headers' => {
|
311
|
-
'Content-Type' => 'text/plain; charset=us-ascii'
|
312
|
-
},
|
313
|
-
'deflater_options' => {
|
314
|
-
:include => %w(text/plain)
|
315
|
-
}
|
316
|
-
}
|
317
|
-
verify(200, 'Hello World!', 'gzip', options)
|
318
|
-
end
|
319
|
-
|
320
|
-
it "not deflate if content-type is not set but given in :include" do
|
321
|
-
options = {
|
322
|
-
'deflater_options' => {
|
323
|
-
:include => %w(text/plain)
|
324
|
-
}
|
325
|
-
}
|
326
|
-
verify(304, 'Hello World!', { 'gzip' => nil }, options)
|
327
|
-
end
|
328
|
-
|
329
|
-
it "not deflate if content-type do not match :include" do
|
330
|
-
options = {
|
331
|
-
'response_headers' => {
|
332
|
-
'Content-Type' => 'text/plain'
|
333
|
-
},
|
334
|
-
'deflater_options' => {
|
335
|
-
:include => %w(text/json)
|
336
|
-
}
|
337
|
-
}
|
338
|
-
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
339
|
-
end
|
340
|
-
|
341
|
-
it "deflate response if :if lambda evaluates to true" do
|
342
|
-
options = {
|
343
|
-
'deflater_options' => {
|
344
|
-
:if => lambda { |env, status, headers, body| true }
|
345
|
-
}
|
346
|
-
}
|
347
|
-
verify(200, 'Hello World!', deflate_or_gzip, options)
|
348
|
-
end
|
349
|
-
|
350
|
-
it "not deflate if :if lambda evaluates to false" do
|
351
|
-
options = {
|
352
|
-
'deflater_options' => {
|
353
|
-
:if => lambda { |env, status, headers, body| false }
|
354
|
-
}
|
355
|
-
}
|
356
|
-
verify(200, 'Hello World!', { 'gzip' => nil }, options)
|
357
|
-
end
|
358
|
-
|
359
|
-
it "check for Content-Length via :if" do
|
360
|
-
response = 'Hello World!'
|
361
|
-
response_len = response.length
|
362
|
-
options = {
|
363
|
-
'response_headers' => {
|
364
|
-
'Content-Length' => response_len.to_s
|
365
|
-
},
|
366
|
-
'deflater_options' => {
|
367
|
-
:if => lambda { |env, status, headers, body|
|
368
|
-
headers['Content-Length'].to_i >= response_len
|
369
|
-
}
|
370
|
-
}
|
371
|
-
}
|
372
|
-
|
373
|
-
verify(200, response, 'gzip', options)
|
374
|
-
end
|
375
|
-
end
|