Class: NiceHttp

Inherits:
Object
  • Object
show all
Includes:
NiceHttpHttpMethods, NiceHttpManageRequest, NiceHttpManageResponse
Defined in:
lib/nice_http.rb,
lib/nice_http/close.rb,
lib/nice_http/reset.rb,
lib/nice_http/defaults.rb,
lib/nice_http/add_stats.rb,
lib/nice_http/inherited.rb,
lib/nice_http/initialize.rb,
lib/nice_http/save_stats.rb,
lib/nice_http/validate_response.rb

Overview

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, :async_wait_seconds, :async_header, :async_completed, :async_resource, :async_status, :connection_retry_attempts, :connection_retry_base_delay

Constant Summary collapse

Error =
Class.new StandardError
InfoMissing =
Class.new Error do
  attr_reader :attribute

  def initialize(attribute, message = "")
    @attribute = attribute
    message += "It was not possible to create the http connection!!!\n"
    message += "Wrong #{attribute}. "
    message += "Remember to supply http:// or https:// in case you specify an url to create the http connection, for example:\n"
    message += "http = NiceHttp.new('http://example.com')"
    super message
  end
end

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from NiceHttpHttpMethods

#delete, #get, #head, #patch, #post, #put, #send_request

Methods included from NiceHttpManageResponse

#manage_response, #wait_async_operation

Methods included from NiceHttpManageRequest

#manage_request

Constructor Details

#initialize(args = {}) ⇒ NiceHttp

Note:

When connection creation fails with "too many open files" (EMFILE/ENFILE), the library retries up to connection_retry_attempts times with backoff. For sustained load or many threads, prefer reusing one connection per thread and calling close when done.

Creates a new http connection.

Examples:

http = NiceHttp.new()
http = NiceHttp.new("https://www.example.com")
http = NiceHttp.new("example.com:8999")
http = NiceHttp.new("localhost:8322")
http2 = NiceHttp.new( host: "reqres.in", port: 443, ssl: true )
my_server = {host: "example.com",
             port: 80,
             headers: {"api-key": "zdDDdjkck"}
            }
http3 = NiceHttp.new my_server

Parameters:

  • args (defaults to: {})

    [] If no parameter supplied, by default will access how is setup on defaults

  • args (String) (defaults to: {})

    . The url to create the connection.

  • args (Hash) (defaults to: {})

    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 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) connection_retry_attempts -- positive integer (default 3). When connection creation fails with "too many open files", retry up to this many times with backoff. connection_retry_base_delay -- non-negative number, seconds (default 1.0). Base delay before each retry; jitter is added to avoid thundering herd.

Raises:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/nice_http/initialize.rb', line 48

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
  @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
  @connection_retry_attempts = self.class.connection_retry_attempts || 3
  @connection_retry_base_delay = self.class.connection_retry_base_delay || 1.0

  #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) # 'http://www.example.com'
    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)
    # Keep object reference when it has .generate (e.g. nice_hash); otherwise dup to avoid mutating caller's hash
    if args.keys.include?(:headers)
      h = args[:headers]
      @headers = (h.is_a?(Hash) && h.respond_to?(:generate)) ? h : h.dup
    end
    @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)
    @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)
    @connection_retry_attempts = args[:connection_retry_attempts] if args.keys.include?(:connection_retry_attempts)
    @connection_retry_base_delay = args[:connection_retry_base_delay] if args.keys.include?(:connection_retry_base_delay)
  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
  @log_file = log_filename
  @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)
  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?
  raise InfoMissing, :connection_retry_attempts unless @connection_retry_attempts.is_a?(Integer) && @connection_retry_attempts >= 1
  raise InfoMissing, :connection_retry_base_delay unless @connection_retry_base_delay.is_a?(Numeric) && @connection_retry_base_delay >= 0

  begin
    attempt = 1
    loop do
      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
        break
      rescue Exception => e
        if connection_retryable_error?(e) && attempt < @connection_retry_attempts
          delay = @connection_retry_base_delay + rand(0.25)
          @logger.warn "(#{self.object_id}): Too many open files (attempt #{attempt}/#{@connection_retry_attempts}), retrying in #{delay.round(2)}s..."
          sleep(delay)
          attempt += 1
        else
          raise
        end
      end
    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 = if @headers.is_a?(Hash) && @headers.respond_to?(:generate)
        @headers.generate
      else
        @headers.dup
      end

    self.class.active += 1
    self.class.connections.push(self)
  rescue Exception => stack
    puts stack
    @logger.fatal stack
    raise stack
  end
