From a4189de1de1e9262c49a9b6d0d1b34056e1d7620 Mon Sep 17 00:00:00 2001 From: Renato Arruda Date: Thu, 7 Mar 2024 15:17:57 +0100 Subject: [PATCH] feat: allow payload compression in POST requests - only done if custom_http_headers config includes {'Content-Encoding' => 'gzip'} - only allow gzip. - 'Content-Encoding' header will get dropped in GET requests. - undocumented feature. --- lib/unleash/util/http.rb | 14 ++++- spec/unleash/client_spec.rb | 104 ++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/lib/unleash/util/http.rb b/lib/unleash/util/http.rb index 57c2e34d..0391b677 100644 --- a/lib/unleash/util/http.rb +++ b/lib/unleash/util/http.rb @@ -1,5 +1,7 @@ require 'net/http' require 'uri' +require 'zlib' +require 'stringio' module Unleash module Util @@ -8,6 +10,7 @@ def self.get(uri, etag = nil, headers_override = nil) http = http_connection(uri) request = Net::HTTP::Get.new(uri.request_uri, http_headers(etag, headers_override)) + request.delete('Content-Encoding') http.request(request) end @@ -16,7 +19,15 @@ def self.post(uri, body) http = http_connection(uri) request = Net::HTTP::Post.new(uri.request_uri, http_headers) - request.body = body + request_body = + if request['Content-Encoding'] == 'gzip' + request_body_writer = Zlib::GzipWriter.new(StringIO.new) + request_body_writer << body + request_body_writer.close.string + else + body + end + request.body = request_body http.request(request) end @@ -32,6 +43,7 @@ def self.http_connection(uri) # @param etag [String, nil] # @param headers_override [Hash, nil] + # @return [Hash] def self.http_headers(etag = nil, headers_override = nil) Unleash.logger.debug "ETag: #{etag}" unless etag.nil? diff --git a/spec/unleash/client_spec.rb b/spec/unleash/client_spec.rb index 814efbe1..446a77d8 100644 --- a/spec/unleash/client_spec.rb +++ b/spec/unleash/client_spec.rb @@ -1,3 +1,6 @@ +require 'stringio' +require 'zlib' + RSpec.describe Unleash::Client do after do WebMock.reset! @@ -108,6 +111,107 @@ ).to have_been_made.once end + it "The compress http header compresses post requests when initializing client" do + WebMock.stub_request(:post, "http://test-url/client/register") + .with( + headers: { + 'Accept' => '*/*', + 'Content-Type' => 'application/json', + 'Content-Encoding' => 'gzip', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'User-Agent' => 'Ruby', + 'X-Api-Key' => '123' + } + ) + .to_return(status: 200, body: "", headers: {}) + WebMock.stub_request(:post, "http://test-url/client/metrics") + .with( + headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'Content-Type' => 'application/json', + 'Content-Encoding' => 'gzip', + 'User-Agent' => 'Ruby' + } + ) + .to_return(status: 200, body: "", headers: {}) + + simple_features = { + "version": 1, + "features": [ + { + "name": "Feature.A", + "description": "Enabled toggle", + "enabled": true, + "strategies": [{ "name": "default" }] + } + ] + } + WebMock.stub_request(:get, "http://test-url/client/features") + .with( + headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'Content-Type' => 'application/json', + 'Unleash-Appname' => 'my-test-app', + 'Unleash-Instanceid' => 'rspec/test', + 'User-Agent' => 'Ruby', + 'X-Api-Key' => '123' + } + ) + .to_return(status: 200, body: simple_features.to_json, headers: {}) + + unleash_client = Unleash::Client.new( + url: 'http://test-url/', + app_name: 'my-test-app', + instance_id: 'rspec/test', + custom_http_headers: { 'X-API-KEY' => '123', 'Content-Encoding' => 'gzip' } + ) + + expect(unleash_client).to be_a(Unleash::Client) + + expect( + a_request(:post, "http://test-url/client/register") + .with(headers: { + 'Content-Type': 'application/json', + 'Content-Encoding': 'gzip', + 'X-API-KEY': '123', + 'UNLEASH-APPNAME': 'my-test-app', + 'UNLEASH-INSTANCEID': 'rspec/test' + }) + ).to have_been_made.once + + expect( + a_request(:get, "http://test-url/client/features") + .with(headers: { + 'Content-Type': 'application/json', + 'X-API-KEY': '123', + 'UNLEASH-APPNAME': 'my-test-app', + 'UNLEASH-INSTANCEID': 'rspec/test' + }) + ).to have_been_made.once + + # Test now sending of metrics + # Sending metrics, if they have been evaluated: + unleash_client.is_enabled?("Feature.A") + unleash_client.get_variant("Feature.A") + Unleash.reporter.post + expect( + a_request(:post, "http://test-url/client/metrics") + .with(headers: { + 'Content-Type': 'application/json', + 'Content-Encoding': 'gzip', + 'X-API-KEY': '123', + 'UNLEASH-APPNAME': 'my-test-app', + 'UNLEASH-INSTANCEID': 'rspec/test' + }) + .with do |request| + uncompressed_request_body = Zlib::GzipReader.wrap(StringIO.new(request.body), &:read) + JSON.parse(uncompressed_request_body)['bucket']['toggles']['Feature.A']['yes'] == 2 + end + ).to have_been_made.once + end + it "should load/use correct variants from the unleash server" do WebMock.stub_request(:post, "http://test-url/client/register") .with(