diff --git a/getstream-ruby.gemspec b/getstream-ruby.gemspec index 7efee36..b686306 100644 --- a/getstream-ruby.gemspec +++ b/getstream-ruby.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'dotenv', '>= 2.0' spec.add_dependency 'faraday', '~> 2.0' + spec.add_dependency 'faraday-gzip', '~> 3.0' spec.add_dependency 'faraday-multipart', '~> 1.0' spec.add_dependency 'faraday-retry', '~> 2.0' spec.add_dependency 'json', '~> 2.0' diff --git a/lib/getstream_ruby/client.rb b/lib/getstream_ruby/client.rb index 68a7398..40a64b4 100644 --- a/lib/getstream_ruby/client.rb +++ b/lib/getstream_ruby/client.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'faraday' +require 'faraday/gzip' require 'faraday/retry' require 'faraday/multipart' require 'json' @@ -177,6 +178,8 @@ def build_connection backoff_factor: 2, } conn.response :json, content_type: /\bjson$/ + # :gzip must come after :json (Faraday runs response middleware in reverse). + conn.request :gzip conn.headers['Connection'] = 'keep-alive' if @configuration.connection_keep_alive configure_adapter(conn) conn.options.timeout = @configuration.timeout diff --git a/spec/gzip_spec.rb b/spec/gzip_spec.rb new file mode 100644 index 0000000..214f3e6 --- /dev/null +++ b/spec/gzip_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require 'faraday' +require 'json' +require 'stringio' +require 'zlib' + +# Verify the SDK's default Faraday connection advertises Accept-Encoding: gzip +# and transparently decodes a gzip-encoded JSON response body. +RSpec.describe 'Gzip request/response handling' do + + let(:stubs) { Faraday::Adapter::Test::Stubs.new } + let(:client) do + + c = GetStreamRuby.manual( + api_key: 'test_key', + api_secret: 'test_secret', + base_url: 'https://chat.stream-io-api.test', + ) + # Replace the production connection with one that wires the same middleware + # stack but uses Faraday's :test adapter so we can stub outgoing requests. + # We mirror the production middleware order from Client#build_connection so + # this spec exercises the real gzip-request middleware. + test_conn = Faraday.new(url: c.configuration.base_url) do |conn| + + conn.request :multipart + conn.response :json, content_type: /\bjson$/ + conn.request :gzip + conn.adapter :test, stubs + + end + c.instance_variable_set(:@connection, test_conn) + c + + end + + def gzip(payload) + io = StringIO.new + io.set_encoding('ASCII-8BIT') + gz = Zlib::GzipWriter.new(io) + gz.write(payload) + gz.close + io.string + end + + it 'sends an Accept-Encoding header that advertises gzip' do + + captured_request = nil + stubs.post('/some-path') do |env| + + captured_request = env + [200, { 'Content-Type' => 'application/json' }, '{}'] + + end + + client.post('/some-path', { hello: 'world' }) + + expect(captured_request).not_to be_nil + expect(captured_request.request_headers['Accept-Encoding']).to include('gzip') + + end + + it 'transparently decodes a gzipped JSON response body' do + + expected_body = { 'message' => 'decoded', 'count' => 42 } + compressed = gzip(expected_body.to_json) + + stubs.post('/echo') do + + [ + 200, + { + 'Content-Type' => 'application/json', + 'Content-Encoding' => 'gzip', + }, + compressed, + ] + + end + + response = client.post('/echo', {}) + + expect(response.to_h).to eq(expected_body) + expect(response.message).to eq('decoded') + expect(response.count).to eq(42) + + end + +end