Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ gemspec

group :development, :test do
gem "climate_control", "~> 0.1"
gem "rack", "~> 3.1"
gem "rack-proxy", "~> 0.7.7"
gem "rackup", "~> 2.2"
gem "rake", "~> 12"
gem "rspec", "~> 3"
gem "rubocop", "~> 1.64.1"
gem "rubocop-rspec", "~> 2.31.0"
gem "toxiproxy", "~> 1.0"
gem "vcr", "~> 5.0"
gem "webmock", "~> 3.7"
gem "webrick", "~> 1.9"
end
4 changes: 3 additions & 1 deletion lib/twingly/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ class Client # rubocop:disable Metrics/ClassLength
attr_accessor :logger
attr_accessor :retryable_exceptions

def initialize(base_user_agent:, logger: default_logger, user_agent: nil)
def initialize(base_user_agent:, logger: default_logger, user_agent: nil, proxy: nil)
@base_user_agent = base_user_agent
@logger = logger
@user_agent = user_agent
@proxy = proxy

initialize_defaults
end
Expand Down Expand Up @@ -212,6 +213,7 @@ def create_http_client # rubocop:disable Metrics/MethodLength
max_size_bytes: @max_response_body_size_bytes
faraday.adapter Faraday.default_adapter
faraday.headers[:user_agent] = user_agent
faraday.proxy = @proxy if @proxy
end
end

Expand Down
37 changes: 36 additions & 1 deletion spec/lib/twingly/http_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative "../../spec_help/http_test_server"

class CustomError < StandardError; end

# rubocop:disable RSpec/MultipleMemoizedHelpers
Expand Down Expand Up @@ -533,6 +535,35 @@ class CustomError < StandardError; end
end
end

RSpec.shared_examples "verifies proxy functionality" do
context "when a proxy is provided" do
let(:proxy_pid_and_url) { HttpTestServer.spawn("proxy_server") }
let(:proxy_pid) { proxy_pid_and_url[0] }
let(:proxy_url) { proxy_pid_and_url[1] }
let(:target_pid_and_url) { HttpTestServer.spawn("echoed_headers_in_body") }
let(:target_pid) { target_pid_and_url[0] }
let(:target_url) { target_pid_and_url[1] }
Comment thread
cequele marked this conversation as resolved.
Outdated
let(:client) do
described_class.new(
base_user_agent: base_user_agent,
proxy: proxy_url
)
end
let(:url) { target_url }

after do
HttpTestServer.stop(proxy_pid)
HttpTestServer.stop(target_pid)
end

it "routes requests through the proxy", vcr: false do
with_real_http_connections do
expect(response.fetch(:body)).to include("HTTP_X_PROXIED_BY")
end
end
end
end

describe "#initialize" do
context "when no logger is given" do
subject(:default_logger) do
Expand All @@ -552,6 +583,7 @@ class CustomError < StandardError; end

describe "#post", vcr: Fixture.post_example_org do
include_examples "common HTTP behaviour for", :post, "example.org"
include_examples "verifies proxy functionality"

let(:post_body) { nil }
let(:post_headers) { {} }
Expand Down Expand Up @@ -600,7 +632,7 @@ class CustomError < StandardError; end

describe "#get", vcr: Fixture.example_org do
include_examples "common HTTP behaviour for", :get, "example.org"

include_examples "verifies proxy functionality"
Comment thread
cequele marked this conversation as resolved.
let(:request_response) do
client.get(url)
end
Expand Down Expand Up @@ -692,6 +724,7 @@ class CustomError < StandardError; end

describe "#put", vcr: Fixture.put_httpbin_org do
include_examples "common HTTP behaviour for", :put, "https://httpbin.org/put"
include_examples "verifies proxy functionality"

let(:url) { "https://httpbin.org/put" }

Expand Down Expand Up @@ -742,6 +775,7 @@ class CustomError < StandardError; end

describe "#patch", vcr: Fixture.patch_httpbin_org do
include_examples "common HTTP behaviour for", :patch, "https://httpbin.org/patch"
include_examples "verifies proxy functionality"

let(:url) { "https://httpbin.org/patch" }

Expand Down Expand Up @@ -792,6 +826,7 @@ class CustomError < StandardError; end

describe "#delete", vcr: Fixture.delete_httpbin_org do
include_examples "common HTTP behaviour for", :delete, "https://httpbin.org/delete"
include_examples "verifies proxy functionality"

let(:url) { "https://httpbin.org/delete" }

Expand Down
9 changes: 9 additions & 0 deletions spec/rack_servers/echoed_headers_in_body.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require "json"

run lambda { |env|
request_headers = env.select { |k, _v| k.start_with? "HTTP_" }

[200, { "content-type" => "application/json" }, [request_headers.to_json]]
}
12 changes: 12 additions & 0 deletions spec/rack_servers/proxy_server.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require "rack/proxy"

class TestProxy < Rack::Proxy
def rewrite_env(env)
env["HTTP_X_PROXIED_BY"] = "test-proxy"
env
end
end

run TestProxy.new
15 changes: 15 additions & 0 deletions spec/spec_help/http_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module HttpHelpers
def with_real_http_connections
original_cassette = VCR.eject_cassette
VCR.turn_off!
WebMock.allow_net_connect!

yield
ensure
WebMock.disable_net_connect!
VCR.turn_on!
VCR.insert_cassette(original_cassette.name) if original_cassette
end
end
40 changes: 40 additions & 0 deletions spec/spec_help/http_test_server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require "timeout"

module HttpTestServer
module_function

def spawn(server_name, env: {}) # rubocop:disable Metrics/MethodLength
ip_address = PortProber.localhost
port = PortProber.random(ip_address)
url = "http://#{ip_address}:#{port}"
server = "spec/rack_servers/#{server_name}.ru"
command = "bundle exec rackup --quiet --port #{port} #{server}"

puts "starting HTTP test server: #{command}"
pid = fork do
$stdout.reopen File::NULL
$stderr.reopen File::NULL
exec env, command
end

Timeout.timeout(10.0) do
sleep 0.05 until started?(pid) && PortProber.port_open?(ip_address, port)
end

[pid, url]
end

def stop(pid)
Process.kill(:TERM, pid)
Process.wait(pid)
end

def started?(pid)
Process.getpgid(pid)
true
rescue Errno::ESRCH
false
end
end
37 changes: 37 additions & 0 deletions spec/spec_help/port_prober.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require "socket"
require "timeout"

module PortProber
module_function

def random(host)
server = TCPServer.new(host, 0)
port = server.addr[1]

port
ensure
server&.close
end

def port_open?(ip_address, port)
Timeout.timeout(0.5) do
TCPSocket.new(ip_address, port).close
true
end
rescue StandardError
false
end

def localhost
info = Socket.getaddrinfo("localhost",
80,
Socket::AF_INET,
Socket::SOCK_STREAM)

raise "unable to translate 'localhost' for TCP + IPv4" if info.empty?

info[0][3]
end
end
3 changes: 3 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

require_relative "spec_help/env_helper"
require_relative "spec_help/fixture"
require_relative "spec_help/http_helpers"
require_relative "spec_help/test_logger"
require_relative "spec_help/toxiproxy_config"
require_relative "spec_help/port_prober"

# Start with a clean slate, destroy all proxies if any
Toxiproxy.all.destroy
Expand All @@ -33,6 +35,7 @@

RSpec.configure do |conf|
conf.include EnvHelper
conf.include HttpHelpers

conf.after(:suite) do
Toxiproxy.all.destroy # Be nice, end with a clean slate
Expand Down
Loading