# encoding: utf-8 require "spec_helper" java_import "org.apache.http.entity.mime.MultipartEntityBuilder" java_import "org.apache.http.entity.ContentType" describe Manticore::Client do let(:client) { Manticore::Client.new } it "fetches a URL and return a response" do expect(client.get(local_server)).to be_a Manticore::Response end it "resolves redirections" do response = client.get(local_server, headers: {"X-Redirect" => "/foobar"}) expect(response.code).to eq 200 expect(response.final_url).to eq URI(local_server("/foobar")) end it "accepts custom headers" do response = client.get(local_server, headers: {"X-Custom-Header" => "Blaznotts"}) json = JSON.load(response.body) expect(json["headers"]["X-Custom-Header"]).to eq "Blaznotts" end it "accepts repeated header values" do response = client.get(local_server, headers: {"X-Custom-Header" => ["Whizzles", "Blaznotts"]}) json = JSON.load(response.body) expect(json["headers"]["X-Custom-Header"].sort).to eq ["Blaznotts", "Whizzles"] end it "enables compression" do response = client.get(local_server) json = JSON.load(response.body) expect(json["headers"]).to have_key "Accept-Encoding" expect(json["headers"]["Accept-Encoding"]).to match "gzip" end it "authenticates" do expect(client.get(local_server("/auth")).code).to eq 401 expect(client.get(local_server("/auth"), auth: {user: "user", pass: "pass"}).code).to eq 200 end it "authenticates with eager auth" do expect(client.get(local_server("/auth"), auth: {user: "user", pass: "pass", eager: true}).code).to eq 200 end it "proxies" do j = JSON.parse(client.get(local_server("/proxy"), proxy: "http://localhost:55442").body) expect(j["server_port"]).to eq 55442 expect(j["uri"]["port"]).to eq 55441 end it "automatically decodes application/JSON as UTF-8" do j = JSON.parse client.get(local_server("/json_utf8")).body expect(j["last_name"]).to eq "Töger" end context "via an authenticated proxy" do let(:proxy) { "http://localhost:55442" } let(:auth) { nil } let(:j) { JSON.parse req.body } context "with authentication as a hash" do let(:auth) { {user: "user", pass: "pass"} } let(:req) { client.get(local_server("/authproxy"), proxy: proxy, auth: auth) } it "proxies" do expect(j["server_port"]).to eq 55442 expect(j["uri"]["port"]).to eq 55441 expect(j["headers"]["Proxy-Authorization"]).to eq "Basic dXNlcjpwYXNz" end end context "with authentication as a string" do let(:proxy) { "http://user:pass@localhost:55442" } let(:req) { client.get(local_server("/authproxy"), proxy: proxy) } it "proxies" do expect(j["server_port"]).to eq 55442 expect(j["uri"]["port"]).to eq 55441 expect(j["headers"]["Proxy-Authorization"]).to eq "Basic dXNlcjpwYXNz" end end end describe "with a custom user agent" do let(:client) { Manticore::Client.new user_agent: "test-agent/1.0" } it "uses the specified UA" do response = client.get(local_server("/")) json = JSON.load(response.body) expect(json["headers"]["User-Agent"]).to eq "test-agent/1.0" end end describe "ignore_ssl_validation (deprecated option)" do context "when on" do let(:client) { Manticore::Client.new ssl: {verify: false} } it "does not break on SSL validation errors" do expect { client.get("https://localhost:55444/").body }.to_not raise_exception end end context "when off" do let(:client) { Manticore::Client.new ssl: {verify: true} } it "breaks on SSL validation errors" do expect { client.get("https://localhost:55444/").call }.to raise_exception(Manticore::ClientProtocolException) end end end describe "ssl settings" do describe "verify" do context "default" do let(:client) { Manticore::Client.new } it "breaks on SSL validation errors" do expect { client.get("https://localhost:55444/").call }.to raise_exception(Manticore::ClientProtocolException) end it "breaks on SSL expiry errors" do expect { client.get("https://localhost:55446/").call }.to raise_exception(Manticore::ClientProtocolException) end end context "when on and no trust store is given" do let(:client) { Manticore::Client.new :ssl => {:verify => :strict} } it "breaks on SSL validation errors" do expect { client.get("https://localhost:55444/").call }.to raise_exception(Manticore::ClientProtocolException) end end context "when on and custom trust store is given" do let(:client) { Manticore::Client.new :ssl => {verify: :strict, truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "test123"} } it "verifies the request and succeed" do expect { client.get("https://localhost:55444/").body }.to_not raise_exception end end context "when the client specifies a protocol list" do let(:client) { Manticore::Client.new :ssl => {verify: :strict, truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "test123", protocols: ["TLSv1", "TLSv1.1", "TLSv1.2"]} } it "verifies the request and succeed" do expect { client.get("https://localhost:55444/").body }.to_not raise_exception end end context "when on and custom trust store is given with the wrong password" do let(:client) { Manticore::Client.new :ssl => {verify: :strict, truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "wrongpass"} } it "fails to load the keystore" do expect { client.get("https://localhost:55444/").body }.to raise_exception(Java::JavaIo::IOException) end end context "when ca_file is given" do let(:client) { Manticore::Client.new :ssl => {verify: :strict, ca_file: File.expand_path("../../ssl/root-ca.crt", __FILE__)} } it "verifies the request and succeed" do expect { client.get("https://localhost:55444/").body }.to_not raise_exception end end context "when client_cert and client_key are given as file paths" do let(:client) { Manticore::Client.new( :ssl => { verify: :strict, ca_file: File.expand_path("../../ssl/root-ca.crt", __FILE__), client_cert: File.expand_path("../../ssl/client.crt", __FILE__), client_key: File.expand_path("../../ssl/client.key", __FILE__), }, ) } it "successfully auths requests" do expect(client.get("https://localhost:55445/").body).to match("hello") end end context "when client_cert and client_key are given as OpenSSL::X509::Certificate" do let(:client) { Manticore::Client.new( :ssl => { verify: :strict, ca_file: File.expand_path("../../ssl/root-ca.crt", __FILE__), client_cert: OpenSSL::X509::Certificate.new(File.read(File.expand_path("../../ssl/client.crt", __FILE__))), client_key: OpenSSL::PKey::RSA.new(File.read(File.expand_path("../../ssl/client.key", __FILE__))), }, ) } it "successfully auths requests" do expect(client.get("https://localhost:55445/").body).to match("hello") end end context "when client_cert and client_key are given as strings" do let(:client) { Manticore::Client.new( :ssl => { verify: :strict, ca_file: File.expand_path("../../ssl/root-ca.crt", __FILE__), client_cert: File.read(File.expand_path("../../ssl/client.crt", __FILE__)), client_key: File.read(File.expand_path("../../ssl/client.key", __FILE__)), }, ) } it "successfully auths requests" do expect(client.get("https://localhost:55445/").body).to match("hello") end end context "when off" do let(:client) { Manticore::Client.new :ssl => {:verify => :disable} } it "does not break on SSL validation errors" do expect { client.get("https://localhost:55444/").body }.to_not raise_exception end it "does not break on expired SSL certificates" do expect { client.get("https://localhost:55446/").body }.to_not raise_exception end end context "against a server that verifies clients" do context "when client cert auth is provided" do let(:client) { options = { truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "test123", keystore: File.expand_path("../../ssl/client.p12", __FILE__), keystore_password: "test123", verify: :strict, } Manticore::Client.new :ssl => options } it "successfully auths requests" do expect(client.get("https://localhost:55445/").body).to match("hello") end end context "when client cert auth is not provided" do let(:client) { options = { truststore: File.expand_path("../../ssl/truststore.jks", __FILE__), truststore_password: "test123", verify: :strict, } Manticore::Client.new :ssl => options } it "fails the request" do # oraclejdk7 throws a SocketException here, oraclejdk8/openjdk7 throw ClientProtocolException expect { client.get("https://localhost:55445/").body }.to raise_exception(Manticore::ManticoreException) end end end end describe ":cipher_suites" do skip end describe ":protocols" do skip end end describe "lazy evaluation" do it "does not call synchronous requests by default" do req = client.get(local_server) expect(req).to_not be_called end context "given a lazy request" do subject { client.get(local_server) } before do expect(subject).to_not be_called expect(subject).to receive(:call).once.and_call_original end specify { expect { subject.body }.to change { subject.called? } } specify { expect { subject.headers }.to change { subject.called? } } specify { expect { subject.final_url }.to change { subject.called? } } specify { expect { subject.code }.to change { subject.called? } } specify { expect { subject.length }.to change { subject.called? } } specify { expect { subject.cookies }.to change { subject.called? } } end it "automatically calls synchronous requests that pass a handler block" do req = client.get(local_server) { |r| } expect(req).to be_called end it "does not call asynchronous requests even if a block is passed" do req = client.async.get(local_server) { |r| } expect(req).to_not be_called end it "does not call asynchronous requests when on_success is passed" do req = client.async.get(local_server).on_success { |r| } expect(req).to_not be_called end it "calls async requests on client execution" do req = client.async.get(local_server).on_success { |r| } expect { client.execute! }.to change { req.called? }.from(false).to(true) end describe "with a bad port number" do it "returns a Manticore::InvalidArgumentException" do failure = nil client.async.get(local_server("/", 65536)). on_failure { |f| failure = f } client.execute! expect(failure).to be_a(Manticore::InvalidArgumentException) end end describe "mysterious failures" do it "still returns an error to on failure" do failure = nil # I'm not crazy about reaching into the client via instance_variable_get here, but it works. expect(client.instance_variable_get("@client")).to receive(:execute).and_raise(StandardError.new("Uh oh")) client.async.get(local_server). on_failure { |f| failure = f } client.execute! expect(failure).to be_a(StandardError) end end end context "when client-wide cookie management is disabled" do let(:client) { Manticore::Client.new cookies: false } it "persists cookies across multiple redirects from a single request" do response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.cookies["x"]).to be_nil expect(response.headers["set-cookie"]).to match(/1/) end it "does not persist cookies between requests" do response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.cookies["x"]).to be_nil expect(response.headers["set-cookie"]).to match(/1/) response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.cookies["x"]).to be_nil expect(response.headers["set-cookie"]).to match(/1/) end end context "when client-wide cookie management is set to per-request" do let(:client) { Manticore::Client.new cookies: :per_request } it "persists cookies across multiple redirects from a single request" do response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.headers["set-cookie"]).to match(/2/) expect(response.cookies["x"].first.value).to eq "2" end it "does not persist cookies between requests" do response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.headers["set-cookie"]).to match(/2/) expect(response.cookies["x"].first.value).to eq "2" response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.headers["set-cookie"]).to match(/2/) expect(response.cookies["x"].first.value).to eq "2" end end context "when client-wide cookie management is enabled" do let(:client) { Manticore::Client.new cookies: true } it "persists cookies across multiple redirects from a single request" do response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.cookies["x"].first.value).to eq "2" end it "persists cookies between requests" do response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.cookies["x"].first.value).to eq "2" response = client.get(local_server("/cookies/1/2")) expect(response.final_url.to_s).to eq local_server("/cookies/2/2") expect(response.cookies["x"].first.value).to eq "4" end end context "when compression is disabled" do let(:client) { Manticore::Client.new do |client, request_config| client.disable_content_compression end } it "disables compression" do response = client.get(local_server) json = JSON.load(response.body) expect(json["headers"]["Accept-Encoding"]).to be_nil end end context "when no response charset is specified" do let(:content_type) { "text/plain" } it "decodes response bodies according to the content-type header" do expect(client.get(local_server, headers: {"X-Content-Type" => content_type}).body.encoding.name).to eq "ISO-8859-1" end end context "when an invalid response charset is specified" do let(:content_type) { "text/plain; charset=bogus" } it "decodes the content as UTF-8" do expect(client.get(local_server, headers: {"X-Content-Type" => content_type}).body.encoding.name).to eq "ISO-8859-1" end end context "when the response charset is UTF-8" do let(:content_type) { "text/plain; charset=utf-8" } it "decodes response bodies according to the content-type header" do expect(client.get(local_server, headers: {"X-Content-Type" => content_type}).body.encoding.name).to eq "UTF-8" end end describe "#get" do it "works" do response = client.get(local_server) expect(JSON.load(response.body)["method"]).to eq "GET" end it "works with a URI object" do response = client.get(URI.parse local_server) expect(JSON.load(response.body)["method"]).to eq "GET" end it "send a query" do response = client.get local_server, query: {foo: "bar"} expect(CGI.parse(JSON.load(response.body)["uri"]["query"])["foo"]).to eq ["bar"] end it "sends a body" do response = client.get(local_server, body: "This is a post body") expect(JSON.load(response.body)["body"]).to eq "This is a post body" end it "can send an array of parameters as :params" do response = client.get(local_server, params: {"foo" => ["baz", "bar"], "bar" => {"baz" => ["bin", 1, :b]}}) j = JSON.load(response.body) expect(j["body"]).to eq "" expect(j["uri"]["query"]).to include("foo=baz") end it "can send an array of parameters as :query" do response = client.get(local_server, query: {"foo" => ["baz", "bar"]}) j = JSON.load(response.body) expect(j["body"]).to eq "" expect(j["uri"]["query"]).to include("foo=baz") end it "sends non-ASCII params" do response = client.get(local_server, query: {"∑" => "√"}) j = JSON.load(response.body) expect(CGI.unescape j["uri"]["query"]).to eq "∑=√" end it "merges implicit query parameters with explicit ones" do response = client.get(local_server + "?foo=bar", query: {"baz" => "bin"}) j = JSON.load(response.body) expect(CGI.parse j["uri"]["query"]).to eq({"foo" => ["bar"], "baz" => ["bin"]}) end end describe "#post" do it "works" do response = client.post(local_server) expect(JSON.load(response.body)["method"]).to eq "POST" end it "sends a body" do response = client.post(local_server, body: "This is a post body") expect(JSON.load(response.body)["body"]).to eq "This is a post body" end it "sends a UTF-8 body" do response = client.post(local_server, body: "This is a post body ∑") expect(JSON.load(response.body)["body"]).to eq "This is a post body ∑" end it "sends params" do response = client.post(local_server, params: {key: "value"}) expect(CGI.unescape(JSON.load(response.body)["body"])).to eq "key=value" end it "sends non-ASCII params" do response = client.post(local_server, params: {"∑" => "√"}) expect(CGI.unescape(JSON.load(response.body)["body"])).to eq "∑=√" end it "sends a binary body" do bytes = [145, 167, 116, 101, 115, 116, 49, 50, 51].pack("c*").encode("binary") response = client.post(local_server, body: bytes, headers: {"X-Base64" => "1"}) expect(Base64.decode64(JSON.load(response.body)["body"])).to eq bytes end it "sends an arbitrary entity" do f = open(File.expand_path(File.join(__FILE__, "..", "..", "spec_helper.rb")), "r").to_inputstream multipart_entity = MultipartEntityBuilder.create.add_text_body("foo", "bar").add_binary_body("whatever", f, ContentType::TEXT_PLAIN, __FILE__) response = client.post(local_server, entity: multipart_entity.build) expect(response.body).to match "RSpec.configure" end end describe "#put" do it "works" do response = client.put(local_server) expect(JSON.load(response.body)["method"]).to eq "PUT" end it "sends a body" do response = client.put(local_server, body: "This is a put body") expect(JSON.load(response.body)["body"]).to eq "This is a put body" end it "sends params" do response = client.put(local_server, params: {key: "value"}) expect(JSON.load(response.body)["body"]).to eq "key=value" end end describe "#delete" do it "works" do response = client.delete(local_server) expect(JSON.load(response.body)["method"]).to eq "DELETE" end it "sends a body" do response = client.delete(local_server, body: "This is a delete body") expect(JSON.load(response.body)["body"]).to eq "This is a delete body" end end describe "#head" do it "works" do response = client.head(local_server) expect(response.body).to be_nil end end describe "#options" do it "works" do response = client.options(local_server) expect(JSON.load(response.body)["method"]).to eq "OPTIONS" end end describe "#patch" do it "works" do response = client.patch(local_server) expect(JSON.load(response.body)["method"]).to eq "PATCH" end it "sends a body" do response = client.patch(local_server, body: "This is a patch body") expect(JSON.load(response.body)["body"]).to eq "This is a patch body" end it "sends params" do response = client.patch(local_server, params: {key: "value"}) expect(JSON.load(response.body)["body"]).to eq "key=value" end end describe "#execute!" do it "performs multiple concurrent requests" do futures = [55441, 55442].map do |port| client.async.get("http://localhost:#{port}/?sleep=1"). on_success do |response| Time.now.to_f end end client.execute! values = futures.map(&:callback_result) expect((values[0] - values[1]).abs).to be < 0.25 end it "returns the results of the handler blocks" do [55441, 55442].each do |port| client.async.get("http://localhost:#{port}/"). on_success { |response, request| "Result" } end expect(client.execute!.map(&:callback_result)).to eq ["Result", "Result"] end end describe "#clear_pending" do it "removes pending requests" do ran = false client.async.get("http://google.com").on_success { |r| ran = true } client.clear_pending expect(client.execute!).to be_empty expect(ran).to be false end end describe "#stub" do it "responds with a stubbed response until it is unstubbed" do client.stub(local_server, body: "body", code: 200) called = false 2.times { client.get(local_server) do |response| called = true expect(response).to be_a Manticore::StubbedResponse expect(response.body).to eq "body" expect(response.code).to eq 200 end } expect(called).to be true client.clear_stubs! client.get(local_server) do |response| expect(response).to be_a Manticore::Response expect(response.body).to match /Manticore/ expect(response.code).to eq 200 end end context "stubbing" do it "only the provided URLs" do client.stub local_server, body: "body" client.async.get(local_server).on_success { |r| expect(r).to be_a Manticore::StubbedResponse } client.async.get(local_server("/other")).on_success { |r| expect(r).to_not be_a Manticore::StubbedResponse } client.execute! end it "by regex matching" do client.stub %r{#{local_server("/foo")}}, body: "body" client.async.get(local_server("/foo")).on_success { |r| expect(r).to be_a Manticore::StubbedResponse } client.async.get(local_server("/bar")).on_success { |r| expect(r).to_not be_a Manticore::StubbedResponse } client.execute! end it "strictly matches string stubs" do client.stub local_server("/foo"), body: "body" client.async.get(local_server("/foo")).on_success { |r| expect(r).to be_a Manticore::StubbedResponse } client.async.get(local_server("/other")).on_success { |r| expect(r).to_not be_a Manticore::StubbedResponse } client.execute! end it "matches stubs with query strings" do url = "http://google.com?k=v" client.stub(url, body: "response body", code: 200) client.get(url) do |response| expect(response.body).to eq("response body") end end it "persists the stub for non-block calls" do url = "http://google.com" client.stub(url, body: "response body", code: 200) response = client.get(url) expect(response.body).to eq("response body") end end end describe "#close" do it "makes the client unusable" do client.close response = client.head(local_server) expect { response.body }.to raise_error(Manticore::ClientStoppedException) end end describe "keepalive" do let(:url) { "http://www.facebook.com/" } context "with keepalive" do it "adds the Connection: Keep-Alive header for http/1.0" do expect(client.get(url).request["Connection"]).to eq "Keep-Alive" end let(:client) { Manticore::Client.new keepalive: true, pool_max: 1 } it "keeps the connection open after a request" do skip response = client.get(url).call get_connection(client, url) do |conn| expect(conn.is_open).to be true end end end context "without keepalive" do let(:client) { Manticore::Client.new keepalive: false, pool_max: 1 } it "does not add the Connection: Keep-Alive header for http/1.0" do expect(client.get(url).request["Connection"]).to be_nil end it "closes the connection after a request" do skip response = client.get(url).call puts `netstat -apn` # get_connection(client, url) do |conn| # conn.is_open.should be false # end end end end context "with a misbehaving endpoint" do let(:port) do p = 4000 server = nil begin server = TCPServer.new p rescue Errno::EADDRINUSE p += 1 retry ensure server.close end p end before do @socket = TCPServer.new port @server = Thread.new do loop do begin client = @socket.accept client.puts([ "HTTP/1.1 200 OK", "Keep-Alive: timeout=3000", "Connection: Keep-Alive", "Content-Length: 6", "", "Hello!", ].join("\n")) client.close rescue IOError => e break end end end end after do @server.kill end let(:client) { Manticore::Client.new keepalive: true, pool_max: 1 } it "retries 3 times by default" do # The first time, reply with keepalive, then close the connection # The second connection should succeed request1 = client.get("http://localhost:#{port}/") request2 = client.get("http://localhost:#{port}/") expect { request1.call }.to_not raise_exception expect { request2.call }.to_not raise_exception expect(request1.times_retried).to eq 0 expect(request2.times_retried).to eq 1 end context "when the max retry is restrictive" do let(:client) { Manticore::Client.new keepalive: true, pool_max: 1, automatic_retries: 0 } it "retries 0 times and fail on the second request" do # The first time, reply with keepalive, then close the connection # The second connection should succeed expect { client.get("http://localhost:#{port}/").call }.to_not raise_exception expect { client.get("http://localhost:#{port}/").call }.to raise_exception(Manticore::SocketException) end end context "when keepalive is off" do let(:client) { Manticore::Client.new keepalive: false, pool_max: 1 } it "succeeds without any retries" do # The first time, reply with keepalive, then close the connection # The second connection should succeed request1 = client.get("http://localhost:#{port}/") request2 = client.get("http://localhost:#{port}/") expect { request1.call }.to_not raise_exception expect { request2.call }.to_not raise_exception expect(request1.times_retried).to eq 0 expect(request2.times_retried).to eq 0 end end after do Thread.kill @server begin @socket.close rescue IOError # pass end end end describe "with connection timeouts" do let(:client) { Manticore::Client.new request_timeout: 1, connect_timeout: 1, socket_timeout: 1 } it "times out" do expect { client.get(local_server "/?sleep=2").body }.to raise_exception(Manticore::SocketTimeout) end it "times out when custom request options are passed" do expect { client.get(local_server("/?sleep=2"), max_redirects: 5).body }.to raise_exception(Manticore::SocketTimeout) end end describe "request types:" do describe "sync" do it "returns a response" do expect(client.get(local_server)).to be_a Manticore::Response end it "evaluates immediately when given a block" do called = false expect { client.get(local_server) { called = true } }.to change { called }.to(true) end it "can be evaluated synchronously on-demand" do expect(client.get(local_server).body).to be_a String end end describe "async" do it "returns a response" do expect(client.async.get(local_server)).to be_a Manticore::Response end it "works with `parallel`" do expect(client.parallel.get(local_server)).to be_a Manticore::Response end it "works with `batch`" do expect(client.parallel.get(local_server)).to be_a Manticore::Response end it "does not evaluate immediately when given a block" do called = false expect { client.async.get(local_server) { called = true }; sleep 0.5 }.to_not change { called } end it "can be evaluated synchronously on-demand" do expect(client.async.get(local_server).body).to be_a String end end describe "background" do it "returns a response" do expect(client.background.get(local_server)).to be_a Manticore::Response end it "has the background flag set" do expect(client.background.get(local_server).background).to eq true end it "returns a future when called" do expect(client.background.get(local_server).call).to be_a Java::JavaUtilConcurrent::FutureTask end it "evaluates immediately when given a block" do called = false expect { (client.background.get(local_server) { called = true }).get }.to change { called }.to(true) end it "raises an exception when a sync method is called on it" do called = false expect { client.background.get(local_server).body }.to raise_exception(RuntimeError) end end end def get_connection(client, uri, &block) java_import "java.util.concurrent.TimeUnit" host = URI.parse(uri).host pool = client.instance_variable_get("@pool") req = pool.requestConnection(Java::OrgApacheHttpConnRouting::HttpRoute.new(Java::OrgApacheHttp::HttpHost.new(host)), nil) conn = req.get(3, TimeUnit::SECONDS) begin yield conn ensure pool.releaseConnection(conn, nil, 0, TimeUnit::SECONDS) end end end