From 23ae0735ff3c63261c8f3c9c08a0b3dfce946670 Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Thu, 20 Oct 2022 11:11:01 +0000 Subject: [PATCH 1/8] close #28 --- lib/nice_http.rb | 493 +------------ lib/nice_http/add_stats.rb | 85 +++ lib/nice_http/close.rb | 39 + lib/nice_http/defaults.rb | 25 + lib/nice_http/http_methods.rb | 686 ------------------ lib/nice_http/inherited.rb | 8 + lib/nice_http/initialize.rb | 233 ++++++ lib/nice_http/manage/create_stats.rb | 64 ++ .../{manage_request.rb => manage/request.rb} | 18 +- .../response.rb} | 117 +-- lib/nice_http/manage/set_stats.rb | 43 ++ lib/nice_http/methods/delete.rb | 108 +++ lib/nice_http/methods/get.rb | 159 ++++ lib/nice_http/methods/head.rb | 72 ++ lib/nice_http/methods/patch.rb | 103 +++ lib/nice_http/methods/post.rb | 122 ++++ lib/nice_http/methods/put.rb | 87 +++ lib/nice_http/methods/send_request.rb | 47 ++ lib/nice_http/reset.rb | 49 ++ lib/nice_http/save_stats.rb | 37 + lib/nice_http/utils/basic_authentication.rb | 18 + .../{utils.rb => utils/get_value_xml_tag.rb} | 45 -- lib/nice_http/utils/set_value_xml_tag.rb | 30 + 23 files changed, 1365 insertions(+), 1323 deletions(-) create mode 100644 lib/nice_http/add_stats.rb create mode 100644 lib/nice_http/close.rb create mode 100644 lib/nice_http/defaults.rb delete mode 100644 lib/nice_http/http_methods.rb create mode 100644 lib/nice_http/inherited.rb create mode 100644 lib/nice_http/initialize.rb create mode 100644 lib/nice_http/manage/create_stats.rb rename lib/nice_http/{manage_request.rb => manage/request.rb} (97%) rename lib/nice_http/{manage_response.rb => manage/response.rb} (63%) create mode 100644 lib/nice_http/manage/set_stats.rb create mode 100644 lib/nice_http/methods/delete.rb create mode 100644 lib/nice_http/methods/get.rb create mode 100644 lib/nice_http/methods/head.rb create mode 100644 lib/nice_http/methods/patch.rb create mode 100644 lib/nice_http/methods/post.rb create mode 100644 lib/nice_http/methods/put.rb create mode 100644 lib/nice_http/methods/send_request.rb create mode 100644 lib/nice_http/reset.rb create mode 100644 lib/nice_http/save_stats.rb create mode 100644 lib/nice_http/utils/basic_authentication.rb rename lib/nice_http/{utils.rb => utils/get_value_xml_tag.rb} (50%) create mode 100644 lib/nice_http/utils/set_value_xml_tag.rb diff --git a/lib/nice_http.rb b/lib/nice_http.rb index 432e3f9..f26393d 100644 --- a/lib/nice_http.rb +++ b/lib/nice_http.rb @@ -1,9 +1,26 @@ require "logger" require "nice_hash" -require_relative "nice_http/utils" -require_relative "nice_http/manage_request" -require_relative "nice_http/manage_response" -require_relative "nice_http/http_methods" +require_relative "nice_http/methods/get" +require_relative "nice_http/methods/post" +require_relative "nice_http/methods/head" +require_relative "nice_http/methods/put" +require_relative "nice_http/methods/delete" +require_relative "nice_http/methods/patch" +require_relative "nice_http/methods/send_request" +require_relative "nice_http/manage/create_stats" +require_relative "nice_http/manage/request" +require_relative "nice_http/manage/response" +require_relative "nice_http/manage/set_stats" +require_relative "nice_http/utils/basic_authentication" +require_relative "nice_http/utils/get_value_xml_tag" +require_relative "nice_http/utils/set_value_xml_tag" +require_relative "nice_http/reset" +require_relative "nice_http/add_stats" +require_relative "nice_http/defaults" +require_relative "nice_http/inherited" +require_relative "nice_http/save_stats" +require_relative "nice_http/close" +require_relative "nice_http/initialize" ###################################################### # Attributes you can access using NiceHttp.the_attribute: @@ -83,478 +100,10 @@ class << self end end - ###################################################### - # to reset to the original defaults - ###################################################### - def self.reset! - @host = nil - @port = 80 - @ssl = false - @timeout = nil - @headers = {} - @values_for = {} - @debug = false - @log = :fix_file - @log_path = '' - @log_headers = :all - @proxy_host = nil - @proxy_port = nil - @last_request = nil - @request = nil - @requests = nil - @last_response = nil - @request_id = "" - @use_mocks = false - @connections = [] - @active = 0 - @auto_redirect = true - @log_files = {} - @create_stats = false - @stats = { - all: { - num_requests: 0, - started: nil, - finished: nil, - real_time_elapsed: 0, - time_elapsed: { - total: 0, - maximum: 0, - minimum: 1000000, - average: 0, - }, - method: {}, - }, - path: {}, - name: {}, - } - @capture = false - @captured = [] - end reset! - ###################################################### - # If inheriting from NiceHttp class - ###################################################### - def self.inherited(subclass) - subclass.reset! - end - 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, :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) - @log_path = par[:log_path] if par.key?(:log_path) - @log = par[:log] if par.key?(:log) - @log_headers = par[:log_headers] if par.key?(:log_headers) - @proxy_host = par[:proxy_host] if par.key?(:proxy_host) - @proxy_port = par[:proxy_port] if par.key?(:proxy_port) - @use_mocks = par[:use_mocks] if par.key?(:use_mocks) - @auto_redirect = par[:auto_redirect] if par.key?(:auto_redirect) - @create_stats = par[:create_stats] if par.key?(:create_stats) - @capture = par[:capture] if par.key?(:capture) - end - - ###################################################### - # To add specific stats - # The stats will be added to NiceHttp.stats[:specific] - # - # @param name [Symbol] name to group your specific stats - # @param state [Symbol] state of the name supplied to group your specific stats - # @param started [Time] when the process you want the stats started - # @param finished [Time] when the process you want the stats finished - # @param item [Object] (Optional) The item to be added to :items key to store all items in an array - # - # @example - # started = Time.now - # @http.send_request Requests::Customer.add_customer - # 30.times do - # resp = @http.get(Requests::Customer.get_customer) - # break if resp.code == 200 - # sleep 0.5 - # end - # NiceHttp.add_stats(:customer, :create, started, Time.now) - ###################################################### - def self.add_stats(name, state, started, finished, item = nil) - self.stats[:specific] ||= {} - self.stats[:specific][name] ||= { num: 0, started: started, finished: started, real_time_elapsed: 0, time_elapsed: { total: 0, maximum: 0, minimum: 100000, average: 0 } } - self.stats[:specific][name][:num] += 1 - - if started < self.stats[:specific][name][:finished] - self.stats[:specific][name][:real_time_elapsed] += (finished - self.stats[:specific][name][:finished]) - else - self.stats[:specific][name][:real_time_elapsed] += (finished - started) - end - self.stats[:specific][name][:finished] = finished - - time_elapsed = self.stats[:specific][name][:time_elapsed] - time_elapsed[:total] += finished - started - if time_elapsed[:maximum] < (finished - started) - time_elapsed[:maximum] = (finished - started) - if !item.nil? - time_elapsed[:item_maximum] = item - elsif Thread.current.name.to_s != "" - time_elapsed[:item_maximum] = Thread.current.name - end - end - if time_elapsed[:minimum] > (finished - started) - time_elapsed[:minimum] = (finished - started) - if !item.nil? - time_elapsed[:item_minimum] = item - elsif Thread.current.name.to_s != "" - time_elapsed[:item_minimum] = Thread.current.name - end - end - time_elapsed[:average] = time_elapsed[:total] / self.stats[:specific][name][:num] - - self.stats[:specific][name][state] ||= { num: 0, started: started, finished: started, real_time_elapsed: 0, time_elapsed: { total: 0, maximum: 0, minimum: 1000, average: 0 }, items: [] } - self.stats[:specific][name][state][:num] += 1 - if started < self.stats[:specific][name][state][:finished] - self.stats[:specific][name][state][:real_time_elapsed] += (finished - self.stats[:specific][name][state][:finished]) - else - self.stats[:specific][name][state][:real_time_elapsed] += (finished - started) - end - - self.stats[:specific][name][state][:finished] = finished - - self.stats[:specific][name][state][:items] << item unless item.nil? or self.stats[:specific][name][state][:items].include?(item) - time_elapsed = self.stats[:specific][name][state][:time_elapsed] - time_elapsed[:total] += finished - started - if time_elapsed[:maximum] < (finished - started) - time_elapsed[:maximum] = (finished - started) - if !item.nil? - time_elapsed[:item_maximum] = item - elsif Thread.current.name.to_s != "" - time_elapsed[:item_maximum] = Thread.current.name - end - end - if time_elapsed[:minimum] > (finished - started) - time_elapsed[:minimum] = (finished - started) - if !item.nil? - time_elapsed[:item_minimum] = item - elsif Thread.current.name.to_s != "" - time_elapsed[:item_minimum] = Thread.current.name - end - end - time_elapsed[:average] = time_elapsed[:total] / self.stats[:specific][name][state][:num] - end - - ###################################################### - # It will save the NiceHttp.stats on different files, each key of the hash in a different file. - # - # @param file_name [String] path and file name to be used to store the stats. - # In case no one supplied it will be used the value in NiceHttp.log and it will be saved on YAML format. - # In case extension is .yaml will be saved on YAML format. - # In case extension is .json will be saved on JSON format. - # - # @example - # NiceHttp.save_stats - # NiceHttp.save_stats('./stats/my_stats.yaml') - # NiceHttp.save_stats('./stats/my_stats.json') - ###################################################### - def self.save_stats(file_name = "") - if file_name == "" - if self.log.is_a?(String) - file_name = self.log - else - file_name = "./#{self.log_path}nice_http.log" - end - end - require "fileutils" - FileUtils.mkdir_p File.dirname(file_name) - if file_name.match?(/\.json$/) - require "json" - self.stats.keys.each do |key| - File.open("#{file_name.gsub(/.json$/, "_stats_")}#{key}.json", "w") { |file| file.write(self.stats[key].to_json) } - end - else - require "yaml" - self.stats.keys.each do |key| - File.open("#{file_name.gsub(/.\w+$/, "_stats_")}#{key}.yaml", "w") { |file| file.write(self.stats[key].to_yaml) } - end - end - end - - ###################################################### - # Creates a new http connection. - # - # @param args [] If no parameter supplied, by default will access how is setup on defaults - # @example - # http = NiceHttp.new() - # @param args [String]. The url to create the connection. - # @example - # http = NiceHttp.new("https://www.example.com") - # @example - # http = NiceHttp.new("example.com:8999") - # @example - # http = NiceHttp.new("localhost:8322") - # @param args [Hash] containing these possible keys: - # 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) - # log_path -- string with path for the logs, empty string (default) - # log -- :no, :screen, :file, :fix_file (default). - # log_headers -- :all, :none, :partial (default). - # A string with a path can be supplied. - # If :fix_file: nice_http.log - # In case :file it will be generated a log file with name: nice_http_YY-mm-dd-HHMMSS.log - # proxy_host - # proxy_port - # @example - # http2 = NiceHttp.new( host: "reqres.in", port: 443, ssl: true ) - # @example - # my_server = {host: "example.com", - # port: 80, - # headers: {"api-key": "zdDDdjkck"} - # } - # http3 = NiceHttp.new my_server - ###################################################### - def initialize(args = {}) - require "net/http" - require "net/https" - @host = self.class.host - @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 - @log = self.class.log - @log_path = self.class.log_path - @log_headers = self.class.log_headers - @proxy_host = self.class.proxy_host - @proxy_port = self.class.proxy_port - @use_mocks = self.class.use_mocks - @auto_redirect = false #set it up at the end of initialize - auto_redirect = self.class.auto_redirect - @num_redirects = 0 - @create_stats = self.class.create_stats - @capture = self.class.capture - - #todo: set only the cookies for the current domain - #key: path, value: hash with key is the name of the cookie and value the value - # we set the default value for non existing keys to empty Hash {} so in case of merge there is no problem - @cookies = Hash.new { |h, k| h[k] = {} } - - if args.is_a?(String) - uri = URI.parse(args) - @host = uri.host unless uri.host.nil? - @port = uri.port unless uri.port.nil? - @ssl = true if !uri.scheme.nil? && (uri.scheme == "https") - @prepath = uri.path unless uri.path == "/" - elsif args.is_a?(Hash) && !args.keys.empty? - @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) - @log = args[:log] if args.keys.include?(:log) - @log_path = args[:log_path] if args.keys.include?(:log_path) - @log_headers = args[:log_headers] if args.keys.include?(:log_headers) - @proxy_host = args[:proxy_host] if args.keys.include?(:proxy_host) - @proxy_port = args[:proxy_port] if args.keys.include?(:proxy_port) - @use_mocks = args[:use_mocks] if args.keys.include?(:use_mocks) - auto_redirect = args[:auto_redirect] if args.keys.include?(:auto_redirect) - end - - log_filename = "" - if @log.kind_of?(String) or @log == :fix_file or @log == :file or @log == :file_run - if @log.kind_of?(String) - log_filename = @log.dup - unless log_filename.start_with?(".") - if caller.first.start_with?(Dir.pwd) - folder = File.dirname(caller.first.scan(/(.+):\d/).join) - else - folder = File.dirname("#{Dir.pwd}/#{caller.first.scan(/(.+):\d/).join}") - end - folder += "/" unless log_filename.start_with?("/") or log_filename.match?(/^\w+:/) - log_filename = folder + log_filename - end - require "fileutils" - FileUtils.mkdir_p File.dirname(log_filename) - unless Dir.exist?(File.dirname(log_filename)) - @logger = Logger.new nil - raise InfoMissing, :log, "Wrong directory specified for logs.\n" - end - elsif @log == :fix_file - log_filename = "nice_http.log" - elsif @log == :file - log_filename = "nice_http_#{Time.now.strftime("%Y-%m-%d-%H%M%S")}.log" - elsif @log == :file_run - log_filename = "#{caller.first.scan(/(.+):\d/).join}.log" - end - if Thread.current.name.to_s != "" - log_filename.gsub!(/\.log$/, "_#{Thread.current.name}.log") - end - unless @log_path.to_s == '' - log_filename.gsub!(Dir.pwd,'.') - dpath = @log_path.split("/") - dfile = log_filename.split("/") - log_filenamepath = '' - dfile.each_with_index do |d,i| - if d==dpath[i] - log_filenamepath<<"#{d}/" - else - log_filename = @log_path + "#{log_filename.gsub(/^#{log_filenamepath}/,'')}" - break - end - end - log_filename = "./#{log_filename}" unless log_filename[0..1]=='./' - log_filename = ".#{log_filename}" unless log_filename[0]=='.' - - unless File.exist?(log_filename) - require 'fileutils' - FileUtils.mkdir_p(File.dirname(log_filename)) - end - end - - if self.class.log_files.key?(log_filename) and File.exist?(log_filename) - @logger = self.class.log_files[log_filename] - else - begin - f = File.new(log_filename, "w") - f.sync = true - @logger = Logger.new f - rescue Exception => stack - @logger = Logger.new nil - raise InfoMissing, :log - end - self.class.log_files[log_filename] = @logger - end - elsif @log == :screen - @logger = Logger.new STDOUT - elsif @log == :no - @logger = Logger.new nil - else - raise InfoMissing, :log - end - @logger.level = Logger::INFO - - if @host.to_s != "" and (@host.start_with?("http:") or @host.start_with?("https:")) - uri = URI.parse(@host) - @host = uri.host unless uri.host.nil? - @port = uri.port unless uri.port.nil? - @ssl = true if !uri.scheme.nil? && (uri.scheme == "https") - @prepath = uri.path unless uri.path == "/" - end - 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) - raise InfoMissing, :headers unless @headers.is_a?(Hash) - raise InfoMissing, :values_for unless @values_for.is_a?(Hash) - raise InfoMissing, :log_headers unless [:all, :none, :partial].include?(@log_headers) - - begin - if !@proxy_host.nil? && !@proxy_port.nil? - @http = Net::HTTP::Proxy(@proxy_host, @proxy_port).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 - 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}, timeout:#{@timeout}, mode:#{@mode}, proxy_host: #{@proxy_host.to_s()}, proxy_port: #{@proxy_port.to_s()} " - - @logger.info(log_message) - @message_server += " Http connection: " - if @ssl - @message_server += "https://" - else - @message_server += "http://" - end - @message_server += "#{@host}:#{@port}" - if @proxy_host.to_s != "" - @message_server += " proxy:#{@proxy_host}:#{@proxy_port}" - end - @auto_redirect = auto_redirect - # for the case we have headers following nice_hash implementation - @headers_orig = @headers.dup - @headers = @headers.generate - - self.class.active += 1 - self.class.connections.push(self) - rescue Exception => stack - puts stack - @logger.fatal stack - raise stack - end - end - - ###################################################### - # Close HTTP connection - ###################################################### - def close - begin - pos = 0 - found = false - self.class.connections.each { |conn| - if conn.object_id == self.object_id - found = true - break - else - pos += 1 - end - } - if found - self.class.connections.delete_at(pos) - end - - unless @closed - if !@http.nil? - @http.finish() - @http = nil - @logger.info "the HTTP connection was closed: #{@message_server}" - else - @http = nil - @logger.fatal "It was not possible to close the HTTP connection: #{@message_server}" - end - @closed = true - else - @logger.warn "It was not possible to close the HTTP connection, already closed: #{@message_server}" - end - rescue Exception => stack - @logger.fatal stack - end - self.class.active -= 1 - end - private :manage_request, :manage_response end diff --git a/lib/nice_http/add_stats.rb b/lib/nice_http/add_stats.rb new file mode 100644 index 0000000..579d484 --- /dev/null +++ b/lib/nice_http/add_stats.rb @@ -0,0 +1,85 @@ +class NiceHttp + ###################################################### + # To add specific stats + # The stats will be added to NiceHttp.stats[:specific] + # + # @param name [Symbol] name to group your specific stats + # @param state [Symbol] state of the name supplied to group your specific stats + # @param started [Time] when the process you want the stats started + # @param finished [Time] when the process you want the stats finished + # @param item [Object] (Optional) The item to be added to :items key to store all items in an array + # + # @example + # started = Time.now + # @http.send_request Requests::Customer.add_customer + # 30.times do + # resp = @http.get(Requests::Customer.get_customer) + # break if resp.code == 200 + # sleep 0.5 + # end + # NiceHttp.add_stats(:customer, :create, started, Time.now) + ###################################################### + def self.add_stats(name, state, started, finished, item = nil) + self.stats[:specific] ||= {} + self.stats[:specific][name] ||= { num: 0, started: started, finished: started, real_time_elapsed: 0, time_elapsed: { total: 0, maximum: 0, minimum: 100000, average: 0 } } + self.stats[:specific][name][:num] += 1 + + if started < self.stats[:specific][name][:finished] + self.stats[:specific][name][:real_time_elapsed] += (finished - self.stats[:specific][name][:finished]) + else + self.stats[:specific][name][:real_time_elapsed] += (finished - started) + end + self.stats[:specific][name][:finished] = finished + + time_elapsed = self.stats[:specific][name][:time_elapsed] + time_elapsed[:total] += finished - started + if time_elapsed[:maximum] < (finished - started) + time_elapsed[:maximum] = (finished - started) + if !item.nil? + time_elapsed[:item_maximum] = item + elsif Thread.current.name.to_s != "" + time_elapsed[:item_maximum] = Thread.current.name + end + end + if time_elapsed[:minimum] > (finished - started) + time_elapsed[:minimum] = (finished - started) + if !item.nil? + time_elapsed[:item_minimum] = item + elsif Thread.current.name.to_s != "" + time_elapsed[:item_minimum] = Thread.current.name + end + end + time_elapsed[:average] = time_elapsed[:total] / self.stats[:specific][name][:num] + + self.stats[:specific][name][state] ||= { num: 0, started: started, finished: started, real_time_elapsed: 0, time_elapsed: { total: 0, maximum: 0, minimum: 1000, average: 0 }, items: [] } + self.stats[:specific][name][state][:num] += 1 + if started < self.stats[:specific][name][state][:finished] + self.stats[:specific][name][state][:real_time_elapsed] += (finished - self.stats[:specific][name][state][:finished]) + else + self.stats[:specific][name][state][:real_time_elapsed] += (finished - started) + end + + self.stats[:specific][name][state][:finished] = finished + + self.stats[:specific][name][state][:items] << item unless item.nil? or self.stats[:specific][name][state][:items].include?(item) + time_elapsed = self.stats[:specific][name][state][:time_elapsed] + time_elapsed[:total] += finished - started + if time_elapsed[:maximum] < (finished - started) + time_elapsed[:maximum] = (finished - started) + if !item.nil? + time_elapsed[:item_maximum] = item + elsif Thread.current.name.to_s != "" + time_elapsed[:item_maximum] = Thread.current.name + end + end + if time_elapsed[:minimum] > (finished - started) + time_elapsed[:minimum] = (finished - started) + if !item.nil? + time_elapsed[:item_minimum] = item + elsif Thread.current.name.to_s != "" + time_elapsed[:item_minimum] = Thread.current.name + end + end + time_elapsed[:average] = time_elapsed[:total] / self.stats[:specific][name][state][:num] + end +end diff --git a/lib/nice_http/close.rb b/lib/nice_http/close.rb new file mode 100644 index 0000000..a47bb50 --- /dev/null +++ b/lib/nice_http/close.rb @@ -0,0 +1,39 @@ +class NiceHttp + ###################################################### + # Close HTTP connection + ###################################################### + def close + begin + pos = 0 + found = false + self.class.connections.each { |conn| + if conn.object_id == self.object_id + found = true + break + else + pos += 1 + end + } + if found + self.class.connections.delete_at(pos) + end + + unless @closed + if !@http.nil? + @http.finish() + @http = nil + @logger.info "the HTTP connection was closed: #{@message_server}" + else + @http = nil + @logger.fatal "It was not possible to close the HTTP connection: #{@message_server}" + end + @closed = true + else + @logger.warn "It was not possible to close the HTTP connection, already closed: #{@message_server}" + end + rescue Exception => stack + @logger.fatal stack + end + self.class.active -= 1 + end +end diff --git a/lib/nice_http/defaults.rb b/lib/nice_http/defaults.rb new file mode 100644 index 0000000..6861a49 --- /dev/null +++ b/lib/nice_http/defaults.rb @@ -0,0 +1,25 @@ +class NiceHttp + ###################################################### + # Change the default values for NiceHttp supplying a Hash + # + # @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) + @log_path = par[:log_path] if par.key?(:log_path) + @log = par[:log] if par.key?(:log) + @log_headers = par[:log_headers] if par.key?(:log_headers) + @proxy_host = par[:proxy_host] if par.key?(:proxy_host) + @proxy_port = par[:proxy_port] if par.key?(:proxy_port) + @use_mocks = par[:use_mocks] if par.key?(:use_mocks) + @auto_redirect = par[:auto_redirect] if par.key?(:auto_redirect) + @create_stats = par[:create_stats] if par.key?(:create_stats) + @capture = par[:capture] if par.key?(:capture) + end +end diff --git a/lib/nice_http/http_methods.rb b/lib/nice_http/http_methods.rb deleted file mode 100644 index 926a729..0000000 --- a/lib/nice_http/http_methods.rb +++ /dev/null @@ -1,686 +0,0 @@ -module NiceHttpHttpMethods - - ###################################################### - # Get data from path - # - # @param arg [Hash, String] hash containing at least key :path or a string with the path - # @param save_data [String] the path or path and file name where we want to save the response data - # - # @return [Hash] response. - # Including at least the symbol keys: - # :data = the response data body. - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # data, message and code can also be accessed as attributes like .message .code .data. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } - # - # @example - # resp = @http.get(Requests::Customer.get_profile) - # assert resp.code == 200 - # @example - # resp = @http.get("/customers/1223") - # assert resp.message == "OK" - # @example - # resp = @http.get("/assets/images/logo.png", save_data: './tmp/') - # @example - # resp = @http.get("/assets/images/logo.png", save_data: './tmp/example.png') - ###################################################### - def get(arg, save_data: '') - begin - path, data, headers_t = manage_request(arg) - - @start_time = Time.now if @start_time.nil? - if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) - data = "" - if arg[:mock_response].keys.include?(:data) - data = arg[:mock_response][:data] - if data.kind_of?(Hash) #to json - begin - require "json" - data = data.to_json - rescue - @logger.fatal "There was a problem converting to json: #{data}" - end - end - end - @logger.warn "Pay attention!!! This is a mock response:" - @start_time_net = Time.now if @start_time_net.nil? - manage_response(arg[:mock_response], data.to_s) - return @response - end - begin - if path.start_with?("http:") or path.start_with?("https:") #server included on path problably because of a redirection to a different server - require "uri" - uri = URI.parse(path) - ssl = false - ssl = true if path.include?("https:") - - server = "http://" - server = "https://" if path.start_with?("https:") - if uri.port != 443 - server += "#{uri.host}:#{uri.port}" - else - server += "#{uri.host}" - end - - http_redir = nil - self.class.connections.each { |conn| - if conn.host == uri.host and conn.port == uri.port - http_redir = conn - break - end - } - - if !http_redir.nil? - path, data, headers_t = manage_request(arg) - http_redir.cookies.merge!(@cookies) - http_redir.headers.merge!(headers_t) - #todo: remove only the server at the begining in case in query is the server it will be replaced when it should not be - resp = http_redir.get(path.gsub(server, "")) - @response = http_redir.response - else - @logger.warn "It seems like the http connection cannot redirect to #{server} because there is no active connection for that server. You need to create previously one." - end - else - @start_time_net = Time.now if @start_time_net.nil? - resp = @http.get(path, headers_t) - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - resp = @http.get(path, headers_t) - end - end - data = resp.body - manage_response(resp, data) - end - rescue Exception => stack - @logger.warn stack - if !@timeout.nil? and (Time.now - @start_time_net) > @timeout - @logger.warn "The connection seems to be closed in the host machine. Timeout." - return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } - else - @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" - @http.finish() - @http.start() - @start_time_net = Time.now if @start_time_net.nil? - @headers_orig.each {|k,v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k)} - resp = @http.get(path, headers_t) - data = resp.body - manage_response(resp, data) - end - end - if @auto_redirect and @response[:code].to_i >= 300 and @response[:code].to_i < 400 and @response.include?(:location) - if @num_redirects <= 30 - @num_redirects += 1 - current_server = "http" - current_server += "s" if @ssl == true - current_server += "://#{@host}" - location = @response[:location].gsub(current_server, "") - @logger.info "(#{@num_redirects}) Redirecting NiceHttp to #{location}" - get(location) - else - @logger.fatal "(#{@num_redirects}) Maximum number of redirections for a single request reached. Be sure everything is correct, it seems there is a non ending loop" - @num_redirects = 0 - end - else - @num_redirects = 0 - end - if save_data!='' - require 'pathname' - pn_get = Pathname.new(path) - - if Dir.exist?(save_data) - save = save_data + "/" + pn_get.basename.to_s - elsif save_data[-1]=="/" - save = save_data + pn_get.basename.to_s - else - save = save_data - end - if Dir.exist?(Pathname.new(save).dirname) - File.open(save, 'wb') { |fp| fp.write(@response.data) } - else - @logger.fatal "The folder #{Pathname.new(save).dirname} doesn't exist" - end - end - return @response - rescue Exception => stack - @logger.fatal stack - return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } - end - end - - ###################################################### - # Post data to path - # @param arguments [Hash] containing at least keys :data and :path. - # In case :data not supplied and :data_examples array supplied, it will be taken the first example as :data. - # @param arguments [Array] - # path (string). - # data (json data for example). - # additional_headers (Hash key=>value). - # @return [Hash] response - # Including at least the symbol keys: - # :data = the response data body. - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # data, message and code can also be accessed as attributes like .message .code .data. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } - # @example - # resp = @http.post(Requests::Customer.update_customer) - # assert resp.code == 201 - # @example - # resp = http.post( { - # path: "/api/users", - # data: {name: "morpheus", job: "leader"} - # } ) - # pp resp.data.json - ###################################################### - def post(*arguments) - begin - path, data, headers_t = manage_request(*arguments) - @start_time = Time.now if @start_time.nil? - if arguments.size > 0 and arguments[0].kind_of?(Hash) - arg = arguments[0] - if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) - data = "" - if arg[:mock_response].keys.include?(:data) - data = arg[:mock_response][:data] - if data.kind_of?(Hash) #to json - begin - require "json" - data = data.to_json - rescue - @logger.fatal "There was a problem converting to json: #{data}" - end - end - end - @logger.warn "Pay attention!!! This is a mock response:" - @start_time_net = Time.now if @start_time_net.nil? - manage_response(arg[:mock_response], data.to_s) - return @response - end - end - - begin - @start_time_net = Time.now if @start_time_net.nil? - if headers_t["Content-Type"] == "multipart/form-data" - require "net/http/post/multipart" - headers_t.each { |key, value| - arguments[0][:data].add_field(key, value) #add to Headers - } - resp = @http.request(arguments[0][:data]) - elsif headers_t["Content-Type"].to_s.include?("application/x-www-form-urlencoded") - encoded_form = URI.encode_www_form(arguments[0][:data]) - resp = @http.request_post(path, encoded_form, headers_t) - data = resp.body - else - resp = @http.post(path, data, headers_t) - #todo: do it also for forms and multipart - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - resp = @http.post(path, data, headers_t) - end - end - data = resp.body - end - rescue Exception => stack - @logger.warn stack - if !@timeout.nil? and (Time.now - @start_time_net) > @timeout - @logger.warn "The connection seems to be closed in the host machine. Timeout." - return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } - else - @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" - @http.finish() - @http.start() - @start_time_net = Time.now if @start_time_net.nil? - @headers_orig.each {|k,v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k)} - resp, data = @http.post(path, data, headers_t) - end - end - manage_response(resp, data) - if @auto_redirect and @response[:code].to_i >= 300 and @response[:code].to_i < 400 and @response.include?(:location) - if @num_redirects <= 30 - @num_redirects += 1 - current_server = "http" - current_server += "s" if @ssl == true - current_server += "://#{@host}" - location = @response[:location].gsub(current_server, "") - @logger.info "(#{@num_redirects}) Redirecting NiceHttp to #{location}" - get(location) - else - @logger.fatal "(#{@num_redirects}) Maximum number of redirections for a single request reached. Be sure everything is correct, it seems there is a non ending loop" - @num_redirects = 0 - end - else - @num_redirects = 0 - end - return @response - rescue Exception => stack - @logger.fatal stack - return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } - end - end - - ###################################################### - # Put data to path - # @param arguments [Hash] containing at least keys :data and :path. - # In case :data not supplied and :data_examples array supplied, it will be taken the first example as :data. - # @param arguments [Array] - # path (string). - # data (json data for example). - # additional_headers (Hash key=>value). - # @return [Hash] response - # Including at least the symbol keys: - # :data = the response data body. - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # data, message and code can also be accessed as attributes like .message .code .data. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } - # @example - # resp = @http.put(Requests::Customer.remove_phone) - ###################################################### - def put(*arguments) - begin - path, data, headers_t = manage_request(*arguments) - @start_time = Time.now if @start_time.nil? - if arguments.size > 0 and arguments[0].kind_of?(Hash) - arg = arguments[0] - if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) - data = "" - if arg[:mock_response].keys.include?(:data) - data = arg[:mock_response][:data] - if data.kind_of?(Hash) #to json - begin - require "json" - data = data.to_json - rescue - @logger.fatal "There was a problem converting to json: #{data}" - end - end - end - @logger.warn "Pay attention!!! This is a mock response:" - @start_time_net = Time.now if @start_time_net.nil? - manage_response(arg[:mock_response], data.to_s) - return @response - end - end - - begin - @start_time_net = Time.now if @start_time_net.nil? - resp = @http.send_request("PUT", path, data, headers_t) - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - resp = @http.send_request("PUT", path, data, headers_t) - end - end - data = resp.body - rescue Exception => stack - @logger.warn stack - if !@timeout.nil? and (Time.now - @start_time_net) > @timeout - @logger.warn "The connection seems to be closed in the host machine. Timeout." - return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } - else - @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" - @http.finish() - @http.start() - @headers_orig.each {|k,v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k)} - @start_time_net = Time.now if @start_time_net.nil? - resp, data = @http.send_request("PUT", path, data, headers_t) - end - end - manage_response(resp, data) - - return @response - rescue Exception => stack - @logger.fatal stack - return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } - end - end - - ###################################################### - # Patch data to path - # - # @param arguments [Hash] containing at least keys :data and :path. - # In case :data not supplied and :data_examples array supplied, it will be taken the first example as :data. - # @param arguments [Array] - # path (string). - # data (json data for example). - # additional_headers (Hash key=>value). - # @return [Hash] response - # Including at least the symbol keys: - # :data = the response data body. - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # data, message and code can also be accessed as attributes like .message .code .data. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } - # @example - # resp = @http.patch(Requests::Customer.unrelease_account) - ###################################################### - def patch(*arguments) - begin - path, data, headers_t = manage_request(*arguments) - @start_time = Time.now if @start_time.nil? - if arguments.size > 0 and arguments[0].kind_of?(Hash) - arg = arguments[0] - if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) - data = "" - if arg[:mock_response].keys.include?(:data) - data = arg[:mock_response][:data] - if data.kind_of?(Hash) #to json - begin - require "json" - data = data.to_json - rescue - @logger.fatal "There was a problem converting to json: #{data}" - end - end - end - @logger.warn "Pay attention!!! This is a mock response:" - @start_time_net = Time.now if @start_time_net.nil? - manage_response(arg[:mock_response], data.to_s) - return @response - end - end - - begin - @start_time_net = Time.now if @start_time_net.nil? - resp = @http.patch(path, data, headers_t) - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - resp = @http.patch(path, data, headers_t) - end - end - data = resp.body - rescue Exception => stack - @logger.warn stack - if !@timeout.nil? and (Time.now - @start_time_net) > @timeout - @logger.warn "The connection seems to be closed in the host machine. Timeout." - return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } - else - @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" - @http.finish() - @http.start() - @headers_orig.each {|k,v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k)} - @start_time_net = Time.now if @start_time_net.nil? - resp, data = @http.patch(path, data, headers_t) - end - end - manage_response(resp, data) - if @auto_redirect and @response[:code].to_i >= 300 and @response[:code].to_i < 400 and @response.include?(:location) - if @num_redirects <= 30 - @num_redirects += 1 - current_server = "http" - current_server += "s" if @ssl == true - current_server += "://#{@host}" - location = @response[:location].gsub(current_server, "") - @logger.info "(#{@num_redirects}) Redirecting NiceHttp to #{location}" - get(location) - else - @logger.fatal "(#{@num_redirects}) Maximum number of redirections for a single request reached. Be sure everything is correct, it seems there is a non ending loop" - @num_redirects = 0 - end - else - @num_redirects = 0 - end - return @response - rescue Exception => stack - @logger.fatal stack - return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } - end - end - - ###################################################### - # Delete an existing resource - # @param argument [Hash, String] hash containing at least key :path or a string with the path - # - # @return [Hash] response - # Including at least the symbol keys: - # :data = the response data body. - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # data, message and code can also be accessed as attributes like .message .code .data. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } - # @example - # resp = @http.delete(Requests::Customer.remove_session) - # assert resp.code == 204 - ###################################################### - def delete(argument) - begin - if argument.kind_of?(String) - argument = { :path => argument } - end - path, data, headers_t = manage_request(argument) - @start_time = Time.now if @start_time.nil? - if argument.kind_of?(Hash) - arg = argument - if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) - data = "" - if arg[:mock_response].keys.include?(:data) - data = arg[:mock_response][:data] - if data.kind_of?(Hash) #to json - begin - require "json" - data = data.to_json - rescue - @logger.fatal "There was a problem converting to json: #{data}" - end - end - end - @logger.warn "Pay attention!!! This is a mock response:" - @start_time_net = Time.now if @start_time_net.nil? - manage_response(arg[:mock_response], data.to_s) - return @response - end - end - - begin - @start_time_net = Time.now if @start_time_net.nil? - if data.to_s == "" - resp = @http.delete(path, headers_t) - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - resp = @http.delete(path, headers_t) - end - end - else - request = Net::HTTP::Delete.new(path, headers_t) - request.body = data - resp = @http.request(request) - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - request = Net::HTTP::Delete.new(path, headers_t) - request.body = data - resp = @http.request(request) - end - end - end - data = resp.body - rescue Exception => stack - @logger.warn stack - if !@timeout.nil? and (Time.now - @start_time_net) > @timeout - @logger.warn "The connection seems to be closed in the host machine. Timeout." - return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } - else - @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" - @http.finish() - @http.start() - @headers_orig.each {|k,v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k)} - @start_time_net = Time.now if @start_time_net.nil? - resp, data = @http.delete(path, headers_t) - end - end - manage_response(resp, data) - - return @response - rescue Exception => stack - @logger.fatal stack - return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } - end - end - - ###################################################### - # Implementation of the http HEAD method. - # Asks for the response identical to the one that would correspond to a GET request, but without the response body. - # This is useful for retrieving meta-information written in response headers, without having to transport the entire content. - # @param argument [Hash, String] hash containing at least key :path or directly an string with the path - # - # @return [Hash] response - # Including at least the symbol keys: - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # message and code can also be accessed as attributes like .message .code. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil } - ###################################################### - def head(argument) - begin - if argument.kind_of?(String) - argument = { :path => argument } - end - path, data, headers_t = manage_request(argument) - @start_time = Time.now if @start_time.nil? - if argument.kind_of?(Hash) - arg = argument - if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) - @logger.warn "Pay attention!!! This is a mock response:" - @start_time_net = Time.now if @start_time_net.nil? - manage_response(arg[:mock_response], "") - return @response - end - end - - begin - @start_time_net = Time.now if @start_time_net.nil? - resp = @http.head(path, headers_t) - if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) - try = false - @headers_orig.each do |k,v| - if v.is_a?(Proc) and headers_t.key?(k) - try = true - headers_t[k] = v.call - end - end - if try - @logger.warn "Not authorized. Trying to generate a new token." - resp = @http.head(path, headers_t) - end - end - data = resp.body - rescue Exception => stack - @logger.warn stack - if !@timeout.nil? and (Time.now - @start_time_net) > @timeout - @logger.warn "The connection seems to be closed in the host machine. Timeout." - return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } - else - @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" - @http.finish() - @http.start() - @headers_orig.each {|k,v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k)} - @start_time_net = Time.now if @start_time_net.nil? - resp, data = @http.head(path, headers_t) - end - end - manage_response(resp, data) - return @response - rescue Exception => stack - @logger.fatal stack - return { fatal_error: stack.to_s, code: nil, message: nil } - end - end - - ###################################################### - # It will send the request depending on the :method declared on the request hash - # Take a look at https://github.com/MarioRuiz/Request-Hash - # - # @param request_hash [Hash] containing at least key :path and :method. The methods that are accepted are: :get, :head, :post, :put, :delete, :patch - # - # @return [Hash] response - # Including at least the symbol keys: - # :data = the response data body. - # :message = plain text response. - # :code = code response (200=ok,500=wrong...). - # All keys in response are lowercase. - # data, message and code can also be accessed as attributes like .message .code .data. - # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } - # @example - # resp = @http.send_request Requests::Customer.remove_session - # assert resp.code == 204 - ###################################################### - def send_request(request_hash) - unless request_hash.is_a?(Hash) and request_hash.key?(:method) and request_hash.key?(:path) and - request_hash[:method].is_a?(Symbol) and - [:get, :head, :post, :put, :delete, :patch].include?(request_hash[:method]) - message = "send_request: it needs to be supplied a Request Hash that includes a :method and :path. " - message += "Supported methods: :get, :head, :post, :put, :delete, :patch" - @logger.fatal message - return { fatal_error: message, code: nil, message: nil } - else - case request_hash[:method] - when :get - resp = get request_hash - when :post - resp = post request_hash - when :head - resp = head request_hash - when :put - resp = put request_hash - when :delete - resp = delete request_hash - when :patch - resp = patch request_hash - end - return resp - end - end -end diff --git a/lib/nice_http/inherited.rb b/lib/nice_http/inherited.rb new file mode 100644 index 0000000..2105bd5 --- /dev/null +++ b/lib/nice_http/inherited.rb @@ -0,0 +1,8 @@ +class NiceHttp + ###################################################### + # If inheriting from NiceHttp class + ###################################################### + def self.inherited(subclass) + subclass.reset! + end +end diff --git a/lib/nice_http/initialize.rb b/lib/nice_http/initialize.rb new file mode 100644 index 0000000..393eaf7 --- /dev/null +++ b/lib/nice_http/initialize.rb @@ -0,0 +1,233 @@ +class NiceHttp + ###################################################### + # Creates a new http connection. + # + # @param args [] If no parameter supplied, by default will access how is setup on defaults + # @example + # http = NiceHttp.new() + # @param args [String]. The url to create the connection. + # @example + # http = NiceHttp.new("https://www.example.com") + # @example + # http = NiceHttp.new("example.com:8999") + # @example + # http = NiceHttp.new("localhost:8322") + # @param args [Hash] containing these possible keys: + # 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) + # log_path -- string with path for the logs, empty string (default) + # log -- :no, :screen, :file, :fix_file (default). + # log_headers -- :all, :none, :partial (default). + # A string with a path can be supplied. + # If :fix_file: nice_http.log + # In case :file it will be generated a log file with name: nice_http_YY-mm-dd-HHMMSS.log + # proxy_host + # proxy_port + # @example + # http2 = NiceHttp.new( host: "reqres.in", port: 443, ssl: true ) + # @example + # my_server = {host: "example.com", + # port: 80, + # headers: {"api-key": "zdDDdjkck"} + # } + # http3 = NiceHttp.new my_server + ###################################################### + def initialize(args = {}) + require "net/http" + require "net/https" + @host = self.class.host + @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 + @log = self.class.log + @log_path = self.class.log_path + @log_headers = self.class.log_headers + @proxy_host = self.class.proxy_host + @proxy_port = self.class.proxy_port + @use_mocks = self.class.use_mocks + @auto_redirect = false #set it up at the end of initialize + auto_redirect = self.class.auto_redirect + @num_redirects = 0 + @create_stats = self.class.create_stats + @capture = self.class.capture + + #todo: set only the cookies for the current domain + #key: path, value: hash with key is the name of the cookie and value the value + # we set the default value for non existing keys to empty Hash {} so in case of merge there is no problem + @cookies = Hash.new { |h, k| h[k] = {} } + + if args.is_a?(String) + uri = URI.parse(args) + @host = uri.host unless uri.host.nil? + @port = uri.port unless uri.port.nil? + @ssl = true if !uri.scheme.nil? && (uri.scheme == "https") + @prepath = uri.path unless uri.path == "/" + elsif args.is_a?(Hash) && !args.keys.empty? + @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) + @log = args[:log] if args.keys.include?(:log) + @log_path = args[:log_path] if args.keys.include?(:log_path) + @log_headers = args[:log_headers] if args.keys.include?(:log_headers) + @proxy_host = args[:proxy_host] if args.keys.include?(:proxy_host) + @proxy_port = args[:proxy_port] if args.keys.include?(:proxy_port) + @use_mocks = args[:use_mocks] if args.keys.include?(:use_mocks) + auto_redirect = args[:auto_redirect] if args.keys.include?(:auto_redirect) + end + + log_filename = "" + if @log.kind_of?(String) or @log == :fix_file or @log == :file or @log == :file_run + if @log.kind_of?(String) + log_filename = @log.dup + unless log_filename.start_with?(".") + if caller.first.start_with?(Dir.pwd) + folder = File.dirname(caller.first.scan(/(.+):\d/).join) + else + folder = File.dirname("#{Dir.pwd}/#{caller.first.scan(/(.+):\d/).join}") + end + folder += "/" unless log_filename.start_with?("/") or log_filename.match?(/^\w+:/) + log_filename = folder + log_filename + end + require "fileutils" + FileUtils.mkdir_p File.dirname(log_filename) + unless Dir.exist?(File.dirname(log_filename)) + @logger = Logger.new nil + raise InfoMissing, :log, "Wrong directory specified for logs.\n" + end + elsif @log == :fix_file + log_filename = "nice_http.log" + elsif @log == :file + log_filename = "nice_http_#{Time.now.strftime("%Y-%m-%d-%H%M%S")}.log" + elsif @log == :file_run + log_filename = "#{caller.first.scan(/(.+):\d/).join}.log" + end + if Thread.current.name.to_s != "" + log_filename.gsub!(/\.log$/, "_#{Thread.current.name}.log") + end + unless @log_path.to_s == "" + log_filename.gsub!(Dir.pwd, ".") + dpath = @log_path.split("/") + dfile = log_filename.split("/") + log_filenamepath = "" + dfile.each_with_index do |d, i| + if d == dpath[i] + log_filenamepath << "#{d}/" + else + log_filename = @log_path + "#{log_filename.gsub(/^#{log_filenamepath}/, "")}" + break + end + end + log_filename = "./#{log_filename}" unless log_filename[0..1] == "./" + log_filename = ".#{log_filename}" unless log_filename[0] == "." + + unless File.exist?(log_filename) + require "fileutils" + FileUtils.mkdir_p(File.dirname(log_filename)) + end + end + + if self.class.log_files.key?(log_filename) and File.exist?(log_filename) + @logger = self.class.log_files[log_filename] + else + begin + f = File.new(log_filename, "w") + f.sync = true + @logger = Logger.new f + rescue Exception => stack + @logger = Logger.new nil + raise InfoMissing, :log + end + self.class.log_files[log_filename] = @logger + end + elsif @log == :screen + @logger = Logger.new STDOUT + elsif @log == :no + @logger = Logger.new nil + else + raise InfoMissing, :log + end + @logger.level = Logger::INFO + + if @host.to_s != "" and (@host.start_with?("http:") or @host.start_with?("https:")) + uri = URI.parse(@host) + @host = uri.host unless uri.host.nil? + @port = uri.port unless uri.port.nil? + @ssl = true if !uri.scheme.nil? && (uri.scheme == "https") + @prepath = uri.path unless uri.path == "/" + end + 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) + raise InfoMissing, :headers unless @headers.is_a?(Hash) + raise InfoMissing, :values_for unless @values_for.is_a?(Hash) + raise InfoMissing, :log_headers unless [:all, :none, :partial].include?(@log_headers) + + begin + if !@proxy_host.nil? && !@proxy_port.nil? + @http = Net::HTTP::Proxy(@proxy_host, @proxy_port).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 + 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}, timeout:#{@timeout}, mode:#{@mode}, proxy_host: #{@proxy_host.to_s()}, proxy_port: #{@proxy_port.to_s()} " + + @logger.info(log_message) + @message_server += " Http connection: " + if @ssl + @message_server += "https://" + else + @message_server += "http://" + end + @message_server += "#{@host}:#{@port}" + if @proxy_host.to_s != "" + @message_server += " proxy:#{@proxy_host}:#{@proxy_port}" + end + @auto_redirect = auto_redirect + # for the case we have headers following nice_hash implementation + @headers_orig = @headers.dup + @headers = @headers.generate + + self.class.active += 1 + self.class.connections.push(self) + rescue Exception => stack + puts stack + @logger.fatal stack + raise stack + end + end +end diff --git a/lib/nice_http/manage/create_stats.rb b/lib/nice_http/manage/create_stats.rb new file mode 100644 index 0000000..4c45b9a --- /dev/null +++ b/lib/nice_http/manage/create_stats.rb @@ -0,0 +1,64 @@ +module NiceHttpManageResponse + private + + def create_stats(resp) + # all + set_stats(self.class.stats[:all]) + # all method + unless self.class.stats[:all][:method].key?(@prev_request[:method]) + self.class.stats[:all][:method][@prev_request[:method]] = { + response: {}, + } + end + set_stats(self.class.stats[:all][:method][@prev_request[:method]]) + # all method response + unless self.class.stats[:all][:method][@prev_request[:method]][:response].key?(resp.code) + self.class.stats[:all][:method][@prev_request[:method]][:response][resp.code] = {} + end + set_stats(self.class.stats[:all][:method][@prev_request[:method]][:response][resp.code]) + + # server + server = "#{@host}:#{@port}" + unless self.class.stats[:path].key?(server) + self.class.stats[:path][server] = {} + end + set_stats(self.class.stats[:path][server]) + # server path + unless self.class.stats[:path][server].key?(@prev_request[:path]) + self.class.stats[:path][server][@prev_request[:path]] = { method: {} } + end + set_stats(self.class.stats[:path][server][@prev_request[:path]]) + # server path method + unless self.class.stats[:path][server][@prev_request[:path]][:method].key?(@prev_request[:method]) + self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]] = { + response: {}, + } + end + set_stats(self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]]) + # server path method response + unless self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response].key?(resp.code) + self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response][resp.code] = {} + end + set_stats(self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response][resp.code]) + + if @prev_request.key?(:name) + # name + unless self.class.stats[:name].key?(@prev_request[:name]) + self.class.stats[:name][@prev_request[:name]] = { method: {} } + end + set_stats(self.class.stats[:name][@prev_request[:name]]) + # name method + unless self.class.stats[:name][@prev_request[:name]][:method].key?(@prev_request[:method]) + self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]] = { + response: {}, + } + end + set_stats(self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]]) + # name method response + unless self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response].key?(resp.code) + self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response][resp.code] = {} + end + set_stats(self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response][resp.code]) + end + end +end diff --git a/lib/nice_http/manage_request.rb b/lib/nice_http/manage/request.rb similarity index 97% rename from lib/nice_http/manage_request.rb rename to lib/nice_http/manage/request.rb index 6458bd2..1444155 100644 --- a/lib/nice_http/manage_request.rb +++ b/lib/nice_http/manage/request.rb @@ -112,7 +112,7 @@ def manage_request(*arguments) !headers_t["Content-Type"][/application\/jxml/].nil?) if arguments[0].include?(:values_for) arguments[0][:values_for].each { |key, value| - #todo: implement set_nested + #todo: implement set_nested data = NiceHttpUtils.set_value_xml_tag(key.to_s(), data, value.to_s(), true) } end @@ -121,7 +121,7 @@ def manage_request(*arguments) if data.kind_of?(String) if arguments[0].include?(:values_for) arguments[0][:values_for].each { |key, value| - #todo: implement set_nested + #todo: implement set_nested data.gsub!(/"(#{key})":\s*"([^"]*)"/, '"\1": "' + value + '"') # "key":"value" data.gsub!(/(#{key}):\s*"([^"]*)"/, '\1: "' + value + '"') # key:"value" data.gsub!(/(#{key}):\s*'([^']*)'/, '\1: \'' + value + "'") # key:'value' @@ -134,7 +134,7 @@ def manage_request(*arguments) #lambdas on data only supports on root of the hash data.each do |k, v| if v.is_a?(Proc) - data[k] = v.call + data[k] = v.call end end if arguments[0].include?(:values_for) @@ -142,7 +142,7 @@ def manage_request(*arguments) end data = data.to_json() elsif data.kind_of?(Array) - #todo: implement set_nested + #todo: implement set_nested data_arr = Array.new() data.each_with_index { |row, indx| if arguments[0].include?(:values_for) and (row.is_a?(Array) or row.is_a?(Hash)) @@ -218,7 +218,7 @@ def manage_request(*arguments) headers_t.each do |k, v| # for lambdas if v.is_a?(Proc) - headers_t[k] = v.call + headers_t[k] = v.call end end @request[:headers] = headers_t @@ -245,11 +245,11 @@ def manage_request(*arguments) headers_t.each { |key, val| headers_ts += key.to_s + ":" + "''" + ", " } elsif @log_headers == :partial @logger.info "Just the last 10 characters on header values since option log_headers is set to :partial" - headers_t.each { |key, val| - if val.to_s.size>10 - headers_ts += key.to_s + ": ..." + (val.to_s[-10..-1] || val.to_s) + ", " + headers_t.each { |key, val| + if val.to_s.size > 10 + headers_ts += key.to_s + ": ..." + (val.to_s[-10..-1] || val.to_s) + ", " else - headers_ts += key.to_s + ":" + (val.to_s[-10..-1] || val.to_s) + ", " + headers_ts += key.to_s + ":" + (val.to_s[-10..-1] || val.to_s) + ", " end } else diff --git a/lib/nice_http/manage_response.rb b/lib/nice_http/manage/response.rb similarity index 63% rename from lib/nice_http/manage_response.rb rename to lib/nice_http/manage/response.rb index 572df6e..6e5c26d 100644 --- a/lib/nice_http/manage_response.rb +++ b/lib/nice_http/manage/response.rb @@ -31,7 +31,7 @@ def manage_response(resp, data) begin # this is to be able to access all keys as symbols on Ruby < 2.6.0 - if RUBY_VERSION < '2.6.0' + if RUBY_VERSION < "2.6.0" new_resp = Hash.new() resp.each { |key, value| if key.kind_of?(String) @@ -107,11 +107,11 @@ def manage_response(resp, data) if key.kind_of?(Symbol) if key == :code or key == :data or key == :header or key == :message if key == :data and !@response[:'content-type'].to_s.include?("text/html") - if key == :data and - (!@response[:'content-type'].include?('text') and - !@response[:'content-type'].include?('json') and - !@response[:'content-type'].include?('xml') and - !@response[:'content-type'].include?('charset=utf-8')) + if key == :data and + (!@response[:'content-type'].include?("text") and + !@response[:'content-type'].include?("json") and + !@response[:'content-type'].include?("xml") and + !@response[:'content-type'].include?("charset=utf-8")) message += "\n data: It's not text data so won't be in the logs." else begin @@ -192,109 +192,4 @@ def manage_response(resp, data) end @start_time = nil end - - private - - def set_stats(hash) - unless hash.key?(:num_requests) - # to add to the end the previous keys so num_requests and time_elapsed come first - keys = hash.keys - hash.keys.each do |k| - hash.delete(k) - end - - hash[:num_requests] = 0 - hash[:started] = @start_time - hash[:finished] = @start_time - hash[:real_time_elapsed] = 0 - hash[:time_elapsed] = { - total: 0, - maximum: 0, - minimum: 100000, - average: 0, - } - - # to add to the end the previous keys so num_requests and time_elapsed come first - keys.each do |k| - hash[k] = {} - end - end - hash[:num_requests] += 1 - hash[:started] = hash[:finished] = @start_time if hash[:started].nil? - - if @start_time < hash[:finished] - hash[:real_time_elapsed] += (@finish_time - hash[:finished]) - else - hash[:real_time_elapsed] += (@finish_time - @start_time) - end - hash[:finished] = @finish_time - - hash[:time_elapsed][:total] += @response[:time_elapsed] - hash[:time_elapsed][:maximum] = @response[:time_elapsed] if @response[:time_elapsed] > hash[:time_elapsed][:maximum] - hash[:time_elapsed][:minimum] = @response[:time_elapsed] if @response[:time_elapsed] < hash[:time_elapsed][:minimum] - hash[:time_elapsed][:average] = hash[:time_elapsed][:total] / hash[:num_requests] - end - - private - - def create_stats(resp) - # all - set_stats(self.class.stats[:all]) - # all method - unless self.class.stats[:all][:method].key?(@prev_request[:method]) - self.class.stats[:all][:method][@prev_request[:method]] = { - response: {}, - } - end - set_stats(self.class.stats[:all][:method][@prev_request[:method]]) - # all method response - unless self.class.stats[:all][:method][@prev_request[:method]][:response].key?(resp.code) - self.class.stats[:all][:method][@prev_request[:method]][:response][resp.code] = {} - end - set_stats(self.class.stats[:all][:method][@prev_request[:method]][:response][resp.code]) - - # server - server = "#{@host}:#{@port}" - unless self.class.stats[:path].key?(server) - self.class.stats[:path][server] = {} - end - set_stats(self.class.stats[:path][server]) - # server path - unless self.class.stats[:path][server].key?(@prev_request[:path]) - self.class.stats[:path][server][@prev_request[:path]] = { method: {} } - end - set_stats(self.class.stats[:path][server][@prev_request[:path]]) - # server path method - unless self.class.stats[:path][server][@prev_request[:path]][:method].key?(@prev_request[:method]) - self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]] = { - response: {}, - } - end - set_stats(self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]]) - # server path method response - unless self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response].key?(resp.code) - self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response][resp.code] = {} - end - set_stats(self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response][resp.code]) - - if @prev_request.key?(:name) - # name - unless self.class.stats[:name].key?(@prev_request[:name]) - self.class.stats[:name][@prev_request[:name]] = { method: {} } - end - set_stats(self.class.stats[:name][@prev_request[:name]]) - # name method - unless self.class.stats[:name][@prev_request[:name]][:method].key?(@prev_request[:method]) - self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]] = { - response: {}, - } - end - set_stats(self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]]) - # name method response - unless self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response].key?(resp.code) - self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response][resp.code] = {} - end - set_stats(self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response][resp.code]) - end - end end diff --git a/lib/nice_http/manage/set_stats.rb b/lib/nice_http/manage/set_stats.rb new file mode 100644 index 0000000..ccd2162 --- /dev/null +++ b/lib/nice_http/manage/set_stats.rb @@ -0,0 +1,43 @@ +module NiceHttpManageResponse + private + + def set_stats(hash) + unless hash.key?(:num_requests) + # to add to the end the previous keys so num_requests and time_elapsed come first + keys = hash.keys + hash.keys.each do |k| + hash.delete(k) + end + + hash[:num_requests] = 0 + hash[:started] = @start_time + hash[:finished] = @start_time + hash[:real_time_elapsed] = 0 + hash[:time_elapsed] = { + total: 0, + maximum: 0, + minimum: 100000, + average: 0, + } + + # to add to the end the previous keys so num_requests and time_elapsed come first + keys.each do |k| + hash[k] = {} + end + end + hash[:num_requests] += 1 + hash[:started] = hash[:finished] = @start_time if hash[:started].nil? + + if @start_time < hash[:finished] + hash[:real_time_elapsed] += (@finish_time - hash[:finished]) + else + hash[:real_time_elapsed] += (@finish_time - @start_time) + end + hash[:finished] = @finish_time + + hash[:time_elapsed][:total] += @response[:time_elapsed] + hash[:time_elapsed][:maximum] = @response[:time_elapsed] if @response[:time_elapsed] > hash[:time_elapsed][:maximum] + hash[:time_elapsed][:minimum] = @response[:time_elapsed] if @response[:time_elapsed] < hash[:time_elapsed][:minimum] + hash[:time_elapsed][:average] = hash[:time_elapsed][:total] / hash[:num_requests] + end +end diff --git a/lib/nice_http/methods/delete.rb b/lib/nice_http/methods/delete.rb new file mode 100644 index 0000000..11536fe --- /dev/null +++ b/lib/nice_http/methods/delete.rb @@ -0,0 +1,108 @@ +module NiceHttpHttpMethods + + ###################################################### + # Delete an existing resource + # @param argument [Hash, String] hash containing at least key :path or a string with the path + # + # @return [Hash] response + # Including at least the symbol keys: + # :data = the response data body. + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # data, message and code can also be accessed as attributes like .message .code .data. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } + # @example + # resp = @http.delete(Requests::Customer.remove_session) + # assert resp.code == 204 + ###################################################### + def delete(argument) + begin + if argument.kind_of?(String) + argument = { :path => argument } + end + path, data, headers_t = manage_request(argument) + @start_time = Time.now if @start_time.nil? + if argument.kind_of?(Hash) + arg = argument + if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) + data = "" + if arg[:mock_response].keys.include?(:data) + data = arg[:mock_response][:data] + if data.kind_of?(Hash) #to json + begin + require "json" + data = data.to_json + rescue + @logger.fatal "There was a problem converting to json: #{data}" + end + end + end + @logger.warn "Pay attention!!! This is a mock response:" + @start_time_net = Time.now if @start_time_net.nil? + manage_response(arg[:mock_response], data.to_s) + return @response + end + end + + begin + @start_time_net = Time.now if @start_time_net.nil? + if data.to_s == "" + resp = @http.delete(path, headers_t) + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + resp = @http.delete(path, headers_t) + end + end + else + request = Net::HTTP::Delete.new(path, headers_t) + request.body = data + resp = @http.request(request) + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + request = Net::HTTP::Delete.new(path, headers_t) + request.body = data + resp = @http.request(request) + end + end + end + data = resp.body + rescue Exception => stack + @logger.warn stack + if !@timeout.nil? and (Time.now - @start_time_net) > @timeout + @logger.warn "The connection seems to be closed in the host machine. Timeout." + return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } + else + @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" + @http.finish() + @http.start() + @headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) } + @start_time_net = Time.now if @start_time_net.nil? + resp, data = @http.delete(path, headers_t) + end + end + manage_response(resp, data) + + return @response + rescue Exception => stack + @logger.fatal stack + return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } + end + end +end diff --git a/lib/nice_http/methods/get.rb b/lib/nice_http/methods/get.rb new file mode 100644 index 0000000..ff20757 --- /dev/null +++ b/lib/nice_http/methods/get.rb @@ -0,0 +1,159 @@ +module NiceHttpHttpMethods + + ###################################################### + # Get data from path + # + # @param arg [Hash, String] hash containing at least key :path or a string with the path + # @param save_data [String] the path or path and file name where we want to save the response data + # + # @return [Hash] response. + # Including at least the symbol keys: + # :data = the response data body. + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # data, message and code can also be accessed as attributes like .message .code .data. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } + # + # @example + # resp = @http.get(Requests::Customer.get_profile) + # assert resp.code == 200 + # @example + # resp = @http.get("/customers/1223") + # assert resp.message == "OK" + # @example + # resp = @http.get("/assets/images/logo.png", save_data: './tmp/') + # @example + # resp = @http.get("/assets/images/logo.png", save_data: './tmp/example.png') + ###################################################### + def get(arg, save_data: "") + begin + path, data, headers_t = manage_request(arg) + + @start_time = Time.now if @start_time.nil? + if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) + data = "" + if arg[:mock_response].keys.include?(:data) + data = arg[:mock_response][:data] + if data.kind_of?(Hash) #to json + begin + require "json" + data = data.to_json + rescue + @logger.fatal "There was a problem converting to json: #{data}" + end + end + end + @logger.warn "Pay attention!!! This is a mock response:" + @start_time_net = Time.now if @start_time_net.nil? + manage_response(arg[:mock_response], data.to_s) + return @response + end + begin + if path.start_with?("http:") or path.start_with?("https:") #server included on path problably because of a redirection to a different server + require "uri" + uri = URI.parse(path) + ssl = false + ssl = true if path.include?("https:") + + server = "http://" + server = "https://" if path.start_with?("https:") + if uri.port != 443 + server += "#{uri.host}:#{uri.port}" + else + server += "#{uri.host}" + end + + http_redir = nil + self.class.connections.each { |conn| + if conn.host == uri.host and conn.port == uri.port + http_redir = conn + break + end + } + + if !http_redir.nil? + path, data, headers_t = manage_request(arg) + http_redir.cookies.merge!(@cookies) + http_redir.headers.merge!(headers_t) + #todo: remove only the server at the begining in case in query is the server it will be replaced when it should not be + resp = http_redir.get(path.gsub(server, "")) + @response = http_redir.response + else + @logger.warn "It seems like the http connection cannot redirect to #{server} because there is no active connection for that server. You need to create previously one." + end + else + @start_time_net = Time.now if @start_time_net.nil? + resp = @http.get(path, headers_t) + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + resp = @http.get(path, headers_t) + end + end + data = resp.body + manage_response(resp, data) + end + rescue Exception => stack + @logger.warn stack + if !@timeout.nil? and (Time.now - @start_time_net) > @timeout + @logger.warn "The connection seems to be closed in the host machine. Timeout." + return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } + else + @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" + @http.finish() + @http.start() + @start_time_net = Time.now if @start_time_net.nil? + @headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) } + resp = @http.get(path, headers_t) + data = resp.body + manage_response(resp, data) + end + end + if @auto_redirect and @response[:code].to_i >= 300 and @response[:code].to_i < 400 and @response.include?(:location) + if @num_redirects <= 30 + @num_redirects += 1 + current_server = "http" + current_server += "s" if @ssl == true + current_server += "://#{@host}" + location = @response[:location].gsub(current_server, "") + @logger.info "(#{@num_redirects}) Redirecting NiceHttp to #{location}" + get(location) + else + @logger.fatal "(#{@num_redirects}) Maximum number of redirections for a single request reached. Be sure everything is correct, it seems there is a non ending loop" + @num_redirects = 0 + end + else + @num_redirects = 0 + end + if save_data != "" + require "pathname" + pn_get = Pathname.new(path) + + if Dir.exist?(save_data) + save = save_data + "/" + pn_get.basename.to_s + elsif save_data[-1] == "/" + save = save_data + pn_get.basename.to_s + else + save = save_data + end + if Dir.exist?(Pathname.new(save).dirname) + File.open(save, "wb") { |fp| fp.write(@response.data) } + else + @logger.fatal "The folder #{Pathname.new(save).dirname} doesn't exist" + end + end + return @response + rescue Exception => stack + @logger.fatal stack + return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } + end + end +end diff --git a/lib/nice_http/methods/head.rb b/lib/nice_http/methods/head.rb new file mode 100644 index 0000000..d3d8cf7 --- /dev/null +++ b/lib/nice_http/methods/head.rb @@ -0,0 +1,72 @@ +module NiceHttpHttpMethods + + ###################################################### + # Implementation of the http HEAD method. + # Asks for the response identical to the one that would correspond to a GET request, but without the response body. + # This is useful for retrieving meta-information written in response headers, without having to transport the entire content. + # @param argument [Hash, String] hash containing at least key :path or directly an string with the path + # + # @return [Hash] response + # Including at least the symbol keys: + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # message and code can also be accessed as attributes like .message .code. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil } + ###################################################### + def head(argument) + begin + if argument.kind_of?(String) + argument = { :path => argument } + end + path, data, headers_t = manage_request(argument) + @start_time = Time.now if @start_time.nil? + if argument.kind_of?(Hash) + arg = argument + if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) + @logger.warn "Pay attention!!! This is a mock response:" + @start_time_net = Time.now if @start_time_net.nil? + manage_response(arg[:mock_response], "") + return @response + end + end + + begin + @start_time_net = Time.now if @start_time_net.nil? + resp = @http.head(path, headers_t) + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + resp = @http.head(path, headers_t) + end + end + data = resp.body + rescue Exception => stack + @logger.warn stack + if !@timeout.nil? and (Time.now - @start_time_net) > @timeout + @logger.warn "The connection seems to be closed in the host machine. Timeout." + return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } + else + @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" + @http.finish() + @http.start() + @headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) } + @start_time_net = Time.now if @start_time_net.nil? + resp, data = @http.head(path, headers_t) + end + end + manage_response(resp, data) + return @response + rescue Exception => stack + @logger.fatal stack + return { fatal_error: stack.to_s, code: nil, message: nil } + end + end +end diff --git a/lib/nice_http/methods/patch.rb b/lib/nice_http/methods/patch.rb new file mode 100644 index 0000000..debc3e4 --- /dev/null +++ b/lib/nice_http/methods/patch.rb @@ -0,0 +1,103 @@ +module NiceHttpHttpMethods + + ###################################################### + # Patch data to path + # + # @param arguments [Hash] containing at least keys :data and :path. + # In case :data not supplied and :data_examples array supplied, it will be taken the first example as :data. + # @param arguments [Array] + # path (string). + # data (json data for example). + # additional_headers (Hash key=>value). + # @return [Hash] response + # Including at least the symbol keys: + # :data = the response data body. + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # data, message and code can also be accessed as attributes like .message .code .data. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } + # @example + # resp = @http.patch(Requests::Customer.unrelease_account) + ###################################################### + def patch(*arguments) + begin + path, data, headers_t = manage_request(*arguments) + @start_time = Time.now if @start_time.nil? + if arguments.size > 0 and arguments[0].kind_of?(Hash) + arg = arguments[0] + if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) + data = "" + if arg[:mock_response].keys.include?(:data) + data = arg[:mock_response][:data] + if data.kind_of?(Hash) #to json + begin + require "json" + data = data.to_json + rescue + @logger.fatal "There was a problem converting to json: #{data}" + end + end + end + @logger.warn "Pay attention!!! This is a mock response:" + @start_time_net = Time.now if @start_time_net.nil? + manage_response(arg[:mock_response], data.to_s) + return @response + end + end + + begin + @start_time_net = Time.now if @start_time_net.nil? + resp = @http.patch(path, data, headers_t) + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + resp = @http.patch(path, data, headers_t) + end + end + data = resp.body + rescue Exception => stack + @logger.warn stack + if !@timeout.nil? and (Time.now - @start_time_net) > @timeout + @logger.warn "The connection seems to be closed in the host machine. Timeout." + return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } + else + @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" + @http.finish() + @http.start() + @headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) } + @start_time_net = Time.now if @start_time_net.nil? + resp, data = @http.patch(path, data, headers_t) + end + end + manage_response(resp, data) + if @auto_redirect and @response[:code].to_i >= 300 and @response[:code].to_i < 400 and @response.include?(:location) + if @num_redirects <= 30 + @num_redirects += 1 + current_server = "http" + current_server += "s" if @ssl == true + current_server += "://#{@host}" + location = @response[:location].gsub(current_server, "") + @logger.info "(#{@num_redirects}) Redirecting NiceHttp to #{location}" + get(location) + else + @logger.fatal "(#{@num_redirects}) Maximum number of redirections for a single request reached. Be sure everything is correct, it seems there is a non ending loop" + @num_redirects = 0 + end + else + @num_redirects = 0 + end + return @response + rescue Exception => stack + @logger.fatal stack + return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } + end + end +end diff --git a/lib/nice_http/methods/post.rb b/lib/nice_http/methods/post.rb new file mode 100644 index 0000000..9fe27d3 --- /dev/null +++ b/lib/nice_http/methods/post.rb @@ -0,0 +1,122 @@ +module NiceHttpHttpMethods + + ###################################################### + # Post data to path + # @param arguments [Hash] containing at least keys :data and :path. + # In case :data not supplied and :data_examples array supplied, it will be taken the first example as :data. + # @param arguments [Array] + # path (string). + # data (json data for example). + # additional_headers (Hash key=>value). + # @return [Hash] response + # Including at least the symbol keys: + # :data = the response data body. + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # data, message and code can also be accessed as attributes like .message .code .data. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } + # @example + # resp = @http.post(Requests::Customer.update_customer) + # assert resp.code == 201 + # @example + # resp = http.post( { + # path: "/api/users", + # data: {name: "morpheus", job: "leader"} + # } ) + # pp resp.data.json + ###################################################### + def post(*arguments) + begin + path, data, headers_t = manage_request(*arguments) + @start_time = Time.now if @start_time.nil? + if arguments.size > 0 and arguments[0].kind_of?(Hash) + arg = arguments[0] + if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) + data = "" + if arg[:mock_response].keys.include?(:data) + data = arg[:mock_response][:data] + if data.kind_of?(Hash) #to json + begin + require "json" + data = data.to_json + rescue + @logger.fatal "There was a problem converting to json: #{data}" + end + end + end + @logger.warn "Pay attention!!! This is a mock response:" + @start_time_net = Time.now if @start_time_net.nil? + manage_response(arg[:mock_response], data.to_s) + return @response + end + end + + begin + @start_time_net = Time.now if @start_time_net.nil? + if headers_t["Content-Type"] == "multipart/form-data" + require "net/http/post/multipart" + headers_t.each { |key, value| + arguments[0][:data].add_field(key, value) #add to Headers + } + resp = @http.request(arguments[0][:data]) + elsif headers_t["Content-Type"].to_s.include?("application/x-www-form-urlencoded") + encoded_form = URI.encode_www_form(arguments[0][:data]) + resp = @http.request_post(path, encoded_form, headers_t) + data = resp.body + else + resp = @http.post(path, data, headers_t) + #todo: do it also for forms and multipart + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + resp = @http.post(path, data, headers_t) + end + end + data = resp.body + end + rescue Exception => stack + @logger.warn stack + if !@timeout.nil? and (Time.now - @start_time_net) > @timeout + @logger.warn "The connection seems to be closed in the host machine. Timeout." + return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } + else + @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" + @http.finish() + @http.start() + @start_time_net = Time.now if @start_time_net.nil? + @headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) } + resp, data = @http.post(path, data, headers_t) + end + end + manage_response(resp, data) + if @auto_redirect and @response[:code].to_i >= 300 and @response[:code].to_i < 400 and @response.include?(:location) + if @num_redirects <= 30 + @num_redirects += 1 + current_server = "http" + current_server += "s" if @ssl == true + current_server += "://#{@host}" + location = @response[:location].gsub(current_server, "") + @logger.info "(#{@num_redirects}) Redirecting NiceHttp to #{location}" + get(location) + else + @logger.fatal "(#{@num_redirects}) Maximum number of redirections for a single request reached. Be sure everything is correct, it seems there is a non ending loop" + @num_redirects = 0 + end + else + @num_redirects = 0 + end + return @response + rescue Exception => stack + @logger.fatal stack + return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } + end + end +end diff --git a/lib/nice_http/methods/put.rb b/lib/nice_http/methods/put.rb new file mode 100644 index 0000000..1bde8a3 --- /dev/null +++ b/lib/nice_http/methods/put.rb @@ -0,0 +1,87 @@ +module NiceHttpHttpMethods + + ###################################################### + # Put data to path + # @param arguments [Hash] containing at least keys :data and :path. + # In case :data not supplied and :data_examples array supplied, it will be taken the first example as :data. + # @param arguments [Array] + # path (string). + # data (json data for example). + # additional_headers (Hash key=>value). + # @return [Hash] response + # Including at least the symbol keys: + # :data = the response data body. + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # data, message and code can also be accessed as attributes like .message .code .data. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } + # @example + # resp = @http.put(Requests::Customer.remove_phone) + ###################################################### + def put(*arguments) + begin + path, data, headers_t = manage_request(*arguments) + @start_time = Time.now if @start_time.nil? + if arguments.size > 0 and arguments[0].kind_of?(Hash) + arg = arguments[0] + if @use_mocks and arg.kind_of?(Hash) and arg.keys.include?(:mock_response) + data = "" + if arg[:mock_response].keys.include?(:data) + data = arg[:mock_response][:data] + if data.kind_of?(Hash) #to json + begin + require "json" + data = data.to_json + rescue + @logger.fatal "There was a problem converting to json: #{data}" + end + end + end + @logger.warn "Pay attention!!! This is a mock response:" + @start_time_net = Time.now if @start_time_net.nil? + manage_response(arg[:mock_response], data.to_s) + return @response + end + end + + begin + @start_time_net = Time.now if @start_time_net.nil? + resp = @http.send_request("PUT", path, data, headers_t) + if (resp.code == 401 or resp.code == 408) and @headers_orig.values.map(&:class).include?(Proc) + try = false + @headers_orig.each do |k, v| + if v.is_a?(Proc) and headers_t.key?(k) + try = true + headers_t[k] = v.call + end + end + if try + @logger.warn "Not authorized. Trying to generate a new token." + resp = @http.send_request("PUT", path, data, headers_t) + end + end + data = resp.body + rescue Exception => stack + @logger.warn stack + if !@timeout.nil? and (Time.now - @start_time_net) > @timeout + @logger.warn "The connection seems to be closed in the host machine. Timeout." + return { fatal_error: "Net::ReadTimeout", code: nil, message: nil, data: "" } + else + @logger.warn "The connection seems to be closed in the host machine. Trying to reconnect" + @http.finish() + @http.start() + @headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) } + @start_time_net = Time.now if @start_time_net.nil? + resp, data = @http.send_request("PUT", path, data, headers_t) + end + end + manage_response(resp, data) + + return @response + rescue Exception => stack + @logger.fatal stack + return { fatal_error: stack.to_s, code: nil, message: nil, data: "" } + end + end +end diff --git a/lib/nice_http/methods/send_request.rb b/lib/nice_http/methods/send_request.rb new file mode 100644 index 0000000..78cde41 --- /dev/null +++ b/lib/nice_http/methods/send_request.rb @@ -0,0 +1,47 @@ +module NiceHttpHttpMethods + + ###################################################### + # It will send the request depending on the :method declared on the request hash + # Take a look at https://github.com/MarioRuiz/Request-Hash + # + # @param request_hash [Hash] containing at least key :path and :method. The methods that are accepted are: :get, :head, :post, :put, :delete, :patch + # + # @return [Hash] response + # Including at least the symbol keys: + # :data = the response data body. + # :message = plain text response. + # :code = code response (200=ok,500=wrong...). + # All keys in response are lowercase. + # data, message and code can also be accessed as attributes like .message .code .data. + # In case of fatal error returns { fatal_error: "the error description", code: nil, message: nil, data: '' } + # @example + # resp = @http.send_request Requests::Customer.remove_session + # assert resp.code == 204 + ###################################################### + def send_request(request_hash) + unless request_hash.is_a?(Hash) and request_hash.key?(:method) and request_hash.key?(:path) and + request_hash[:method].is_a?(Symbol) and + [:get, :head, :post, :put, :delete, :patch].include?(request_hash[:method]) + message = "send_request: it needs to be supplied a Request Hash that includes a :method and :path. " + message += "Supported methods: :get, :head, :post, :put, :delete, :patch" + @logger.fatal message + return { fatal_error: message, code: nil, message: nil } + else + case request_hash[:method] + when :get + resp = get request_hash + when :post + resp = post request_hash + when :head + resp = head request_hash + when :put + resp = put request_hash + when :delete + resp = delete request_hash + when :patch + resp = patch request_hash + end + return resp + end + end +end diff --git a/lib/nice_http/reset.rb b/lib/nice_http/reset.rb new file mode 100644 index 0000000..20e8e76 --- /dev/null +++ b/lib/nice_http/reset.rb @@ -0,0 +1,49 @@ +class NiceHttp + ###################################################### + # to reset to the original defaults + ###################################################### + def self.reset! + @host = nil + @port = 80 + @ssl = false + @timeout = nil + @headers = {} + @values_for = {} + @debug = false + @log = :fix_file + @log_path = "" + @log_headers = :all + @proxy_host = nil + @proxy_port = nil + @last_request = nil + @request = nil + @requests = nil + @last_response = nil + @request_id = "" + @use_mocks = false + @connections = [] + @active = 0 + @auto_redirect = true + @log_files = {} + @create_stats = false + @stats = { + all: { + num_requests: 0, + started: nil, + finished: nil, + real_time_elapsed: 0, + time_elapsed: { + total: 0, + maximum: 0, + minimum: 1000000, + average: 0, + }, + method: {}, + }, + path: {}, + name: {}, + } + @capture = false + @captured = [] + end +end diff --git a/lib/nice_http/save_stats.rb b/lib/nice_http/save_stats.rb new file mode 100644 index 0000000..b602cbf --- /dev/null +++ b/lib/nice_http/save_stats.rb @@ -0,0 +1,37 @@ +class NiceHttp + ###################################################### + # It will save the NiceHttp.stats on different files, each key of the hash in a different file. + # + # @param file_name [String] path and file name to be used to store the stats. + # In case no one supplied it will be used the value in NiceHttp.log and it will be saved on YAML format. + # In case extension is .yaml will be saved on YAML format. + # In case extension is .json will be saved on JSON format. + # + # @example + # NiceHttp.save_stats + # NiceHttp.save_stats('./stats/my_stats.yaml') + # NiceHttp.save_stats('./stats/my_stats.json') + ###################################################### + def self.save_stats(file_name = "") + if file_name == "" + if self.log.is_a?(String) + file_name = self.log + else + file_name = "./#{self.log_path}nice_http.log" + end + end + require "fileutils" + FileUtils.mkdir_p File.dirname(file_name) + if file_name.match?(/\.json$/) + require "json" + self.stats.keys.each do |key| + File.open("#{file_name.gsub(/.json$/, "_stats_")}#{key}.json", "w") { |file| file.write(self.stats[key].to_json) } + end + else + require "yaml" + self.stats.keys.each do |key| + File.open("#{file_name.gsub(/.\w+$/, "_stats_")}#{key}.yaml", "w") { |file| file.write(self.stats[key].to_yaml) } + end + end + end +end diff --git a/lib/nice_http/utils/basic_authentication.rb b/lib/nice_http/utils/basic_authentication.rb new file mode 100644 index 0000000..09e5ddb --- /dev/null +++ b/lib/nice_http/utils/basic_authentication.rb @@ -0,0 +1,18 @@ +module NiceHttpUtils + ################################################## + # returns the seed for Basic authentication + # @param user [String] + # @param password [String] + # @param strict [Boolean] (default: false) use strict_encode64 if true, if not encode64 + # @return [String] seed string to be used on Authorization key header on a get request + #################################################### + def self.basic_authentication(user:, password:, strict: false) + require "base64" + if strict + seed = "Basic " + Base64.strict_encode64(user + ":" + password) + else + seed = "Basic " + Base64.encode64(user + ":" + password) + end + return seed + end +end diff --git a/lib/nice_http/utils.rb b/lib/nice_http/utils/get_value_xml_tag.rb similarity index 50% rename from lib/nice_http/utils.rb rename to lib/nice_http/utils/get_value_xml_tag.rb index 8206cfd..d0d4aab 100644 --- a/lib/nice_http/utils.rb +++ b/lib/nice_http/utils/get_value_xml_tag.rb @@ -60,49 +60,4 @@ def self.get_value_xml_tag(tag_name, xml_string, take_off_prefix = false) return ret end end - - ################################################## - # set a value on xml tag - # @param tag_name [String] - # @param xml_string [String] - # @param value [String] - # @param take_off_prefix [Boolean] (optional). true, false(default) - # @return [String] with the new value - #################################################### - def self.set_value_xml_tag(tag_name, xml_string, value, take_off_prefix = false) - tag_name = tag_name.to_s - if take_off_prefix - i = tag_name.index(":") - tag_name = tag_name[i + 1..tag_name.length] unless i.nil? - end - if xml_string.to_s != "" - if take_off_prefix - old_value = NiceHttpUtils.get_value_xml_tag(tag_name, xml_string.dup, true) - xml_string.gsub!(/:#{tag_name}>#{Regexp.escape(old_value)}<\//i, ":" + tag_name + ">" + value + "#{Regexp.escape(old_value)}<\//i, "<" + tag_name + ">" + value + ".*<\/#{tag_name}>/i, "<" + tag_name + ">" + value + "") - end - return xml_string - else - return "" - end - end - - ################################################## - # returns the seed for Basic authentication - # @param user [String] - # @param password [String] - # @param strict [Boolean] (default: false) use strict_encode64 if true, if not encode64 - # @return [String] seed string to be used on Authorization key header on a get request - #################################################### - def self.basic_authentication(user:, password:, strict: false) - require "base64" - if strict - seed = "Basic " + Base64.strict_encode64(user + ":" + password) - else - seed = "Basic " + Base64.encode64(user + ":" + password) - end - return seed - end end diff --git a/lib/nice_http/utils/set_value_xml_tag.rb b/lib/nice_http/utils/set_value_xml_tag.rb new file mode 100644 index 0000000..1bd00a5 --- /dev/null +++ b/lib/nice_http/utils/set_value_xml_tag.rb @@ -0,0 +1,30 @@ +module NiceHttpUtils + + ################################################## + # set a value on xml tag + # @param tag_name [String] + # @param xml_string [String] + # @param value [String] + # @param take_off_prefix [Boolean] (optional). true, false(default) + # @return [String] with the new value + #################################################### + def self.set_value_xml_tag(tag_name, xml_string, value, take_off_prefix = false) + tag_name = tag_name.to_s + if take_off_prefix + i = tag_name.index(":") + tag_name = tag_name[i + 1..tag_name.length] unless i.nil? + end + if xml_string.to_s != "" + if take_off_prefix + old_value = NiceHttpUtils.get_value_xml_tag(tag_name, xml_string.dup, true) + xml_string.gsub!(/:#{tag_name}>#{Regexp.escape(old_value)}<\//i, ":" + tag_name + ">" + value + "#{Regexp.escape(old_value)}<\//i, "<" + tag_name + ">" + value + ".*<\/#{tag_name}>/i, "<" + tag_name + ">" + value + "") + end + return xml_string + else + return "" + end + end +end From 51f47f40c1d1a70d5b68545194711da3e989924a Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Thu, 20 Oct 2022 14:21:27 +0000 Subject: [PATCH 2/8] fixed test --- spec/nice_http/get_spec.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/spec/nice_http/get_spec.rb b/spec/nice_http/get_spec.rb index 175b0e1..adc03e2 100644 --- a/spec/nice_http/get_spec.rb +++ b/spec/nice_http/get_spec.rb @@ -73,16 +73,13 @@ expect(resp.message).to eq "OK" end - # todo: I need to find another ws, this one is not including set-cookie anymore - xit "set the cookies when required" do - server = "https://samples.auth0.com/" - path = "/authorize?client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH&response_type=code" - + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" http = NiceHttp.new(server) http.auto_redirect = true - resp = http.get(path) + resp = http.get('/setcookie') expect(resp.key?(:'set-cookie')).to eq true - expect(http.cookies["/"].key?("auth0")).to eq true + expect(http.cookies["/"].key?("something")).to eq true end it "detects wrong json when supplying wrong mock_response data" do From 8dd11d59c796f7f1c09903213b4864b85995a3f9 Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Fri, 21 Oct 2022 09:59:23 +0000 Subject: [PATCH 3/8] moved tests for #28 --- spec/nice_http/{ => methods}/delete_spec.rb | 0 spec/nice_http/{ => methods}/get_spec.rb | 0 spec/nice_http/{ => methods}/head_spec.rb | 0 spec/nice_http/{ => methods}/patch_spec.rb | 0 spec/nice_http/{ => methods}/post_spec.rb | 0 spec/nice_http/{ => methods}/put_spec.rb | 0 spec/nice_http/{ => methods}/send_request_spec.rb | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename spec/nice_http/{ => methods}/delete_spec.rb (100%) rename spec/nice_http/{ => methods}/get_spec.rb (100%) rename spec/nice_http/{ => methods}/head_spec.rb (100%) rename spec/nice_http/{ => methods}/patch_spec.rb (100%) rename spec/nice_http/{ => methods}/post_spec.rb (100%) rename spec/nice_http/{ => methods}/put_spec.rb (100%) rename spec/nice_http/{ => methods}/send_request_spec.rb (100%) diff --git a/spec/nice_http/delete_spec.rb b/spec/nice_http/methods/delete_spec.rb similarity index 100% rename from spec/nice_http/delete_spec.rb rename to spec/nice_http/methods/delete_spec.rb diff --git a/spec/nice_http/get_spec.rb b/spec/nice_http/methods/get_spec.rb similarity index 100% rename from spec/nice_http/get_spec.rb rename to spec/nice_http/methods/get_spec.rb diff --git a/spec/nice_http/head_spec.rb b/spec/nice_http/methods/head_spec.rb similarity index 100% rename from spec/nice_http/head_spec.rb rename to spec/nice_http/methods/head_spec.rb diff --git a/spec/nice_http/patch_spec.rb b/spec/nice_http/methods/patch_spec.rb similarity index 100% rename from spec/nice_http/patch_spec.rb rename to spec/nice_http/methods/patch_spec.rb diff --git a/spec/nice_http/post_spec.rb b/spec/nice_http/methods/post_spec.rb similarity index 100% rename from spec/nice_http/post_spec.rb rename to spec/nice_http/methods/post_spec.rb diff --git a/spec/nice_http/put_spec.rb b/spec/nice_http/methods/put_spec.rb similarity index 100% rename from spec/nice_http/put_spec.rb rename to spec/nice_http/methods/put_spec.rb diff --git a/spec/nice_http/send_request_spec.rb b/spec/nice_http/methods/send_request_spec.rb similarity index 100% rename from spec/nice_http/send_request_spec.rb rename to spec/nice_http/methods/send_request_spec.rb From d2df9d828d2a71b2d5cf141c8aa1105fcd12fb45 Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Fri, 21 Oct 2022 13:28:01 +0000 Subject: [PATCH 4/8] added test for x-www-form-urlencoded --- spec/nice_http/methods/post_spec.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/spec/nice_http/methods/post_spec.rb b/spec/nice_http/methods/post_spec.rb index 454589f..7965b09 100644 --- a/spec/nice_http/methods/post_spec.rb +++ b/spec/nice_http/methods/post_spec.rb @@ -323,8 +323,22 @@ expect(content).not_to match /Same as the last request/ end - # todo: - xit 'accepts application/x-www-form-urlencoded' do + it 'accepts application/x-www-form-urlencoded' do + request = { + headers: { 'Content-Type': "application/x-www-form-urlencoded"}, + path: "/register", + data: { + "firstname": "my firstname", + "lastname": "my lastname" + } + } + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.post request + expect(resp.code).to eq 201 + expect(resp.data.is_a?(String)).to eq true + expect(resp.data).to include "my firstname" + expect(resp.data).to include "my lastname" end #todo: add tests encoding and cookies From 84777b420c46a29c4a6694791eee161b7bce489d Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Fri, 21 Oct 2022 15:59:42 +0000 Subject: [PATCH 5/8] tests for cookies --- spec/nice_http/methods/delete_spec.rb | 9 +++++++++ spec/nice_http/methods/get_spec.rb | 1 - spec/nice_http/methods/head_spec.rb | 9 +++++++++ spec/nice_http/methods/patch_spec.rb | 11 ++++++++++- spec/nice_http/methods/post_spec.rb | 10 +++++++++- spec/nice_http/methods/put_spec.rb | 11 ++++++++++- 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/spec/nice_http/methods/delete_spec.rb b/spec/nice_http/methods/delete_spec.rb index 7f6941d..1bc6b2f 100644 --- a/spec/nice_http/methods/delete_spec.rb +++ b/spec/nice_http/methods/delete_spec.rb @@ -74,4 +74,13 @@ expect(resp.code).to eq 204 end + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.delete('/setcookie') + expect(resp.key?(:'set-cookie')).to eq true + expect(http.cookies["/"].key?("something")).to eq true + end + + end diff --git a/spec/nice_http/methods/get_spec.rb b/spec/nice_http/methods/get_spec.rb index adc03e2..bfae7c0 100644 --- a/spec/nice_http/methods/get_spec.rb +++ b/spec/nice_http/methods/get_spec.rb @@ -76,7 +76,6 @@ it "set the cookies when required" do server = "https://examplesinatra--tcblues.repl.co/" http = NiceHttp.new(server) - http.auto_redirect = true resp = http.get('/setcookie') expect(resp.key?(:'set-cookie')).to eq true expect(http.cookies["/"].key?("something")).to eq true diff --git a/spec/nice_http/methods/head_spec.rb b/spec/nice_http/methods/head_spec.rb index fcae3c6..7c8b98f 100644 --- a/spec/nice_http/methods/head_spec.rb +++ b/spec/nice_http/methods/head_spec.rb @@ -63,4 +63,13 @@ resp = http.head(req) expect(resp.code.to_i).to be_in(300..399) end + + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.head('/setcookie') + expect(resp.key?(:'set-cookie')).to eq true + expect(http.cookies["/"].key?("something")).to eq true + end + end diff --git a/spec/nice_http/methods/patch_spec.rb b/spec/nice_http/methods/patch_spec.rb index 53ac068..bf7a6f8 100644 --- a/spec/nice_http/methods/patch_spec.rb +++ b/spec/nice_http/methods/patch_spec.rb @@ -108,6 +108,15 @@ expect(content).to match /There was a problem converting to json/ end - #todo: add tests for headers, encoding and cookies + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.patch('/setcookie') + expect(resp.key?(:'set-cookie')).to eq true + expect(http.cookies["/"].key?("something")).to eq true + end + + + #todo: add tests for headers, encoding end diff --git a/spec/nice_http/methods/post_spec.rb b/spec/nice_http/methods/post_spec.rb index 7965b09..0059d30 100644 --- a/spec/nice_http/methods/post_spec.rb +++ b/spec/nice_http/methods/post_spec.rb @@ -341,6 +341,14 @@ expect(resp.data).to include "my lastname" end - #todo: add tests encoding and cookies + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.post('/setcookie') + expect(resp.key?(:'set-cookie')).to eq true + expect(http.cookies["/"].key?("something")).to eq true + end + + #todo: add tests encoding end diff --git a/spec/nice_http/methods/put_spec.rb b/spec/nice_http/methods/put_spec.rb index 309c554..b84429f 100644 --- a/spec/nice_http/methods/put_spec.rb +++ b/spec/nice_http/methods/put_spec.rb @@ -118,6 +118,15 @@ expect(resp.data.json).to eq [20, 30, 40] end - #todo: add tests for headers, encoding and cookies + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.put('/setcookie') + expect(resp.key?(:'set-cookie')).to eq true + expect(http.cookies["/"].key?("something")).to eq true + end + + + #todo: add tests for headers, encoding end From 12567d50135837c452bbb8298895a317121b62d5 Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Sat, 22 Oct 2022 14:57:31 +0000 Subject: [PATCH 6/8] close #29 --- README.md | 15 +++++++++++++-- lib/nice_http/manage/request.rb | 7 +++++++ spec/nice_http/nice_http_spec.rb | 30 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f51662e..033cf2a 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,18 @@ NiceHttp.requests = { } ``` -You can use `NiceHttp.requests` to specify certain `headers` or `data` that will apply on all requests sent. +You can use `NiceHttp.requests` to specify certain `headers`, `path` parameters or `data` that will apply on all requests sent. +```ruby +NiceHttp.requests = { + path: 'api-version=2022-12-09&testing=true', + data: { + properties: { + language: 'eng-us' + } + } +} +``` + ## Responses @@ -734,7 +745,7 @@ Example posting a csv file: Bug reports are very welcome on GitHub at https://github.com/marioruiz/nice_http. -If you want to contribute please follow [GitHub Flow](https://guides.github.com/introduction/flow/index.html) +If you want to contribute please follow [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) ## License diff --git a/lib/nice_http/manage/request.rb b/lib/nice_http/manage/request.rb index 1444155..ca329b8 100644 --- a/lib/nice_http/manage/request.rb +++ b/lib/nice_http/manage/request.rb @@ -35,6 +35,13 @@ def manage_request(*arguments) path = arguments[0].to_s() end path = (@prepath + path).gsub("//", "/") unless path.nil? or path.start_with?("http:") or path.start_with?("https:") + + if @defaults_request.key?(:path) and @defaults_request[:path].is_a?(String) and !@defaults_request[:path].empty? + path += "?" if !path.include?("?") and !@defaults_request[:path].include?("?") + path += '&' if path.match?(/\?.+$/) and @defaults_request[:path][0]!='&' + path += @defaults_request[:path] + end + @cookies.each { |cookie_path, cookies_hash| cookie_path = "" if cookie_path == "/" path_to_check = path diff --git a/spec/nice_http/nice_http_spec.rb b/spec/nice_http/nice_http_spec.rb index 62bbcd3..8507e96 100644 --- a/spec/nice_http/nice_http_spec.rb +++ b/spec/nice_http/nice_http_spec.rb @@ -612,6 +612,36 @@ expect(klass.request.headers[:Referer]).to eq (klass.host + request.path) end + it "supplies :path parameters specified to all requests" do + klass.host = "https://reqres.in" + + klass.requests = { path: 'page=2' } + http = klass.new + resp = http.get("/api/users") + expect(resp.data.json(:page)).to eq '2' + + klass.requests = { path: '?page=2' } + http = klass.new + resp = http.get("/api/users") + expect(resp.data.json(:page)).to eq '2' + + klass.requests = { path: 'page=2' } + http = klass.new + resp = http.get("/api/users?") + expect(resp.data.json(:page)).to eq '2' + + klass.requests = { path: 'page=2' } + http = klass.new + resp = http.get("/api/users?lolo=1") + expect(resp.data.json(:page)).to eq '2' + + klass.requests = { path: '&page=2'} + http = klass.new + resp = http.get("/api/users?lolo=1") + expect(resp.data.json(:page)).to eq '2' + + end + it "supplies :data specified to all requests" do klass.host = "https://reqres.in" klass.requests = { From 848f4d1de7c10b04699dcd7f2769e7501ba83805 Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Mon, 24 Oct 2022 14:53:00 +0000 Subject: [PATCH 7/8] close #27 --- README.md | 41 ++- lib/nice_http.rb | 15 +- lib/nice_http/defaults.rb | 7 +- lib/nice_http/initialize.rb | 26 +- lib/nice_http/manage/response.rb | 1 + lib/nice_http/manage/wait_async_operation.rb | 43 +++ lib/nice_http/reset.rb | 7 +- spec/nice_http/methods/get_spec.rb | 359 ++++++++++++------- spec/nice_http/nice_http_spec.rb | 117 ++++++ 9 files changed, 482 insertions(+), 134 deletions(-) create mode 100644 lib/nice_http/manage/wait_async_operation.rb diff --git a/README.md b/README.md index 033cf2a..8b70504 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Example that creates 1000 good random and unique requests to register an user an - [Create a connection](#Create-a-connection) - [Creating requests](#Creating-requests) - [Responses](#Responses) + - [Async Responses](#Async-Responses) - [Special settings](#Special-settings) - [Authentication requests](#Authentication-requests) - [Basic Authentication](#Basic-Authentication) @@ -282,7 +283,45 @@ The response will include at least the keys: *data*: the data response structure. In case of json we can get it as a hash by using: `resp.data.json`. Also you can filter the json structure and get what you want: `resp.data.json(:loginname, :address)` -Also interesting keys would be: *time_elapsed_total*, *time_elapsed* and many more available +Also interesting keys would be: *time_elapsed_total*, *time_elapsed* and many more available + +### Async responses + +In case a 202 response and set the async settings, it will wait until the operation finishes and will return the result: + +```ruby +NiceHttp.async_wait_seconds = 10 +NiceHttp.async_header = 'location' +NiceHttp.async_completed = 'percComplete' +NiceHttp.async_resource = 'resourceName' +NiceHttp.async_status = 'status' + +http = NiceHttp.new('https://exampleSinatra.tcblues.repl.co') + +response = http.get '/async' +pp response # => +{ :code=>"202", + :message=>"Accepted", + :location=>"https://exampleSinatra.tcblues.repl.co/operation/667", + :data=>"{\"result\":\"this is an async operation id: 667\"}", + :async=> + { :seconds=>4, + :data=> + "{'percComplete':100,'resourceName':'/resource/766','status':'Done','operationId':'667'}", + :status=>"Done", + :resource=>{:data=>"{'resourceId':'766','lolo':'lala'}"} + } + # plus other keys +} + +p response.code # 202 +p response.location # "https://exampleSinatra.tcblues.repl.co/operation/667" +p response.data.json # {:result=>"this is an async operation id: 667"} +p response.async.data.json # {:percComplete=>100, :resourceName=>"/resource/766", :status=>"Done",:operationId=>667} +p response.async.status # Done +p response.async.seconds # 4 +p response.async.resource.data.json # {:resourceId=>"766", :lolo=>"lala"} +``` ## Special settings diff --git a/lib/nice_http.rb b/lib/nice_http.rb index f26393d..58f796d 100644 --- a/lib/nice_http.rb +++ b/lib/nice_http.rb @@ -11,6 +11,7 @@ require_relative "nice_http/manage/request" require_relative "nice_http/manage/response" require_relative "nice_http/manage/set_stats" +require_relative "nice_http/manage/wait_async_operation" require_relative "nice_http/utils/basic_authentication" require_relative "nice_http/utils/get_value_xml_tag" require_relative "nice_http/utils/set_value_xml_tag" @@ -26,7 +27,8 @@ # Attributes you can access using NiceHttp.the_attribute: # :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 +# :active, :auto_redirect, :values_for, :create_stats, :stats, :capture, :captured, :request, :requests, +# :async_wait_seconds, :async_header, :async_completed, :async_resource, :async_status # # @attr [String] host The host to be accessed # @attr [Integer] port The port number @@ -67,6 +69,11 @@ # @attr [Hash] stats It contains detailed stats of the http communication # @attr [Boolean] capture If true, NiceHttp will store all requests and responses on NiceHttp.captured as strings # @attr [Array] captured It contains all the http requests and responses if NiceHttp.capture is set to true +# @attr [Integer] async_wait_seconds Number of seconds to wait until the async request is completed +# @attr [String] async_header The header to check if the async request is completed +# @attr [String] async_completed The value of the async_header to check if the async request is completed +# @attr [String] async_resource The resource to check if the async request is completed +# @attr [String] async_status The status to check if the async request is completed ###################################################### class NiceHttp include NiceHttpManageRequest @@ -91,7 +98,8 @@ def initialize(attribute, message = "") class << self 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 + :active, :auto_redirect, :log_files, :values_for, :create_stats, :stats, :capture, :captured, :requests, + :async_wait_seconds, :async_header, :async_completed, :async_resource, :async_status end at_exit do @@ -103,7 +111,8 @@ class << self reset! 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 + attr_accessor :headers, :cookies, :use_mocks, :auto_redirect, :logger, :values_for, :log_headers, + :async_wait_seconds, :async_header, :async_completed, :async_resource, :async_status private :manage_request, :manage_response end diff --git a/lib/nice_http/defaults.rb b/lib/nice_http/defaults.rb index 6861a49..52720ce 100644 --- a/lib/nice_http/defaults.rb +++ b/lib/nice_http/defaults.rb @@ -2,7 +2,7 @@ class NiceHttp ###################################################### # Change the default values for NiceHttp supplying a Hash # - # @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 + # @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, :async_wait_seconds, :async_header, :async_completed, :async_resource, :async_status ###################################################### def self.defaults=(par = {}) @host = par[:host] if par.key?(:host) @@ -21,5 +21,10 @@ def self.defaults=(par = {}) @auto_redirect = par[:auto_redirect] if par.key?(:auto_redirect) @create_stats = par[:create_stats] if par.key?(:create_stats) @capture = par[:capture] if par.key?(:capture) + @async_wait_seconds = par[:async_wait_seconds] if par.key?(:async_wait_seconds) + @async_header = par[:async_header] if par.key?(:async_header) + @async_completed = par[:async_completed] if par.key?(:async_completed) + @async_resource = par[:async_resource] if par.key?(:async_resource) + @async_status = par[:async_status] if par.key?(:async_status) end end diff --git a/lib/nice_http/initialize.rb b/lib/nice_http/initialize.rb index 393eaf7..82eba7b 100644 --- a/lib/nice_http/initialize.rb +++ b/lib/nice_http/initialize.rb @@ -28,6 +28,11 @@ class NiceHttp # In case :file it will be generated a log file with name: nice_http_YY-mm-dd-HHMMSS.log # proxy_host # proxy_port + # async_wait_seconds -- integer (default 0) + # async_header -- string (default 'location') + # async_completed -- string (default empty string) + # async_resource -- string (default empty string) + # async_status -- string (default empty string) # @example # http2 = NiceHttp.new( host: "reqres.in", port: 443, ssl: true ) # @example @@ -59,13 +64,18 @@ def initialize(args = {}) @num_redirects = 0 @create_stats = self.class.create_stats @capture = self.class.capture - + @async_wait_seconds = self.class.async_wait_seconds + @async_header = self.class.async_header + @async_completed = self.class.async_completed + @async_resource = self.class.async_resource + @async_status = self.class.async_status + #todo: set only the cookies for the current domain #key: path, value: hash with key is the name of the cookie and value the value # we set the default value for non existing keys to empty Hash {} so in case of merge there is no problem @cookies = Hash.new { |h, k| h[k] = {} } - if args.is_a?(String) + if args.is_a?(String) # 'http://www.example.com' uri = URI.parse(args) @host = uri.host unless uri.host.nil? @port = uri.port unless uri.port.nil? @@ -86,6 +96,11 @@ def initialize(args = {}) @proxy_port = args[:proxy_port] if args.keys.include?(:proxy_port) @use_mocks = args[:use_mocks] if args.keys.include?(:use_mocks) auto_redirect = args[:auto_redirect] if args.keys.include?(:auto_redirect) + @async_wait_seconds = args[:async_wait_seconds] if args.keys.include?(:async_wait_seconds) + @async_header = args[:async_header] if args.keys.include?(:async_header) + @async_completed = args[:async_completed] if args.keys.include?(:async_completed) + @async_resource = args[:async_resource] if args.keys.include?(:async_resource) + @async_status = args[:async_status] if args.keys.include?(:async_status) end log_filename = "" @@ -178,7 +193,12 @@ def initialize(args = {}) raise InfoMissing, :headers unless @headers.is_a?(Hash) raise InfoMissing, :values_for unless @values_for.is_a?(Hash) raise InfoMissing, :log_headers unless [:all, :none, :partial].include?(@log_headers) - + raise InfoMissing, :async_wait_seconds unless @async_wait_seconds.is_a?(Integer) or @async_wait_seconds.nil? + raise InfoMissing, :async_header unless @async_header.is_a?(String) or @async_header.nil? + raise InfoMissing, :async_completed unless @async_completed.is_a?(String) or @async_completed.nil? + raise InfoMissing, :async_resource unless @async_resource.is_a?(String) or @async_resource.nil? + raise InfoMissing, :async_status unless @async_status.is_a?(String) or @async_status.nil? + begin if !@proxy_host.nil? && !@proxy_port.nil? @http = Net::HTTP::Proxy(@proxy_host, @proxy_port).new(@host, @port) diff --git a/lib/nice_http/manage/response.rb b/lib/nice_http/manage/response.rb index 6e5c26d..1d9367b 100644 --- a/lib/nice_http/manage/response.rb +++ b/lib/nice_http/manage/response.rb @@ -185,6 +185,7 @@ def manage_response(resp, data) end end end + @response = wait_async_operation() if @response[:code] == "202" @prev_response = @response rescue Exception => stack @logger.fatal stack diff --git a/lib/nice_http/manage/wait_async_operation.rb b/lib/nice_http/manage/wait_async_operation.rb new file mode 100644 index 0000000..896b890 --- /dev/null +++ b/lib/nice_http/manage/wait_async_operation.rb @@ -0,0 +1,43 @@ +module NiceHttpManageResponse + def wait_async_operation(response: @response, async_wait_seconds: @async_wait_seconds, async_header: @async_header, async_completed: @async_completed, async_resource: @async_resource, async_status: @async_status) + resp_orig = response.deep_copy + if async_wait_seconds.to_i > 0 and !async_header.empty? and !async_completed.empty? + if response.code == 202 and response.key?(async_header.to_sym) + begin + location = response[async_header.to_sym] + time_elapsed = 0 + resp_async = {body: ''} + while time_elapsed <= async_wait_seconds + path, data, headers_t = manage_request({ path: location }) + resp_async = @http.get path + completed = resp_async.body.json(async_completed.to_sym) + break if completed.to_i == 100 or time_elapsed >= async_wait_seconds + time_elapsed += 1 + sleep 1 + end + resp_orig.async = {} + resp_orig.async.seconds = time_elapsed + resp_orig.async.data = resp_async.body + if resp_async.body.json.key?(async_status.to_sym) + resp_orig.async.status = resp_async.body.json(async_status.to_sym) + else + resp_orig.async.status = '' + end + unless async_resource.empty? + location = resp_async.body.json(async_resource.to_sym) + if location.empty? + resp_orig.async.resource = {} + else + path, data, headers_t = manage_request({ path: location }) + resp_async = @http.get path + resp_orig.async.resource = {data: resp_async.body} + end + end + rescue Exception => stack + @logger.warn stack + end + end + end + return resp_orig + end +end diff --git a/lib/nice_http/reset.rb b/lib/nice_http/reset.rb index 20e8e76..3bbc149 100644 --- a/lib/nice_http/reset.rb +++ b/lib/nice_http/reset.rb @@ -45,5 +45,10 @@ def self.reset! } @capture = false @captured = [] + @async_wait_seconds = 0 + @async_header = "location" + @async_completed = "" + @async_resource = "" + @async_status = "" end -end +end \ No newline at end of file diff --git a/spec/nice_http/methods/get_spec.rb b/spec/nice_http/methods/get_spec.rb index bfae7c0..b5dfa28 100644 --- a/spec/nice_http/methods/get_spec.rb +++ b/spec/nice_http/methods/get_spec.rb @@ -1,151 +1,260 @@ require "nice_http" RSpec.describe NiceHttp, "#get" do - before do - NiceHttp.log_files = Hash.new() - @http = NiceHttp.new("https://reqres.in") - end + describe "no async" do + before do + NiceHttp.log_files = Hash.new() + @http = NiceHttp.new("https://reqres.in") + end - it "accepts path as string parameter" do - resp = @http.get "/api/users?page=2" - expect(resp.code).to eq 200 - expect(resp.message).to eq "OK" - end + it "accepts path as string parameter" do + resp = @http.get "/api/users?page=2" + expect(resp.code).to eq 200 + expect(resp.message).to eq "OK" + end - it "accepts Hash with key path" do - resp = @http.get({path: "/api/users?page=2"}) - expect(resp.code).to eq 200 - expect(resp.message).to eq "OK" - end + it "accepts Hash with key path" do + resp = @http.get({ path: "/api/users?page=2" }) + expect(resp.code).to eq 200 + expect(resp.message).to eq "OK" + end - it "returns error in case no path in hash" do - resp = @http.get({}) - expect(resp.class).to eq Hash - expect(resp.fatal_error).to match /no[\w\s]+path/i - expect(resp.code).to eq nil - expect(resp.message).to eq nil - expect(resp.data).to eq nil - end + it "returns error in case no path in hash" do + resp = @http.get({}) + expect(resp.class).to eq Hash + expect(resp.fatal_error).to match /no[\w\s]+path/i + expect(resp.code).to eq nil + expect(resp.message).to eq nil + expect(resp.data).to eq nil + end - it "returns the mock response if specified" do - @http.use_mocks = true - request = { - path: "/api/users?page=2", - mock_response: { - code: 100, - message: "mock", - data: {example: "mock"}, - }, - } - resp = @http.get(request) - expect(resp.class).to eq Hash - expect(resp.code).to eq 100 - expect(resp.message).to eq "mock" - expect(resp.data.json).to eq ({example: "mock"}) - end + it "returns the mock response if specified" do + @http.use_mocks = true + request = { + path: "/api/users?page=2", + mock_response: { + code: 100, + message: "mock", + data: { example: "mock" }, + }, + } + resp = @http.get(request) + expect(resp.class).to eq Hash + expect(resp.code).to eq 100 + expect(resp.message).to eq "mock" + expect(resp.data.json).to eq ({ example: "mock" }) + end - it "redirects when auto_redirect is true and http code is 30x" do - server = "https://samples.auth0.com/" - path = "/authorize?client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH&response_type=code" + it "redirects when auto_redirect is true and http code is 30x" do + server = "https://samples.auth0.com/" + path = "/authorize?client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH&response_type=code" - http = NiceHttp.new(server) - http.auto_redirect = true - resp = http.get(path) + http = NiceHttp.new(server) + http.auto_redirect = true + resp = http.get(path) - expect(resp.code).to eq 200 - expect(resp.message).to eq "OK" - end + expect(resp.code).to eq 200 + expect(resp.message).to eq "OK" + end - it 'doesn\'t redirect when auto_redirect is false and http code is 30x' do - server = "https://samples.auth0.com/" - path = "/authorize?client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH&response_type=code" + it 'doesn\'t redirect when auto_redirect is false and http code is 30x' do + server = "https://samples.auth0.com/" + path = "/authorize?client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH&response_type=code" - http = NiceHttp.new(server) - http.auto_redirect = false - resp = http.get(path) - expect(resp.code).to eq 302 - expect(resp.message).to eq "Found" - end + http = NiceHttp.new(server) + http.auto_redirect = false + resp = http.get(path) + expect(resp.code).to eq 302 + expect(resp.message).to eq "Found" + end - it "handles correctly when http or https is on path" do - resp = @http.get "https://reqres.in/api/users?page=2" - expect(resp.code).to eq 200 - expect(resp.message).to eq "OK" - end + it "handles correctly when http or https is on path" do + resp = @http.get "https://reqres.in/api/users?page=2" + expect(resp.code).to eq 200 + expect(resp.message).to eq "OK" + end - it "set the cookies when required" do - server = "https://examplesinatra--tcblues.repl.co/" - http = NiceHttp.new(server) - resp = http.get('/setcookie') - expect(resp.key?(:'set-cookie')).to eq true - expect(http.cookies["/"].key?("something")).to eq true - end + it "set the cookies when required" do + server = "https://examplesinatra--tcblues.repl.co/" + http = NiceHttp.new(server) + resp = http.get("/setcookie") + expect(resp.key?(:'set-cookie')).to eq true + expect(http.cookies["/"].key?("something")).to eq true + end - it "detects wrong json when supplying wrong mock_response data" do - request = { - path: "/api/users?page=2", - mock_response: { - code: 200, - message: "OK", - data: {a: "Android\xAE"}, - }, - } - @http.use_mocks = true - resp = @http.get request - content = File.read("./nice_http.log") - expect(content).to match /There was a problem converting to json/ - end + it "detects wrong json when supplying wrong mock_response data" do + request = { + path: "/api/users?page=2", + mock_response: { + code: 200, + message: "OK", + data: { a: "Android\xAE" }, + }, + } + @http.use_mocks = true + resp = @http.get request + content = File.read("./nice_http.log") + expect(content).to match /There was a problem converting to json/ + end - it 'returns on headers the time_request and time_response' do - started = Time.now - resp = @http.get "/api/users?delay=1" - finished = Time.now - expect(resp.time_request >= started) - expect(resp.time_request <= finished) - expect(resp.time_response >= finished) - end + it "returns on headers the time_request and time_response" do + started = Time.now + resp = @http.get "/api/users?delay=1" + finished = Time.now + expect(resp.time_request >= started) + expect(resp.time_request <= finished) + expect(resp.time_response >= finished) + end - it 'downloads into the specified folder' do - Dir.mkdir('./tmp/') unless Dir.exist?('./tmp') - File.delete('./tmp/logo.png') if File.exist?('./tmp/logo.png') + it "downloads into the specified folder" do + Dir.mkdir("./tmp/") unless Dir.exist?("./tmp") + File.delete("./tmp/logo.png") if File.exist?("./tmp/logo.png") - http = NiceHttp.new("https://euruko2019.org") - http.get("/assets/images/logo.png", save_data: './tmp/') - expect(File.exist?('./tmp/logo.png')).to eq true - end + http = NiceHttp.new("https://euruko2019.org") + http.get("/assets/images/logo.png", save_data: "./tmp/") + expect(File.exist?("./tmp/logo.png")).to eq true + end - it 'downloads into the specified folder finished not on slash' do - Dir.mkdir('./tmp/') unless Dir.exist?('./tmp') - File.delete('./tmp/logo.png') if File.exist?('./tmp/logo.png') + it "downloads into the specified folder finished not on slash" do + Dir.mkdir("./tmp/") unless Dir.exist?("./tmp") + File.delete("./tmp/logo.png") if File.exist?("./tmp/logo.png") - http = NiceHttp.new("https://euruko2019.org") - http.get("/assets/images/logo.png", save_data: './tmp') - expect(File.exist?('./tmp/logo.png')).to eq true - end + http = NiceHttp.new("https://euruko2019.org") + http.get("/assets/images/logo.png", save_data: "./tmp") + expect(File.exist?("./tmp/logo.png")).to eq true + end - it 'downloads into the specified path' do - Dir.mkdir('./tmp/') unless Dir.exist?('./tmp') - File.delete('./tmp/logo2.png') if File.exist?('./tmp/logo2.png') - http = NiceHttp.new("https://euruko2019.org") - http.get("/assets/images/logo.png", save_data: './tmp/logo2.png') - expect(File.exist?('./tmp/logo2.png')).to eq true - end + it "downloads into the specified path" do + Dir.mkdir("./tmp/") unless Dir.exist?("./tmp") + File.delete("./tmp/logo2.png") if File.exist?("./tmp/logo2.png") + http = NiceHttp.new("https://euruko2019.org") + http.get("/assets/images/logo.png", save_data: "./tmp/logo2.png") + expect(File.exist?("./tmp/logo2.png")).to eq true + end - it 'downloads a json into the specified path' do - Dir.mkdir('./tmp/') unless Dir.exist?('./tmp') - File.delete('./tmp/users.json') if File.exist?('./tmp/users.json') - resp = @http.get("/api/users?page=2", save_data: './tmp/users.json') - expect(resp.code).to eq 200 - expect(resp.message).to eq "OK" - expect(File.exist?('./tmp/users.json')).to eq true - end + it "downloads a json into the specified path" do + Dir.mkdir("./tmp/") unless Dir.exist?("./tmp") + File.delete("./tmp/users.json") if File.exist?("./tmp/users.json") + resp = @http.get("/api/users?page=2", save_data: "./tmp/users.json") + expect(resp.code).to eq 200 + expect(resp.message).to eq "OK" + expect(File.exist?("./tmp/users.json")).to eq true + end - it 'doens\'t save if path not reachable' do - Dir.mkdir('./tmpx/') if Dir.exist?('./tmpx') - File.delete('./tmp/logo.png') if File.exist?('./tmp/logo.png') - http = NiceHttp.new("https://euruko2019.org") - http.get("/assets/images/logo.png", save_data: './tmpx/') - expect(File.exist?('./tmp/logo.png')).to eq false + it 'doens\'t save if path not reachable' do + Dir.mkdir("./tmpx/") if Dir.exist?("./tmpx") + File.delete("./tmp/logo.png") if File.exist?("./tmp/logo.png") + http = NiceHttp.new("https://euruko2019.org") + http.get("/assets/images/logo.png", save_data: "./tmpx/") + expect(File.exist?("./tmp/logo.png")).to eq false + end end + describe 'async' do + before :each do + NiceHttp.log_files = Hash.new() + @http = NiceHttp.new(host: 'https://exampleSinatra.tcblues.repl.co', async_wait_seconds: 10, async_header: 'location', + async_completed: 'percComplete', async_resource: 'resourceName', async_status: 'status') + end + + it 'waits until async operation completed when time < than wait time' do + started = Time.now + resp = @http.get '/async' + elapsed = Time.now - started + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + operation_id = resp.data.json(:result).scan(/id:\s(\d+)/).join + resource_id = operation_id.reverse + expect(resp.async.status).to eq 'Done' + expect(resp.async.data.json.percComplete).to eq 100 + expect(resp.async.data.json.status).to eq 'Done' + expect(resp.async.data.json.resourceName).to eq "/resource/#{resource_id}" + expect(resp.async.status).to eq 'Done' + expect(resp.async.resource.data.json.resourceId).to eq resource_id + expect(elapsed).to be < 10 + expect(resp.async.seconds).to eq 4 + end + + it "doesn't wait until async operation completed if time > than wait time" do + @http.async_wait_seconds = 1 + resp = @http.get '/async' + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + operation_id = resp.data.json(:result).scan(/id:\s(\d+)/).join + resource_id = operation_id.reverse + expect(resp.async.status).to eq 'Ongoing' + expect(resp.async.data.json.percComplete).to eq 25 + expect(resp.async.data.json.status).to eq 'Ongoing' + expect(resp.async.data.json.resourceName).to eq "/resource/#{resource_id}" + expect(resp.async.status).to eq 'Ongoing' + expect(resp.async.resource.data.json.resourceId).to eq resource_id + expect(resp.async.seconds).to eq 1 + end + + it "doesn't wait for async operation if wait==0" do + @http.async_wait_seconds = 0 + resp = @http.get '/async' + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + expect(resp.key?(:async)).to eq false + end + it "doesn't wait if async_header not found" do + @http.async_header = 'wrong_header' + resp = @http.get '/async' + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + expect(resp.key?(:async)).to eq false + end + + it "waits until max time if async_completed not found" do + @http.async_completed = 'wrong_completed' + started = Time.now + resp = @http.get '/async' + elapsed = Time.now - started + operation_id = resp.data.json(:result).scan(/id:\s(\d+)/).join + resource_id = operation_id.reverse + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + expect(resp.async.data.json.status).to eq 'Done' + expect(resp.async.data.json.resourceName).to eq "/resource/#{resource_id}" + expect(resp.async.status).to eq 'Done' + expect(resp.async.resource.data.json.resourceId).to eq resource_id + expect(elapsed).to be >= 10 + expect(resp.async.seconds).to eq 10 + end + + it "doesn't return resource if async_resource not found" do + @http.async_resource = 'wrong_resource' + started = Time.now + resp = @http.get '/async' + elapsed = Time.now - started + operation_id = resp.data.json(:result).scan(/id:\s(\d+)/).join + resource_id = operation_id.reverse + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + expect(resp.async.data.json.status).to eq 'Done' + expect(resp.async.data.json.resourceName).to eq "/resource/#{resource_id}" + expect(resp.async.status).to eq 'Done' + expect(resp.async.resource).to eq ({}) + expect(resp.async.seconds).to eq 4 + end + + it "doesn't return async_status if async_status not found" do + @http.async_status = 'wrong_status' + started = Time.now + resp = @http.get '/async' + elapsed = Time.now - started + operation_id = resp.data.json(:result).scan(/id:\s(\d+)/).join + resource_id = operation_id.reverse + expect(resp.code).to eq 202 + expect(resp.data.json(:result)).to include 'this is an async operation' + expect(resp.async.data.json.status).to eq 'Done' + expect(resp.async.data.json.resourceName).to eq "/resource/#{resource_id}" + expect(resp.async.resource.data.json.resourceId).to eq resource_id + expect(resp.async.status).to eq '' + expect(resp.async.seconds).to eq 4 + end + + + end end diff --git a/spec/nice_http/nice_http_spec.rb b/spec/nice_http/nice_http_spec.rb index 8507e96..21f19cf 100644 --- a/spec/nice_http/nice_http_spec.rb +++ b/spec/nice_http/nice_http_spec.rb @@ -663,5 +663,122 @@ end + describe "async" do + it "async_wait_seconds" do + klass.host = "https://reqres.in" + + expect(klass.async_wait_seconds).to eq 0 + http = klass.new + expect(http.async_wait_seconds).to eq 0 + klass.async_wait_seconds = 10 + expect(klass.async_wait_seconds).to eq 10 + http = klass.new + expect(http.async_wait_seconds).to eq 10 + http = klass.new(async_wait_seconds: 20) + expect(http.async_wait_seconds).to eq 20 + + klass.async_wait_seconds = '10' + klass.new rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_wait_seconds + expect(err.message).to match /wrong async_wait_seconds/i + + klass.new(async_wait_seconds: '20') rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_wait_seconds + expect(err.message).to match /wrong async_wait_seconds/i + end + + it "async_header" do + klass.host = "https://reqres.in" + + expect(klass.async_header).to eq 'location' + http = klass.new + expect(http.async_header).to eq 'location' + klass.async_header = 'location2' + expect(klass.async_header).to eq 'location2' + http = klass.new + expect(http.async_header).to eq 'location2' + http = klass.new(async_header: 'location3') + expect(http.async_header).to eq 'location3' + + klass.async_header = 10 + klass.new rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_header + expect(err.message).to match /wrong async_header/i + + klass.new(async_header: 20) rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_header + expect(err.message).to match /wrong async_header/i + end + + it "async_status" do + klass.host = "https://reqres.in" + + expect(klass.async_status).to eq '' + http = klass.new + expect(http.async_status).to eq '' + klass.async_status = 'status' + expect(klass.async_status).to eq 'status' + http = klass.new + expect(http.async_status).to eq 'status' + http = klass.new(async_status: 'status2') + expect(http.async_status).to eq 'status2' + + klass.async_status = 0 + klass.new rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_status + expect(err.message).to match /wrong async_status/i + + klass.new(async_status: 0) rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_status + expect(err.message).to match /wrong async_status/i + end + + it 'async_completed' do + klass.host = "https://reqres.in" + + expect(klass.async_completed).to eq '' + http = klass.new + expect(http.async_completed).to eq '' + klass.async_completed = 'completed' + expect(klass.async_completed).to eq 'completed' + http = klass.new + expect(http.async_completed).to eq 'completed' + http = klass.new(async_completed: 'completed2') + expect(http.async_completed).to eq 'completed2' + + klass.async_completed = 0 + klass.new rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_completed + expect(err.message).to match /wrong async_completed/i + + klass.new(async_completed: 0) rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_completed + expect(err.message).to match /wrong async_completed/i + end + + it 'async_resource' do + klass.host = "https://reqres.in" + + expect(klass.async_resource).to eq '' + http = klass.new + expect(http.async_resource).to eq '' + klass.async_resource = 'resource' + expect(klass.async_resource).to eq 'resource' + http = klass.new + expect(http.async_resource).to eq 'resource' + http = klass.new(async_resource: 'resource2') + expect(http.async_resource).to eq 'resource2' + + klass.async_resource = 0 + klass.new rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_resource + expect(err.message).to match /wrong async_resource/i + + klass.new(async_resource: 0) rescue err = $ERROR_INFO + expect(err.attribute).to eq :async_resource + expect(err.message).to match /wrong async_resource/i + end + + end end From 50d79d5f1fdb4c8cab8d114a98bceb58b17bf8f7 Mon Sep 17 00:00:00 2001 From: MarioRuiz Date: Mon, 24 Oct 2022 14:53:25 +0000 Subject: [PATCH 8/8] released v1.9.0 --- nice_http.gemspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nice_http.gemspec b/nice_http.gemspec index c76a2d8..5ba3817 100644 --- a/nice_http.gemspec +++ b/nice_http.gemspec @@ -1,13 +1,13 @@ Gem::Specification.new do |s| s.name = 'nice_http' - s.version = '1.8.10' + s.version = '1.9.0' 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"] s.email = 'marioruizs@gmail.com' - s.files = ["lib/nice_http.rb","lib/nice_http/http_methods.rb","lib/nice_http/utils.rb", - "lib/nice_http/manage_request.rb","lib/nice_http/manage_response.rb", - "LICENSE","README.md",".yardopts"] + s.files = Dir["lib/nice_http/methods/*.rb"] + Dir["lib/nice_http/manage/*.rb"] + + Dir["lib/nice_http/utils/*.rb"] + Dir["lib/nice_http/*.rb"] + + ["lib/nice_http.rb","LICENSE","README.md",".yardopts"] s.extra_rdoc_files = ["LICENSE","README.md"] s.homepage = 'https://github.com/MarioRuiz/nice_http' s.license = 'MIT'