Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

333 lines (292 sloc) 10.353 kb
require 'enumerator'
require 'stringio'
require 'time' # for Time#httpdate
require 'rack/deflater'
require 'rack/lint'
require 'rack/mock'
require 'zlib'
describe Rack::Deflater do
::Enumerator = ::Enumerable::Enumerator unless Object.const_defined?(:Enumerator)
def deflater(app, options = {})
Rack::Lint.new Rack::Deflater.new(app, options)
end
def build_response(status, body, accept_encoding, options = {})
body = [body] if body.respond_to? :to_str
app = lambda do |env|
res = [status, options['response_headers'] || {}, body]
res[1]["Content-Type"] = "text/plain" unless res[0] == 304
res
end
request = Rack::MockRequest.env_for("", (options['request_headers'] || {}).merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
deflater(app, options['deflater_options'] || {}).call(request)
end
def inflate(buf)
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
inflater.inflate(buf) << inflater.finish
end
##
# Constructs response object and verifies if it yields right results
#
# [expected_status] expected response status, e.g. 200, 304
# [expected_body] expected response body
# [accept_encoing] what Accept-Encoding header to send and expect, e.g.
# 'deflate' - accepts and expects deflate encoding in response
# { 'gzip' => nil } - accepts gzip but expects no encoding in response
# [options] hash of request options, i.e.
# 'app_status' - what status dummy app should return (may be changed by deflater at some point)
# 'app_body' - what body dummy app should return (may be changed by deflater at some point)
# 'request_headers' - extra reqest headers to be sent
# 'response_headers' - extra response headers to be returned
# 'deflater_options' - options passed to deflater middleware
# [block] useful for doing some extra verification
def verify(expected_status, expected_body, accept_encoding, options = {}, &block)
accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash)
[accept_encoding.keys.first, accept_encoding.values.first]
else
[accept_encoding, accept_encoding.dup]
end
# build response
status, headers, body = build_response(
options['app_status'] || expected_status,
options['app_body'] || expected_body,
accept_encoding,
options
)
# verify status
status.should.equal(expected_status)
# verify headers
yield(headers) if block_given?
# verify body
body_text = ''
body.each { |part| body_text << part }
deflated_body = case expected_encoding
when 'deflate'
inflate(body_text)
when 'gzip'
io = StringIO.new(body_text)
gz = Zlib::GzipReader.new(io)
tmp = gz.read
gz.close
tmp
else
body_text
end
deflated_body.should.equal(expected_body)
end
should "be able to deflate bodies that respond to each" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
verify(200, 'foobar', 'deflate', { 'app_body' => body }) do |headers|
headers.should.equal({
"Content-Encoding" => "deflate",
"Vary" => "Accept-Encoding",
"Content-Type" => "text/plain"
})
end
end
should "flush deflated chunks to the client as they become ready" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
response = build_response(200, body, "deflate")
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "deflate",
"Vary" => "Accept-Encoding",
"Content-Type" => "text/plain"
})
buf = []
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
response[2].each { |part| buf << inflater.inflate(part) }
buf << inflater.finish
buf.delete_if { |part| part.empty? }
buf.join.should.equal("foobar")
end
# TODO: This is really just a special case of the above...
should "be able to deflate String bodies" do
verify(200, "Hello world!", 'deflate') do |headers|
headers.should.equal({
"Content-Encoding" => "deflate",
"Vary" => "Accept-Encoding",
"Content-Type" => "text/plain"
})
end
end
should "be able to gzip bodies that respond to each" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
verify(200, "foobar", 'gzip', { 'app_body' => body }) do |headers|
headers.should.equal({
"Content-Encoding" => "gzip",
"Vary" => "Accept-Encoding",
"Content-Type" => "text/plain"
})
end
end
should "flush gzipped chunks to the client as they become ready" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
response = build_response(200, body, "gzip")
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "gzip",
"Vary" => "Accept-Encoding",
"Content-Type" => "text/plain"
})
buf = []
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
response[2].each { |part| buf << inflater.inflate(part) }
buf << inflater.finish
buf.delete_if { |part| part.empty? }
buf.join.should.equal("foobar")
end
should "be able to fallback to no deflation" do
verify(200, 'Hello world!', 'superzip') do |headers|
headers.should.equal({
"Vary" => "Accept-Encoding",
"Content-Type" => "text/plain"
})
end
end
should "be able to skip when there is no response entity body" do
verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |headers|
headers.should.equal({})
end
end
should "handle the lack of an acceptable encoding" do
body = "Hello world!"
not_found_body1 = "An acceptable encoding for the requested resource / could not be found."
not_found_body2 = "An acceptable encoding for the requested resource /foo/bar could not be found."
verify(406, not_found_body1, 'identity;q=0', { 'app_status' => 200, 'app_body' => body, 'request_headers' => { "PATH_INFO" => "/" } }) do |headers|
headers.should.equal({"Content-Type" => "text/plain", "Content-Length" => not_found_body1.length.to_s})
end
verify(406, not_found_body2, 'identity;q=0', { 'app_status' => 200, 'app_body' => body, 'request_headers' => { "PATH_INFO" => "/foo/bar" } }) do |headers|
headers.should.equal({"Content-Type" => "text/plain", "Content-Length" => not_found_body2.length.to_s})
end
end
should "handle gzip response with Last-Modified header" do
last_modified = Time.now.httpdate
verify(200, 'Hello World!', 'gzip', { 'response_headers' => { "Content-Type" => "text/plain", "Last-Modified" => last_modified } }) do |headers|
headers.should.equal({
"Content-Encoding" => "gzip",
"Vary" => "Accept-Encoding",
"Last-Modified" => last_modified,
"Content-Type" => "text/plain"
})
end
end
should "do nothing when no-transform Cache-Control directive present" do
options = {
'response_headers' => {
'Content-Type' => 'text/plain',
'Cache-Control' => 'no-transform'
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options) do |headers|
headers.should.not.include "Content-Encoding"
end
end
should "do nothing when Content-Encoding already present" do
options = {
'response_headers' => {
'Content-Type' => 'text/plain',
'Content-Encoding' => 'gzip'
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options)
end
should "deflate when Content-Encoding is identity" do
options = {
'response_headers' => {
'Content-Type' => 'text/plain',
'Content-Encoding' => 'identity'
}
}
verify(200, 'Hello World!', 'deflate', options)
end
should "do nothing if body length is less than a given threshold" do
body = 'Hello World!'
options = {
'response_headers' => {
'Content-Type' => 'text/plain',
'Content-Length' => body.length.to_s
},
'deflater_options' => {
'min_content_length' => body.length + 1
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options)
end
should "do nothing if Content-Length is not given" do
body = 'Hello World!'
options = {
'response_headers' => {
'Content-Type' => 'text/plain'
},
'deflater_options' => {
'min_content_length' => body.length + 1
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options)
end
should "gzip response if body length is equal or longer than a given threshold" do
body = 'Hello World!'
options = {
'response_headers' => {
'Content-Type' => 'text/plain',
'Content-Length' => body.length.to_s
},
'deflater_options' => {
'min_content_length' => body.length
}
}
verify(200, 'Hello World!', 'gzip', options)
end
should "process if path matches :include" do
options = {
'deflater_options' => {
'include' => /^\/$/
}
}
verify(200, 'Hello World!', 'gzip', options)
end
should "skip processing if path do not match :include" do
options = {
'deflater_options' => {
'include' => /^\/foo\/bar$/
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options)
end
should "skip processing if path matches :exclude" do
options = {
'deflater_options' => {
'exclude' => /^\/$/
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options)
end
should "gzip response if path do not match :exclude" do
options = {
'deflater_options' => {
'exclude' => /^sth$/
}
}
verify(200, 'Hello World!', 'gzip', options)
end
should "do nothing if :skip_if lambda evaluates to true" do
options = {
'deflater_options' => {
'skip_if' => lambda { |env, status, header, body| true }
}
}
verify(200, 'Hello World!', { 'gzip' => nil }, options)
end
should "deflate response if :skip_if lambda evaluates to false" do
options = {
'deflater_options' => {
'skip_if' => lambda { |env, status, header, body| false }
}
}
verify(200, 'Hello World!', 'deflate', options)
end
end
Jump to Line
Something went wrong with that request. Please try again.