diff --git a/lib/faraday_middleware.rb b/lib/faraday_middleware.rb index 6aea3417..49261a59 100644 --- a/lib/faraday_middleware.rb +++ b/lib/faraday_middleware.rb @@ -17,6 +17,7 @@ module FaradayMiddleware autoload :RackCompatible, 'faraday_middleware/rack_compatible' autoload :FollowRedirects, 'faraday_middleware/response/follow_redirects' autoload :Instrumentation, 'faraday_middleware/instrumentation' + autoload :Gzip, 'faraday_middleware/gzip' if Faraday.respond_to? :register_middleware Faraday.register_middleware :request, @@ -39,7 +40,8 @@ module FaradayMiddleware :chunked => lambda { Chunked } Faraday.register_middleware \ - :instrumentation => lambda { Instrumentation } + :instrumentation => lambda { Instrumentation }, + :gzip => lambda { Gzip } end end diff --git a/lib/faraday_middleware/gzip.rb b/lib/faraday_middleware/gzip.rb new file mode 100644 index 00000000..39e9e6c2 --- /dev/null +++ b/lib/faraday_middleware/gzip.rb @@ -0,0 +1,45 @@ +require 'faraday' + +module FaradayMiddleware + # A middleware that ensures that the client requests are sent with the + # headers that encourage servers to send compressed data, and then uncompresses it. + # The Content-Length will reflect the actual body length. + class Gzip < Faraday::Middleware + dependency 'zlib' + + ACCEPT_ENCODING = 'Accept-Encoding'.freeze + ENCODINGS = 'gzip,deflate'.freeze + + def initialize(app, options = nil) + @app = app + end + + def call(env) + (env[:request_headers] ||= {})[ACCEPT_ENCODING] = ENCODINGS + @app.call(env).on_complete do |env| + encoding = env[:response_headers]['content-encoding'].to_s.downcase + if %w[gzip deflate].include?(encoding) + case encoding + when 'gzip' + env[:body] = uncompress_gzip(env[:body]) + when 'deflate' + env[:body] = Zlib::Inflate.inflate(env[:body]) + end + env[:response_headers].delete('content-encoding') + env[:response_headers]['content-length'] = env[:body].length + end + end + end + + private + def uncompress_gzip(body) + io = StringIO.new(body) + gzip_reader = if '1.9'.respond_to?(:force_encoding) + Zlib::GzipReader.new(io, :encoding => 'ASCII-8BIT') + else + Zlib::GzipReader.new(io) + end + gzip_reader.read + end + end +end diff --git a/spec/gzip_spec.rb b/spec/gzip_spec.rb new file mode 100644 index 00000000..9b7f0493 --- /dev/null +++ b/spec/gzip_spec.rb @@ -0,0 +1,67 @@ +require 'helper' +require 'faraday_middleware/gzip' + +describe FaradayMiddleware::Gzip do + + context 'request' do + let(:middleware) { described_class.new(lambda {|env| Faraday::Response.new(env)}) } + + it 'sets the Accept-Encoding header' do + headers = Faraday::Utils::Headers.new + env = {:body => nil, :request_headers => headers, :response_headers => Faraday::Utils::Headers.new} + expect { middleware.call(env) }.to change { + headers['Accept-Encoding'] + }.to('gzip,deflate') + end + end + + context 'response', :type => :response do + let(:uncompressed_body) { + "RspecHello, spec!" + } + + shared_examples 'compressed response' do + it 'uncompresses the body' do + expect(process(body).body).to eq(uncompressed_body) + end + + it 'sets the Content-Length' do + expect(process(body).headers['Content-Length']).to eq(uncompressed_body.length) + end + + it 'removes the Content-Encoding' do + expect(process(body).headers['Content-Encoding']).to be_nil + end + end + + context 'gzipped response' do + let(:body) do + f = StringIO.new + gz = Zlib::GzipWriter.new(f) + gz.write(uncompressed_body) + gz.close + res = f.string + res.force_encoding('BINARY') if res.respond_to?(:force_encoding) + res + end + let(:headers) { {'Content-Encoding' => 'gzip', 'Content-Length' => body.length} } + + it_behaves_like 'compressed response' + end + + context 'deflated response' do + let(:body) { Zlib::Deflate.deflate(uncompressed_body) } + let(:headers) { {'Content-Encoding' => 'deflate', 'Content-Length' => body.length} } + + it_behaves_like 'compressed response' + end + + context 'identity response' do + let(:body) { uncompressed_body } + + it 'does not modify the body' do + expect(process(body).body).to eq(uncompressed_body) + end + end + end +end