end

Class Attribute Details

.activeObject

Returns the value of attribute active.



103
104
105
# File 'lib/nice_http.rb', line 103

def active
  @active
end

.async_completedObject

Returns the value of attribute async_completed.



103
104
105
# File 'lib/nice_http.rb', line 103

def async_completed
  @async_completed
end

.async_headerObject

Returns the value of attribute async_header.



103
104
105
# File 'lib/nice_http.rb', line 103

def async_header
  @async_header
end

.async_resourceObject

Returns the value of attribute async_resource.



103
104
105
# File 'lib/nice_http.rb', line 103

def async_resource
  @async_resource
end

.async_statusObject

Returns the value of attribute async_status.



103
104
105
# File 'lib/nice_http.rb', line 103

def async_status
  @async_status
end

.async_wait_secondsObject

Returns the value of attribute async_wait_seconds.



103
104
105
# File 'lib/nice_http.rb', line 103

def async_wait_seconds
  @async_wait_seconds
end

.auto_redirectObject

Returns the value of attribute auto_redirect.



103
104
105
# File 'lib/nice_http.rb', line 103

def auto_redirect
  @auto_redirect
end

.captureObject

Returns the value of attribute capture.



103
104
105
# File 'lib/nice_http.rb', line 103

def capture
  @capture
end

.capturedObject

Returns the value of attribute captured.



103
104
105
# File 'lib/nice_http.rb', line 103

def captured
  @captured
end

.connection_retry_attemptsObject

Returns the value of attribute connection_retry_attempts.



103
104
105
# File 'lib/nice_http.rb', line 103

def connection_retry_attempts
  @connection_retry_attempts
end

.connection_retry_base_delayObject

Returns the value of attribute connection_retry_base_delay.



103
104
105
# File 'lib/nice_http.rb', line 103

def connection_retry_base_delay
  @connection_retry_base_delay
end

.connectionsObject

Returns the value of attribute connections.



103
104
105
# File 'lib/nice_http.rb', line 103

def connections
  @connections
end

.create_statsObject

Returns the value of attribute create_stats.



103
104
105
# File 'lib/nice_http.rb', line 103

def create_stats
  @create_stats
end

.debugObject

Returns the value of attribute debug.



103
104
105
# File 'lib/nice_http.rb', line 103

def debug
  @debug
end

.headersObject

Returns the value of attribute headers.



103
104
105
# File 'lib/nice_http.rb', line 103

def headers
  @headers
end

.hostObject

Returns the value of attribute host.



103
104
105
# File 'lib/nice_http.rb', line 103

def host
  @host
end

.last_requestObject

Returns the value of attribute last_request.



103
104
105
# File 'lib/nice_http.rb', line 103

def last_request
  @last_request
end

.last_responseObject

Returns the value of attribute last_response.



103
104
105
# File 'lib/nice_http.rb', line 103

def last_response
  @last_response
end

.logObject

Returns the value of attribute log.



103
104
105
# File 'lib/nice_http.rb', line 103

def log
  @log
end

.log_filesObject

Returns the value of attribute log_files.



103
104
105
# File 'lib/nice_http.rb', line 103

def log_files
  @log_files
end

.log_headersObject

Returns the value of attribute log_headers.



103
104
105
# File 'lib/nice_http.rb', line 103

def log_headers
  @log_headers
end

.log_pathObject

Returns the value of attribute log_path.



103
104
105
# File 'lib/nice_http.rb', line 103

def log_path
  @log_path
end

.portObject

Returns the value of attribute port.



103
104
105
# File 'lib/nice_http.rb', line 103

def port
  @port
end

.proxy_hostObject

Returns the value of attribute proxy_host.



103
104
105
# File 'lib/nice_http.rb', line 103

def proxy_host
  @proxy_host
end

.proxy_portObject

Returns the value of attribute proxy_port.



