diff --git a/README.md b/README.md index 371e020..f51662e 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ NiceHttp.defaults = { host: 'reqres.in', ssl: true, port: 443, + timeout: 15, #seconds debug: false, log: "./my_logs.log", headers: {"api-key": "the api key"} @@ -289,6 +290,8 @@ Also interesting keys would be: *time_elapsed_total*, *time_elapsed* and many mo *auto_redirect*: (true or false) in case of true it will take care of the auto redirections. +*timeout*: Integer that will set a time out for the time waiting to connect to a host or waiting for a response. + ## Authentication requests All we need to do is to add to our request the correct authentication tokens, seeds, headers. @@ -584,7 +587,7 @@ threads = [] end end -t.each(&:join) +threads.each(&:join) # log files: nice_http_0.log, nice_http_1.log... nice_http_39.log ``` diff --git a/lib/nice_http.rb b/lib/nice_http.rb index 4f8cd63..432e3f9 100644 --- a/lib/nice_http.rb +++ b/lib/nice_http.rb @@ -7,13 +7,14 @@ ###################################################### # Attributes you can access using NiceHttp.the_attribute: -# :host, :port, :ssl, :headers, :debug, :log, :log_headers, :proxy_host, :proxy_port, +# :host, :port, :ssl, :timeout, :headers, :debug, :log, :log_headers, :proxy_host, :proxy_port, # :last_request, :last_response, :request_id, :use_mocks, :connections, # :active, :auto_redirect, :values_for, :create_stats, :stats, :capture, :captured, :request, :requests # # @attr [String] host The host to be accessed # @attr [Integer] port The port number # @attr [Boolean] ssl If you use ssl or not +# @attr [Integer] timeout Max time to wait until connected to the host or getting a response. # @attr [Hash] headers Contains the headers you will be using on your connection # @attr [Boolean] debug In case true shows all the details of the communication with the host # @attr [String] log_path The path where the logs will be stored. By default empty string. @@ -71,7 +72,7 @@ def initialize(attribute, message = "") end class << self - attr_accessor :host, :port, :ssl, :headers, :debug, :log_path, :log, :proxy_host, :proxy_port, :log_headers, + attr_accessor :host, :port, :ssl, :timeout, :headers, :debug, :log_path, :log, :proxy_host, :proxy_port, :log_headers, :last_request, :last_response, :request, :request_id, :use_mocks, :connections, :active, :auto_redirect, :log_files, :values_for, :create_stats, :stats, :capture, :captured, :requests end @@ -89,6 +90,7 @@ def self.reset! @host = nil @port = 80 @ssl = false + @timeout = nil @headers = {} @values_for = {} @debug = false @@ -137,18 +139,19 @@ def self.inherited(subclass) subclass.reset! end - attr_reader :host, :port, :ssl, :debug, :log, :log_path, :proxy_host, :proxy_port, :response, :num_redirects + attr_reader :host, :port, :ssl, :timeout, :debug, :log, :log_path, :proxy_host, :proxy_port, :response, :num_redirects attr_accessor :headers, :cookies, :use_mocks, :auto_redirect, :logger, :values_for, :log_headers ###################################################### # Change the default values for NiceHttp supplying a Hash # - # @param par [Hash] keys: :host, :port, :ssl, :headers, :debug, :log, :log_path, :proxy_host, :proxy_port, :use_mocks, :auto_redirect, :values_for, :create_stats, :log_headers, :capture + # @param par [Hash] keys: :host, :port, :ssl, :timeout, :headers, :debug, :log, :log_path, :proxy_host, :proxy_port, :use_mocks, :auto_redirect, :values_for, :create_stats, :log_headers, :capture ###################################################### def self.defaults=(par = {}) @host = par[:host] if par.key?(:host) @port = par[:port] if par.key?(:port) @ssl = par[:ssl] if par.key?(:ssl) + @timeout = par[:timeout] if par.key?(:timeout) @headers = par[:headers].dup if par.key?(:headers) @values_for = par[:values_for].dup if par.key?(:values_for) @debug = par[:debug] if par.key?(:debug) @@ -300,6 +303,7 @@ def self.save_stats(file_name = "") # host -- example.com. (default blank screen) # port -- port for the connection. 80 (default) # ssl -- true, false (default) + # timeout -- integer or nil (default) # headers -- hash with the headers # values_for -- hash with the values_for # debug -- true, false (default) @@ -327,6 +331,7 @@ def initialize(args = {}) @port = self.class.port @prepath = "" @ssl = self.class.ssl + @timeout = self.class.timeout @headers = self.class.headers.dup @values_for = self.class.values_for.dup @debug = self.class.debug @@ -357,6 +362,7 @@ def initialize(args = {}) @host = args[:host] if args.keys.include?(:host) @port = args[:port] if args.keys.include?(:port) @ssl = args[:ssl] if args.keys.include?(:ssl) + @timeout = args[:timeout] if args.keys.include?(:timeout) @headers = args[:headers].dup if args.keys.include?(:headers) @values_for = args[:values_for].dup if args.keys.include?(:values_for) @debug = args[:debug] if args.keys.include?(:debug) @@ -452,6 +458,7 @@ def initialize(args = {}) raise InfoMissing, :port if @port.to_s == "" raise InfoMissing, :host if @host.to_s == "" raise InfoMissing, :ssl unless @ssl.is_a?(TrueClass) or @ssl.is_a?(FalseClass) + raise InfoMissing, :timeout unless @timeout.is_a?(Integer) or @timeout.nil? raise InfoMissing, :debug unless @debug.is_a?(TrueClass) or @debug.is_a?(FalseClass) raise InfoMissing, :auto_redirect unless auto_redirect.is_a?(TrueClass) or auto_redirect.is_a?(FalseClass) raise InfoMissing, :use_mocks unless @use_mocks.is_a?(TrueClass) or @use_mocks.is_a?(FalseClass) @@ -465,18 +472,26 @@ def initialize(args = {}) @http.use_ssl = @ssl @http.set_debug_output $stderr if @debug @http.verify_mode = OpenSSL::SSL::VERIFY_NONE + unless @timeout.nil? + @http.open_timeout = @timeout + @http.read_timeout = @timeout + end @http.start else @http = Net::HTTP.new(@host, @port) @http.use_ssl = @ssl @http.set_debug_output $stderr if @debug @http.verify_mode = OpenSSL::SSL::VERIFY_NONE + unless @timeout.nil? + @http.open_timeout = @timeout + @http.read_timeout = @timeout + end @http.start end @message_server = "(#{self.object_id}):" - log_message = "(#{self.object_id}): Http connection created. host:#{@host}, port:#{@port}, ssl:#{@ssl}, mode:#{@mode}, proxy_host: #{@proxy_host.to_s()}, proxy_port: #{@proxy_port.to_s()} " + log_message = "(#{self.object_id}): Http connection created. host:#{@host}, port:#{@port}, ssl:#{@ssl}, timeout:#{@timeout}, mode:#{@mode}, proxy_host: #{@proxy_host.to_s()}, proxy_port: #{@proxy_port.to_s()} " @logger.info(log_message) @message_server += " Http connection: " @@ -499,6 +514,7 @@ def initialize(args = {}) rescue Exception => stack puts stack @logger.fatal stack + raise stack end end diff --git a/nice_http.gemspec b/nice_http.gemspec index 31fd942..8410fe2 100644 --- a/nice_http.gemspec +++ b/nice_http.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'nice_http' - s.version = '1.8.6' + s.version = '1.8.7' s.summary = "NiceHttp -- simplest library for accessing and testing HTTP and REST resources. Get http logs and statistics automatically. Use hashes on your requests. Access JSON even easier." s.description = "NiceHttp -- simplest library for accessing and testing HTTP and REST resources. Get http logs and statistics automatically. Use hashes on your requests. Access JSON even easier." s.authors = ["Mario Ruiz"] @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.extra_rdoc_files = ["LICENSE","README.md"] s.homepage = 'https://github.com/MarioRuiz/nice_http' s.license = 'MIT' - s.add_runtime_dependency 'nice_hash', '~> 1.15', '>= 1.15.6' + s.add_runtime_dependency 'nice_hash', '~> 1.17', '>= 1.17' s.add_development_dependency 'rspec', '~> 3.8', '>= 3.8.0' s.test_files = s.files.grep(%r{^(test|spec|features)/}) s.require_paths = ["lib"] diff --git a/spec/nice_http/nice_http_spec.rb b/spec/nice_http/nice_http_spec.rb index e961ab2..266ec59 100644 --- a/spec/nice_http/nice_http_spec.rb +++ b/spec/nice_http/nice_http_spec.rb @@ -9,6 +9,7 @@ klass.host = "example.com" klass.port = 433 klass.ssl = true + klass.timeout = 20 klass.headers = {uno: "one"} klass.debug = true klass.log = :screen @@ -34,6 +35,7 @@ expect(klass.host).to eq nil expect(klass.port).to eq 80 expect(klass.ssl).to eq false + expect(klass.timeout).to eq nil expect(klass.headers).to eq ({}) expect(klass.debug).to eq false expect(klass.log).to eq :fix_file @@ -58,18 +60,18 @@ describe "port" do it "uses the class port by default" do - klass.host = "localhost" - klass.port = 8888 - expect(klass.new.port).to eq 8888 + klass.host = "example.com" + klass.port = 443 + expect(klass.new.port).to eq 443 end it "uses the URI default when provided a URI and the URI has one" do - klass.port = 8888 + klass.port = 80 expect(klass.new("https://example.com").port).to eq 443 - expect(klass.new("lol://localhost").port).to eq 8888 + expect(klass.new("lol://example.com").port).to eq 80 end it "can be provided an explicit port" do klass.port = 8888 - klass.host = "localhost" + klass.host = "example.com" expect(klass.new(port: 443).port).to eq 443 end it 'raises an error when it can\'t figure out the port' do @@ -82,9 +84,9 @@ describe "host" do it "uses the class host by default" do - klass.host = "localhost" - klass.port = 8888 - expect(klass.new.host).to eq "localhost" + klass.host = "example.com" + klass.port = 80 + expect(klass.new.host).to eq "example.com" end it "uses the URI default when provided a URI and the URI has one" do klass.port = 8888 @@ -133,6 +135,47 @@ end end + describe "timeout" do + it "uses the class timeout by default" do + klass.timeout = 15 + klass.host = "example.com" + klass.port = 443 + expect(klass.new.timeout).to eq 15 + end + it "can be provided an explicit timeout" do + klass.port = 443 + klass.host = "localhost" + klass.timeout = 30 + expect(klass.new(host: "example.com", timeout: 10).timeout).to eq 10 + end + it 'raises an error when it can\'t figure out the timeout' do + klass.timeout = "xxxxxxx" + klass.host = "localhost" + klass.port = 8888 + klass.new rescue err = $ERROR_INFO + expect(err.attribute).to eq :timeout + expect(err.message).to match /wrong timeout/i + end + it 'returns fatal error if timeout reached when reading' do + klass.timeout = 2 + http = klass.new("https://reqres.in") + resp = http.get("/api/users?delay=3") + expect(resp.code).to be_nil + expect(resp.message).to be_nil + expect(resp.fatal_error).to eq 'Net::ReadTimeout' + end + it 'returns error if not possible to connect when connecting' do + klass.timeout = 2 + http = klass.new("https://reqres4s55s.in") rescue err = $ERROR_INFO + expect(err.message).to match /Failed to open TCP/i + end + it 'returns error if timeout reached when connecting' do + klass.timeout = 2 + http = klass.new("http://example.com:8888") rescue err = $ERROR_INFO + expect(err.message).to match /execution expired/i + end + end + describe "debug" do it "uses the class debug by default" do klass.debug = true @@ -142,13 +185,13 @@ end it "can be provided an explicit debug" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.debug = false expect(klass.new(debug: true).debug).to eq true end it 'raises an error when it can\'t figure out the debug' do klass.debug = nil - klass.host = "localhost" + klass.host = "example.com" klass.port = 8888 klass.new rescue err = $ERROR_INFO expect(err.attribute).to eq :debug @@ -164,7 +207,7 @@ end it "can be provided an explicit auto_redirect" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.auto_redirect = true expect(klass.new(auto_redirect: false).auto_redirect).to eq false end @@ -188,7 +231,7 @@ end it "can be provided an explicit log_headers" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.log_headers = :all expect(klass.new(log_headers: :partial).log_headers).to eq :partial end @@ -212,7 +255,7 @@ end it "can be provided an explicit use_mocks" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.use_mocks = false expect(klass.new(use_mocks: true).use_mocks).to eq true end @@ -236,7 +279,7 @@ end it "can be provided an explicit headers" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.headers = {} expect(klass.new(headers: {example: "test"}).headers).to eq ({example: "test"}) end @@ -260,7 +303,7 @@ end it "can be provided an explicit values_for" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.values_for = {} expect(klass.new(values_for: {example: "test"}).values_for).to eq ({example: "test"}) end @@ -325,7 +368,7 @@ end it "can be provided an explicit log" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.log = :screen expect(klass.new(log: :file).log).to eq (:file) end @@ -349,7 +392,7 @@ end it "can be provided an explicit log_path" do klass.port = 443 - klass.host = "localhost" + klass.host = "example.com" klass.log_path = './tmp/' expect(klass.new(log_path: './tmp/tmp/').log_path).to eq ('./tmp/tmp/') end @@ -365,6 +408,9 @@ specify "ssl is false" do expect(klass.ssl).to eq false end + specify "timeout is nil" do + expect(klass.timeout).to eq nil + end specify "debug is false" do expect(klass.debug).to eq false end @@ -399,6 +445,7 @@ expect { klass.port = 8888 }.to change { klass.port }.to(8888) expect { klass.host = "localhost" }.to change { klass.host }.to("localhost") expect { klass.ssl = true }.to change { klass.ssl }.to(true) + expect { klass.timeout = 10 }.to change { klass.timeout }.to(10) expect { klass.debug = true }.to change { klass.debug }.to(true) expect { klass.auto_redirect = false }.to change { klass.auto_redirect }.to(false) expect { klass.log_headers = :partial }.to change { klass.log_headers }.to(:partial) @@ -414,6 +461,7 @@ expect { klass.defaults = {port: 8888} }.to change { klass.port }.to(8888) expect { klass.defaults = {host: "localhost"} }.to change { klass.host }.to("localhost") expect { klass.defaults = {ssl: true} }.to change { klass.ssl }.to(true) + expect { klass.defaults = {timeout: 15} }.to change { klass.timeout }.to(15) expect { klass.defaults = {debug: true} }.to change { klass.debug }.to(true) expect { klass.defaults = {auto_redirect: false} }.to change { klass.auto_redirect }.to(false) expect { klass.defaults = {log_headers: :none} }.to change { klass.log_headers }.to(:none)