Skip to content

Commit 1d9b31c

Browse files
keelerm84saada
andauthored
feat: Allow providing HTTP client option settings (#68)
Co-authored-by: saada <moodysaada@surgehq.ai>
1 parent f272b90 commit 1d9b31c

File tree

2 files changed

+161
-5
lines changed

2 files changed

+161
-5
lines changed

lib/ld-eventsource/client.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ class Client
9191
# request to generate the payload dynamically.
9292
# @param retry_enabled [Boolean] (true) whether to retry connections after failures. If false, the client
9393
# will exit after the first connection failure instead of attempting to reconnect.
94+
# @param http_client_options [Hash] (nil) additional options to pass to
95+
# the HTTP client, such as `socket_factory` or `proxy`. These settings will override
96+
# the socket factory and proxy settings.
9497
# @yieldparam [Client] client the new client instance, before opening the connection
9598
#
9699
def initialize(uri,
@@ -105,7 +108,8 @@ def initialize(uri,
105108
socket_factory: nil,
106109
method: "GET",
107110
payload: nil,
108-
retry_enabled: true)
111+
retry_enabled: true,
112+
http_client_options: nil)
109113
@uri = URI(uri)
110114
@stopped = Concurrent::AtomicBoolean.new(false)
111115
@retry_enabled = retry_enabled
@@ -116,9 +120,10 @@ def initialize(uri,
116120
@method = method.to_s.upcase
117121
@payload = payload
118122
@logger = logger || default_logger
119-
http_client_options = {}
123+
124+
base_http_client_options = {}
120125
if socket_factory
121-
http_client_options["socket_class"] = socket_factory
126+
base_http_client_options["socket_class"] = socket_factory
122127
end
123128

124129
if proxy
@@ -131,13 +136,15 @@ def initialize(uri,
131136
end
132137

133138
if @proxy
134-
http_client_options["proxy"] = {
139+
base_http_client_options["proxy"] = {
135140
:proxy_address => @proxy.host,
136141
:proxy_port => @proxy.port,
137142
}
138143
end
139144

140-
@http_client = HTTP::Client.new(http_client_options)
145+
options = http_client_options.is_a?(Hash) ? base_http_client_options.merge(http_client_options) : base_http_client_options
146+
147+
@http_client = HTTP::Client.new(options)
141148
.follow
142149
.timeout({
143150
read: read_timeout,

spec/client_spec.rb

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,155 @@ def test_object.to_s
738738
end
739739
end
740740

741+
describe "http_client_options precedence" do
742+
it "allows socket_factory to be set via individual parameter" do
743+
mock_socket_factory = double("MockSocketFactory")
744+
745+
with_server do |server|
746+
server.setup_response("/") do |req,res|
747+
send_stream_content(res, "", keep_open: true)
748+
end
749+
750+
# We can't easily test socket creation without actually making a connection,
751+
# but we can verify the options contain the socket_class
752+
client = nil
753+
expect {
754+
client = subject.new(server.base_uri, socket_factory: mock_socket_factory)
755+
}.not_to raise_error
756+
757+
# Access the internal HTTP client to verify socket_class was set
758+
expect(client.instance_variable_get(:@http_client).default_options.socket_class).to eq(mock_socket_factory)
759+
760+
client.close
761+
end
762+
end
763+
764+
it "allows proxy to be set via individual parameter" do
765+
with_server do |server|
766+
server.setup_response("/") do |req,res|
767+
send_stream_content(res, simple_event_1_text, keep_open: false)
768+
end
769+
770+
with_server(StubProxyServer.new) do |proxy|
771+
event_sink = Queue.new
772+
client = subject.new(server.base_uri, proxy: proxy.base_uri) do |c|
773+
c.on_event { |event| event_sink << event }
774+
end
775+
776+
with_client(client) do |c|
777+
expect(event_sink.pop).to eq(simple_event_1)
778+
expect(proxy.request_count).to eq(1)
779+
end
780+
end
781+
end
782+
end
783+
784+
it "allows http_client_options to override socket_factory" do
785+
individual_socket_factory = double("IndividualSocketFactory")
786+
override_socket_factory = double("OverrideSocketFactory")
787+
788+
with_server do |server|
789+
server.setup_response("/") do |req,res|
790+
send_stream_content(res, "", keep_open: true)
791+
end
792+
793+
# http_client_options should take precedence over individual parameter
794+
client = nil
795+
expect {
796+
client = subject.new(server.base_uri,
797+
socket_factory: individual_socket_factory,
798+
http_client_options: {"socket_class" => override_socket_factory})
799+
}.not_to raise_error
800+
801+
# Verify that the override socket factory was used, not the individual one
802+
expect(client.instance_variable_get(:@http_client).default_options.socket_class).to eq(override_socket_factory)
803+
804+
client.close
805+
end
806+
end
807+
808+
it "allows http_client_options to override proxy settings" do
809+
with_server do |server|
810+
server.setup_response("/") do |req,res|
811+
send_stream_content(res, simple_event_1_text, keep_open: false)
812+
end
813+
814+
with_server(StubProxyServer.new) do |individual_proxy|
815+
with_server(StubProxyServer.new) do |override_proxy|
816+
event_sink = Queue.new
817+
client = subject.new(server.base_uri,
818+
proxy: individual_proxy.base_uri,
819+
http_client_options: {"proxy" => {
820+
:proxy_address => override_proxy.base_uri.host,
821+
:proxy_port => override_proxy.base_uri.port,
822+
}}) do |c|
823+
c.on_event { |event| event_sink << event }
824+
end
825+
826+
with_client(client) do |c|
827+
expect(event_sink.pop).to eq(simple_event_1)
828+
# The override proxy should be used, not the individual one
829+
expect(override_proxy.request_count).to eq(1)
830+
expect(individual_proxy.request_count).to eq(0)
831+
end
832+
end
833+
end
834+
end
835+
end
836+
837+
it "merges http_client_options with base options when both socket_factory and other options are provided" do
838+
socket_factory = double("SocketFactory")
839+
ssl_options = { verify_mode: 0 } # OpenSSL::SSL::VERIFY_NONE equivalent
840+
841+
with_server do |server|
842+
server.setup_response("/") do |req,res|
843+
send_stream_content(res, "", keep_open: true)
844+
end
845+
846+
# Should include both socket_factory from individual param and ssl from http_client_options
847+
client = nil
848+
expect {
849+
client = subject.new(server.base_uri,
850+
socket_factory: socket_factory,
851+
http_client_options: {"ssl" => ssl_options})
852+
}.not_to raise_error
853+
854+
# Verify both options are present
855+
http_options = client.instance_variable_get(:@http_client).default_options
856+
expect(http_options.socket_class).to eq(socket_factory)
857+
expect(http_options.ssl).to eq(ssl_options)
858+
859+
client.close
860+
end
861+
end
862+
end
863+
864+
describe "http_client_options SSL pass-through" do
865+
it "passes SSL verification options through http_client_options" do
866+
ssl_options = {
867+
verify_mode: 0, # OpenSSL::SSL::VERIFY_NONE equivalent
868+
verify_hostname: false,
869+
}
870+
871+
with_server do |server|
872+
server.setup_response("/") do |req,res|
873+
send_stream_content(res, "", keep_open: true)
874+
end
875+
876+
client = nil
877+
expect {
878+
client = subject.new(server.base_uri,
879+
http_client_options: {"ssl" => ssl_options})
880+
}.not_to raise_error
881+
882+
# Verify SSL options are passed through
883+
expect(client.instance_variable_get(:@http_client).default_options.ssl).to eq(ssl_options)
884+
885+
client.close
886+
end
887+
end
888+
end
889+
741890
describe "retry parameter" do
742891
it "defaults to true (retries enabled)" do
743892
events_body = simple_event_1_text

0 commit comments

Comments
 (0)