103
104
105
# File 'lib/nice_http.rb', line 103

def proxy_port
  @proxy_port
end

.requestObject

Returns the value of attribute request.



103
104
105
# File 'lib/nice_http.rb', line 103

def request
  @request
end

.request_idObject

Returns the value of attribute request_id.



103
104
105
# File 'lib/nice_http.rb', line 103

def request_id
  @request_id
end

.requestsObject

Returns the value of attribute requests.



103
104
105
# File 'lib/nice_http.rb', line 103

def requests
  @requests
end

.sslObject

Returns the value of attribute ssl.



103
104
105
# File 'lib/nice_http.rb', line 103

def ssl
  @ssl
end

.statsObject

Returns the value of attribute stats.



103
104
105
# File 'lib/nice_http.rb', line 103

def stats
  @stats
end

.timeoutObject

Returns the value of attribute timeout.



103
104
105
# File 'lib/nice_http.rb', line 103

def timeout
  @timeout
end

.use_mocksObject

Returns the value of attribute use_mocks.



103
104
105
# File 'lib/nice_http.rb', line 103

def use_mocks
  @use_mocks
end

.values_forObject

Returns the value of attribute values_for.



103
104
105
# File 'lib/nice_http.rb', line 103

def values_for
  @values_for
end

Instance Attribute Details

#activeInteger

Number of active connections

Returns:

  • (Integer)

    the current value of active



82
83
84
# File 'lib/nice_http.rb', line 82

def active
  @active
end

#async_completedString

The value of the async_header to check if the async request is completed

Returns:

  • (String)

    the current value of async_completed



82
83
84
# File 'lib/nice_http.rb', line 82

def async_completed
  @async_completed
end

#async_headerString

The header to check if the async request is completed

Returns:

  • (String)

    the current value of async_header



82
83
84
# File 'lib/nice_http.rb', line 82

def async_header
  @async_header
end

#async_resourceString

The resource to check if the async request is completed

Returns:

  • (String)

    the current value of async_resource



82
83
84
# File 'lib/nice_http.rb', line 82

def async_resource
  @async_resource
end

#async_statusString

The status to check if the async request is completed

Returns:

  • (String)

    the current value of async_status



82
83
84
# File 'lib/nice_http.rb', line 82

def async_status
  @async_status
end

#async_wait_secondsInteger

Number of seconds to wait until the async request is completed

Returns:

  • (Integer)

    the current value of async_wait_seconds



82
83
84
# File 'lib/nice_http.rb', line 82

def async_wait_seconds
  @async_wait_seconds
end

#auto_redirectBoolean

If true, NiceHttp will take care of the auto redirections when required by the responses

Returns:

  • (Boolean)

    the current value of auto_redirect



82
83
84
# File 'lib/nice_http.rb', line 82

def auto_redirect
  @auto_redirect
end

#captureBoolean

If true, NiceHttp will store all requests and responses on NiceHttp.captured as strings

Returns:

  • (Boolean)

    the current value of capture



82
83
84
# File 'lib/nice_http.rb', line 82

def capture
  @capture
end

#capturedArray

It contains all the http requests and responses if NiceHttp.capture is set to true

Returns:

  • (Array)

    the current value of captured



82
83
84
# File 'lib/nice_http.rb', line 82

def captured
  @captured
end

#connectionsArray

It will include all the active connections (NiceHttp instances)

Returns:

  • (Array)

    the current value of connections



82
83
84
# File 'lib/nice_http.rb', line 82

def connections
  @connections
end

#cookiesHash

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" } }

Returns:

  • (Hash)

    the current value of cookies



82
83
84
# File 'lib/nice_http.rb', line 82

def cookies
  @cookies
end

#create_statsBoolean

If true, NiceHttp will create stats of the http communication and store them on NiceHttp.stats hash

Returns:

  • (Boolean)

    the current value of create_stats



82
83
84
# File 'lib/nice_http.rb', line 82

def create_stats
  @create_stats
end

#debugBoolean

In case true shows all the details of the communication with the host

Returns:

  • (Boolean)

    the current value of debug



82
83
84
# File 'lib/nice_http.rb', line 82

def debug
  @debug
end

#headersHash

