165 lines
5.7 KiB
Ruby
165 lines
5.7 KiB
Ruby
require 'minitest/autorun'
|
|
require 'prism'
|
|
require 'support'
|
|
require 'zlib'
|
|
require 'stringio'
|
|
|
|
class MiddlewareCompressionTest < Minitest::Test
|
|
include TestSupport
|
|
|
|
LARGE_BODY = 'x' * 1000
|
|
|
|
def gunzip(bytes)
|
|
Zlib::GzipReader.new(StringIO.new(bytes)).read
|
|
end
|
|
|
|
def app_returning(status, headers, body)
|
|
Prism::Middleware::Compression.new(lambda { |_env| [status, headers, body] })
|
|
end
|
|
|
|
def test_compresses_a_large_buffered_body_when_client_accepts_gzip
|
|
app = app_returning(200, {'Content-Type' => 'text/html'}, [LARGE_BODY])
|
|
env = env_for('GET', '/posts', headers: {'Accept-Encoding' => 'gzip'})
|
|
status, headers, body = app.call(env)
|
|
|
|
assert_equal 200, status
|
|
assert_equal 'gzip', headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, gunzip(body.join)
|
|
assert_equal body.join.bytesize.to_s, headers['Content-Length']
|
|
end
|
|
|
|
def test_leaves_the_body_alone_when_the_client_sends_no_accept_encoding
|
|
app = app_returning(200, {'Content-Type' => 'text/html'}, [LARGE_BODY])
|
|
status, headers, body = app.call(env_for('GET', '/posts'))
|
|
|
|
assert_equal 200, status
|
|
assert_nil headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, body.join
|
|
end
|
|
|
|
def test_respects_an_explicit_gzip_q0_declining_the_encoding
|
|
app = app_returning(200, {'Content-Type' => 'text/html'}, [LARGE_BODY])
|
|
env = env_for('GET', '/posts', headers: {'Accept-Encoding' => 'gzip;q=0, *;q=1'})
|
|
_, headers, body = app.call(env)
|
|
|
|
assert_nil headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, body.join
|
|
end
|
|
|
|
def test_a_wildcard_accept_encoding_is_honored
|
|
app = app_returning(200, {'Content-Type' => 'text/html'}, [LARGE_BODY])
|
|
env = env_for('GET', '/posts', headers: {'Accept-Encoding' => '*'})
|
|
_, headers, body = app.call(env)
|
|
|
|
assert_equal 'gzip', headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, gunzip(body.join)
|
|
end
|
|
|
|
def test_skips_bodies_smaller_than_the_threshold
|
|
app = app_returning(200, {'Content-Type' => 'text/html'}, ['tiny'])
|
|
env = env_for('GET', '/posts', headers: {'Accept-Encoding' => 'gzip'})
|
|
_, headers, body = app.call(env)
|
|
|
|
assert_nil headers['Content-Encoding']
|
|
assert_equal 'tiny', body.join
|
|
end
|
|
|
|
def test_skips_a_response_that_already_carries_a_content_encoding
|
|
headers = {'Content-Type' => 'text/html', 'Content-Encoding' => 'br'}
|
|
app = app_returning(200, headers, [LARGE_BODY])
|
|
env = env_for('GET', '/posts', headers: {'Accept-Encoding' => 'gzip'})
|
|
_, out_headers, body = app.call(env)
|
|
|
|
assert_equal 'br', out_headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, body.join
|
|
end
|
|
|
|
def test_skips_media_content_types
|
|
app = app_returning(200, {'Content-Type' => 'image/png'}, [LARGE_BODY])
|
|
env = env_for('GET', '/logo.png', headers: {'Accept-Encoding' => 'gzip'})
|
|
_, headers, body = app.call(env)
|
|
|
|
assert_nil headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, body.join
|
|
end
|
|
|
|
def test_skips_a_partial_content_range_response
|
|
headers = {'Content-Type' => 'text/plain', 'Content-Range' => 'bytes 0-99/1000'}
|
|
app = app_returning(206, headers, [LARGE_BODY[0, 100]])
|
|
env = env_for('GET', '/file', headers: {'Accept-Encoding' => 'gzip'})
|
|
status, out_headers, = app.call(env)
|
|
|
|
assert_equal 206, status
|
|
assert_nil out_headers['Content-Encoding']
|
|
end
|
|
|
|
def test_skips_a_304_not_modified_response
|
|
app = app_returning(304, {}, [])
|
|
env = env_for('GET', '/file', headers: {'Accept-Encoding' => 'gzip'})
|
|
status, headers, = app.call(env)
|
|
|
|
assert_equal 304, status
|
|
assert_nil headers['Content-Encoding']
|
|
end
|
|
|
|
def test_skips_head_requests
|
|
app = app_returning(200, {'Content-Type' => 'text/html'}, [LARGE_BODY])
|
|
env = env_for('HEAD', '/posts', headers: {'Accept-Encoding' => 'gzip'})
|
|
_, headers, body = app.call(env)
|
|
|
|
assert_nil headers['Content-Encoding']
|
|
assert_equal LARGE_BODY, body.join
|
|
end
|
|
|
|
def test_appends_to_an_existing_vary_header_instead_of_replacing_it
|
|
app = app_returning(200, {'Content-Type' => 'text/html', 'Vary' => 'Accept'}, [LARGE_BODY])
|
|
env = env_for('GET', '/posts', headers: {'Accept-Encoding' => 'gzip'})
|
|
_, headers, = app.call(env)
|
|
|
|
assert_equal 'Accept, Accept-Encoding', headers['Vary']
|
|
end
|
|
|
|
def test_streams_a_streaming_body_as_a_single_gzip_stream_chunk_by_chunk
|
|
written_before_producer_returns = nil
|
|
downstream = lambda do |_env|
|
|
body = Prism::StreamingBody.new do |writer|
|
|
writer.write("id,title\n")
|
|
writer.write("1,one\n")
|
|
writer.write("2,two\n")
|
|
end
|
|
[200, {'Content-Type' => 'text/csv'}, body]
|
|
end
|
|
app = Prism::Middleware::Compression.new(downstream)
|
|
env = env_for('GET', '/posts/export', headers: {'Accept-Encoding' => 'gzip'})
|
|
status, headers, body = app.call(env)
|
|
|
|
assert_equal 200, status
|
|
assert_equal 'gzip', headers['Content-Encoding']
|
|
assert_nil headers['Content-Length']
|
|
assert_kind_of Prism::StreamingBody, body
|
|
|
|
chunks = []
|
|
body.each { |chunk| chunks << chunk }
|
|
assert chunks.size > 1, 'expected more than one gzip chunk (one per producer write)'
|
|
assert_equal "id,title\n1,one\n2,two\n", gunzip(chunks.join)
|
|
end
|
|
|
|
def test_a_second_each_on_the_wrapped_streaming_body_reruns_the_producer_with_a_fresh_gzip_stream
|
|
downstream = lambda do |_env|
|
|
body = Prism::StreamingBody.new { |writer| writer.write('hello world') }
|
|
[200, {'Content-Type' => 'text/plain'}, body]
|
|
end
|
|
app = Prism::Middleware::Compression.new(downstream)
|
|
env = env_for('GET', '/x', headers: {'Accept-Encoding' => 'gzip'})
|
|
_, _, body = app.call(env)
|
|
|
|
first = +''
|
|
body.each { |chunk| first << chunk }
|
|
second = +''
|
|
body.each { |chunk| second << chunk }
|
|
|
|
assert_equal 'hello world', gunzip(first)
|
|
assert_equal 'hello world', gunzip(second)
|
|
end
|
|
end
|