Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 12 additions & 5 deletions lib/ld-eventsource/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class Client
# request to generate the payload dynamically.
# @param retry_enabled [Boolean] (true) whether to retry connections after failures. If false, the client
# will exit after the first connection failure instead of attempting to reconnect.
# @param http_client_options [Hash] (nil) additional options to pass to
# the HTTP client, such as `socket_factory` or `proxy`. These settings will override
# the socket factory and proxy settings.
# @yieldparam [Client] client the new client instance, before opening the connection
#
def initialize(uri,
Expand All @@ -105,7 +108,8 @@ def initialize(uri,
socket_factory: nil,
method: "GET",
payload: nil,
retry_enabled: true)
retry_enabled: true,
http_client_options: nil)
@uri = URI(uri)
@stopped = Concurrent::AtomicBoolean.new(false)
@retry_enabled = retry_enabled
Expand All @@ -116,9 +120,10 @@ def initialize(uri,
@method = method.to_s.upcase
@payload = payload
@logger = logger || default_logger
http_client_options = {}

base_http_client_options = {}
if socket_factory
http_client_options["socket_class"] = socket_factory
base_http_client_options["socket_class"] = socket_factory
end

if proxy
Expand All @@ -131,13 +136,15 @@ def initialize(uri,
end

if @proxy
http_client_options["proxy"] = {
base_http_client_options["proxy"] = {
:proxy_address => @proxy.host,
:proxy_port => @proxy.port,
}
end

@http_client = HTTP::Client.new(http_client_options)
options = http_client_options.is_a?(Hash) ? base_http_client_options.merge(http_client_options) : base_http_client_options

@http_client = HTTP::Client.new(options)
.follow
.timeout({
read: read_timeout,
Expand Down
149 changes: 149 additions & 0 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,155 @@ def test_object.to_s
end
end

describe "http_client_options precedence" do
it "allows socket_factory to be set via individual parameter" do
mock_socket_factory = double("MockSocketFactory")

with_server do |server|
server.setup_response("/") do |req,res|
send_stream_content(res, "", keep_open: true)
end

# We can't easily test socket creation without actually making a connection,
# but we can verify the options contain the socket_class
client = nil
expect {
client = subject.new(server.base_uri, socket_factory: mock_socket_factory)
}.not_to raise_error

# Access the internal HTTP client to verify socket_class was set
expect(client.instance_variable_get(:@http_client).default_options.socket_class).to eq(mock_socket_factory)

client.close
end
end

it "allows proxy to be set via individual parameter" do
with_server do |server|
server.setup_response("/") do |req,res|
send_stream_content(res, simple_event_1_text, keep_open: false)
end

with_server(StubProxyServer.new) do |proxy|
event_sink = Queue.new
client = subject.new(server.base_uri, proxy: proxy.base_uri) do |c|
c.on_event { |event| event_sink << event }
end

with_client(client) do |c|
expect(event_sink.pop).to eq(simple_event_1)
expect(proxy.request_count).to eq(1)
end
end
end
end

it "allows http_client_options to override socket_factory" do
individual_socket_factory = double("IndividualSocketFactory")
override_socket_factory = double("OverrideSocketFactory")

with_server do |server|
server.setup_response("/") do |req,res|
send_stream_content(res, "", keep_open: true)
end

# http_client_options should take precedence over individual parameter
client = nil
expect {
client = subject.new(server.base_uri,
socket_factory: individual_socket_factory,
http_client_options: {"socket_class" => override_socket_factory})
}.not_to raise_error

# Verify that the override socket factory was used, not the individual one
expect(client.instance_variable_get(:@http_client).default_options.socket_class).to eq(override_socket_factory)

client.close
end
end

it "allows http_client_options to override proxy settings" do
with_server do |server|
server.setup_response("/") do |req,res|
send_stream_content(res, simple_event_1_text, keep_open: false)
end

with_server(StubProxyServer.new) do |individual_proxy|
with_server(StubProxyServer.new) do |override_proxy|
event_sink = Queue.new
client = subject.new(server.base_uri,
proxy: individual_proxy.base_uri,
http_client_options: {"proxy" => {
:proxy_address => override_proxy.base_uri.host,
:proxy_port => override_proxy.base_uri.port,
}}) do |c|
c.on_event { |event| event_sink << event }
end

with_client(client) do |c|
expect(event_sink.pop).to eq(simple_event_1)
# The override proxy should be used, not the individual one
expect(override_proxy.request_count).to eq(1)
expect(individual_proxy.request_count).to eq(0)
end
end
end
end
end

it "merges http_client_options with base options when both socket_factory and other options are provided" do
socket_factory = double("SocketFactory")
ssl_options = { verify_mode: 0 } # OpenSSL::SSL::VERIFY_NONE equivalent

with_server do |server|
server.setup_response("/") do |req,res|
send_stream_content(res, "", keep_open: true)
end

# Should include both socket_factory from individual param and ssl from http_client_options
client = nil
expect {
client = subject.new(server.base_uri,
socket_factory: socket_factory,
http_client_options: {"ssl" => ssl_options})
}.not_to raise_error

# Verify both options are present
http_options = client.instance_variable_get(:@http_client).default_options
expect(http_options.socket_class).to eq(socket_factory)
expect(http_options.ssl).to eq(ssl_options)

client.close
end
end
end

describe "http_client_options SSL pass-through" do
it "passes SSL verification options through http_client_options" do
ssl_options = {
verify_mode: 0, # OpenSSL::SSL::VERIFY_NONE equivalent
verify_hostname: false,
}

with_server do |server|
server.setup_response("/") do |req,res|
send_stream_content(res, "", keep_open: true)
end

client = nil
expect {
client = subject.new(server.base_uri,
http_client_options: {"ssl" => ssl_options})
}.not_to raise_error

# Verify SSL options are passed through
expect(client.instance_variable_get(:@http_client).default_options.ssl).to eq(ssl_options)

client.close
end
end
end

describe "retry parameter" do
it "defaults to true (retries enabled)" do
events_body = simple_event_1_text
Expand Down