Contains the headers you will be using on your connection

Returns:

  • (Hash)

    the current value of headers



82
83
84
# File 'lib/nice_http.rb', line 82

def headers
  @headers
end

#hostString

The host to be accessed

Returns:

  • (String)

    the current value of host



82
83
84
# File 'lib/nice_http.rb', line 82

def host
  @host
end

#last_requestString

The last request with all the content sent

Returns:

  • (String)

    the current value of last_request



82
83
84
# File 'lib/nice_http.rb', line 82

def last_request
  @last_request
end

#last_responseString

Only in case :debug is true, the last response with all the content

Returns:

  • (String)

    the current value of last_response



82
83
84
# File 'lib/nice_http.rb', line 82

def last_response
  @last_response
end

#logString, Symbol

: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.

Returns:

  • (String, Symbol)

    the current value of log



82
83
84
# File 'lib/nice_http.rb', line 82

def log
  @log
end

#log_fileString

path and file name where the logs will be stored. (only reader)

Returns:

  • (String)

    the current value of log_file



82
83
84
# File 'lib/nice_http.rb', line 82

def log_file
  @log_file
end

#log_headersObject

Returns the value of attribute log_headers.



119
120
121
# File 'lib/nice_http.rb', line 119

def log_headers
  @log_headers
end

#log_headers.Symbol

:all, :partial, :none (default :all) If :all will log all the headers. If :partial will log the last 10 characters. If :none no headers.

Returns:

  • (Symbol)

    the current value of log_headers.



82
83
84
# File 'lib/nice_http.rb', line 82

def log_headers.
  @log_headers.
end

#log_pathString

The path where the logs will be stored. By default empty string.

Returns:

  • (String)

    the current value of log_path



82
83
84
# File 'lib/nice_http.rb', line 82

def log_path
  @log_path
end

#loggerLogger

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"

Returns:

  • (Logger)

    the current value of logger

See Also:



82
83
84
# File 'lib/nice_http.rb', line 82

def logger
  @logger
end

#num_redirectsInteger

Number of consecutive redirections managed

Returns:

  • (Integer)

    the current value of num_redirects



82
83
84
# File 'lib/nice_http.rb', line 82

def num_redirects
  @num_redirects
end

#portInteger

The port number

Returns:

  • (Integer)

    the current value of port



82
83
84
# File 'lib/nice_http.rb', line 82

def port
  @port
end

#proxy_hostString

the proxy host to be used

Returns:

  • (String)

    the current value of proxy_host



82
83
84
# File 'lib/nice_http.rb', line 82

def proxy_host
  @proxy_host
end

#proxy_portInteger

the proxy port to be used

Returns:

  • (Integer)

    the current value of proxy_port



82
83
84
# File 'lib/nice_http.rb', line 82

def proxy_port
  @proxy_port
end

#requestHash

The last request with all the content sent

Returns:

  • (Hash)

    the current value of request



82
83
84
# File 'lib/nice_http.rb', line 82

def request
  @request
end

#request_idString

If the response includes a requestId, will be stored here

Returns:

  • (String)

    the current value of request_id



82
83
84
# File 'lib/nice_http.rb', line 82

def request_id
  @request_id
end

#requestsHash

The defaults for all requests. keys: :headers and :data

Returns:

  • (Hash)

    the current value of requests



82
83
84
# File 'lib/nice_http.rb', line 82

def requests
  @requests
end

#responseHash

Contains the full response hash

Returns:

  • (Hash)

    the current value of response



82
83
84
# File 'lib/nice_http.rb', line 82

def response
  @response
end

#sslBoolean

If you use ssl or not

Returns:

  • (Boolean)

    the current value of ssl



82
83
84
# File 'lib/nice_http.rb', line 82

def ssl
  @ssl
end

#statsHash

It contains detailed stats of the http communication

Returns:

  • (Hash)

    the current value of stats



82
83
84
# File 'lib/nice_http.rb', line 82

def stats
  @stats
end

#timeoutInteger

Max time to wait until connected to the host or getting a response.

Returns:

  • (Integer)

    the current value of timeout



82
83
84
# File 'lib/nice_http.rb', line 82

def timeout
  @timeout
end

