class NiceHttp

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

@attr [String] host The host to be accessed @attr [Integer] port The port number @attr [Boolean] ssl If you use ssl or not @attr [Integer] timeout Max time to wait until connected to the host or getting a response. @attr [Hash] headers Contains the headers you will be using on your connection @attr [Boolean] debug In case true shows all the details of the communication with the host @attr [String] log_path The path where the logs will be stored. By default empty string. @attr [String, Symbol] log :fix_file, :no, :screen, :file, “path and file name”.

:fix_file, will log the communication on nice_http.log. (default).  
:no, will not generate any logs.  
:screen, will print the logs on the screen.  
:file, will be generated a log file with name: nice_http_YY-mm-dd-HHMMSS.log.  
:file_run, will generate a log file with the name where the object was created and extension .log, fex: myfile.rb.log  
String the path and file name where the logs will be stored.

@attr [Symbol] log_headers. :all, :partial, :none (default :all) If :all will log all the headers. If :partial will log the last 10 characters. If :none no headers. @attr [String] proxy_host the proxy host to be used @attr [Integer] proxy_port the proxy port to be used @attr [String] last_request The last request with all the content sent @attr [String] last_response Only in case :debug is true, the last response with all the content @attr [Hash] request The last request with all the content sent @attr [Hash] requests The defaults for all requests. keys: :headers and :data @attr [String] request_id If the response includes a requestId, will be stored here @attr [Boolean] use_mocks If true, in case the request hash includes a :mock_response key, it will be used as the response instead @attr [Array] connections It will include all the active connections (NiceHttp instances) @attr [Integer] active Number of active connections @attr [Boolean] auto_redirect If true, NiceHttp will take care of the auto redirections when required by the responses @attr [Hash] response Contains the full response hash @attr [Integer] num_redirects Number of consecutive redirections managed @attr [Hash] headers The updated headers of the communication @attr [Hash] cookies Cookies set. The key is the path (String) where cookies are set and the value a Hash with pairs of cookie keys and values, example:

{ '/' => { "cfid" => "d95adfas2550255", "amddom.settings" => "doom" } }

@attr [Logger] logger An instance of the Logger class where logs will be stored. You can access on anytime to store specific data, for example:

my_http.logger.info "add this to the log file"  
@see https://ruby-doc.org/stdlib-2.5.0/libdoc/logger/rdoc/Logger.html

@attr [Hash] values_for The default values to set on the data in case not specified others @attr [Boolean] create_stats If true, NiceHttp will create stats of the http communication and store them on NiceHttp.stats hash @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

Constants

Error
InfoMissing

Attributes

active[RW]
auto_redirect[RW]
capture[RW]
captured[RW]
connections[RW]
create_stats[RW]
debug[RW]
headers[RW]
host[RW]
last_request[RW]
last_response[RW]
log[RW]
log_files[RW]
log_headers[RW]
log_path[RW]
port[RW]
proxy_host[RW]
proxy_port[RW]
request[RW]
request_id[RW]
requests[RW]
ssl[RW]
stats[RW]
timeout[RW]
use_mocks[RW]
values_for[RW]
auto_redirect[RW]
cookies[RW]
debug[R]
headers[RW]
host[R]
log[R]
log_headers[RW]
log_path[R]
logger[RW]
num_redirects[R]
port[R]
proxy_host[R]
proxy_port[R]
response[R]
ssl[R]
timeout[R]
use_mocks[RW]
values_for[RW]

Public Class Methods

add_stats(name, state, started, finished, item = nil) click to toggle source

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)
# File lib/nice_http.rb, line 189
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
defaults=(par = {}) click to toggle source

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

# File lib/nice_http.rb, line 150
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
inherited(subclass) click to toggle source

If inheriting from NiceHttp class

# File lib/nice_http.rb, line 138
def self.inherited(subclass)
  subclass.reset!
end
new(args = {}) click to toggle source

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
# File lib/nice_http.rb, line 327
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
reset!() click to toggle source

to reset to the original defaults

# File lib/nice_http.rb, line 89
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
save_stats(file_name = "") click to toggle source

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')
# File lib/nice_http.rb, line 266
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

Public Instance Methods

close() click to toggle source

Close HTTP connection

# File lib/nice_http.rb, line 524
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