#use_mocksBoolean

If true, in case the request hash includes a :mock_response key, it will be used as the response instead

Returns:

  • (Boolean)

    the current value of use_mocks



82
83
84
# File 'lib/nice_http.rb', line 82

def use_mocks
  @use_mocks
end

#values_forHash

The default values to set on the data in case not specified others

Returns:

  • (Hash)

    the current value of values_for



82
83
84
# File 'lib/nice_http.rb', line 82

def values_for
  @values_for
end

Class Method Details

.add_stats(name, state, started, finished, item = nil) ⇒ Object

To add specific stats The stats will be added to NiceHttp.stats[:specific]

Examples:

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)

Parameters:

  • name (Symbol)

    name to group your specific stats

  • state (Symbol)

    state of the name supplied to group your specific stats

  • started (Time)

    when the process you want the stats started

  • finished (Time)

    when the process you want the stats finished

  • item (Object) (defaults to: nil)

    (Optional) The item to be added to :items key to store all items in an array



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/nice_http/add_stats.rb', line 22

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 = {}) ⇒ Object

Change the default values for NiceHttp supplying a Hash

Parameters:

  • par (Hash) (defaults to: {})

    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



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/nice_http/defaults.rb', line 7

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)
  @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

.inherited(subclass) ⇒ Object

If inheriting from NiceHttp class



5
6
7
# File 'lib/nice_http/inherited.rb', line 5

def self.inherited(subclass)
  subclass.reset!
end

.reset!Object

to reset to the original defaults



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/nice_http/reset.rb', line 5

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 = []
  @async_wait_seconds = 0
  @async_header = "location"
  @async_completed = ""
  @async_resource = ""
  @async_status = ""
  @connection_retry_attempts = 3
  @connection_retry_base_delay = 1.0
end

.save_stats(file_name = "") ⇒ Object

It will save the NiceHttp.stats on different files, each key of the hash in a different file.

Examples:

NiceHttp.save_stats
NiceHttp.save_stats('./stats/my_stats.yaml')
NiceHttp.save_stats('./stats/my_stats.json')

Parameters:

  • file_name (String) (defaults to: "")

    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.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/nice_http/save_stats.rb', line 15

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

.validate_response(resp, expected_structure, options = {}) ⇒ Hash

Validates that a response body matches an expected structure (keys and nesting). Uses NiceHash.compare_structure. Best for JSON responses.

Examples:

resp = http.get("/api/users/1")
result = NiceHttp.validate_response(resp, { user: { name: "xxx", id: 1 } })
expect(result[:ok]).to be true
result = NiceHttp.validate_response(resp, expected, include_diff: true)
puts result[:diff] unless result[:ok]

Parameters:

  • resp (Hash)

    Response object (e.g. from get/post). Must have :data (body string or Hash/Array).

  • expected_structure (Hash, Array)

    Structure with expected keys and nesting (values can be placeholders).

  • options (Hash) (defaults to: {})

    Optional. :include_diff => true to add a diff in the result when validation fails.

Returns:

  • (Hash)

    { ok: true } when structure matches, or { ok: false } or { ok: false, diff: Hash } when it does not.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/nice_http/validate_response.rb', line 18

def self.validate_response(resp, expected_structure, options = {})
  data = resp.is_a?(Hash) ? resp[:data] : resp.data
  parsed = if data.is_a?(String)
      begin
        require "json"
        JSON.parse(data.to_s)
      rescue JSON::ParserError
        return { ok: false, error: "response data is not valid JSON" }
      end
    elsif data.is_a?(Hash) || data.is_a?(Array)
      data
    else
      return { ok: false, error: "response data is missing or not JSON" }
    end

  # Normalize to symbol keys so compare_structure/diff match typical expected_structure (symbol keys)
  parsed = deep_symbolize(parsed) if parsed.is_a?(Hash) || parsed.is_a?(Array)

  ok = NiceHash.compare_structure(expected_structure, parsed)

  result = { ok: ok }
  if !ok && options[:include_diff]
    result[:diff] = NiceHash.diff(expected_structure, parsed)
  end
  result
end

Instance Method Details

#closeObject

Close HTTP connection



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/nice_http/close.rb', line 5

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