Class: RestRequestor

Inherits:
Object
  • Object
show all
Defined in:
lib/rest_requestor.rb,
lib/rest_requestor/config.rb,
lib/rest_requestor/version.rb

Defined Under Namespace

Modules: Config Classes: NotFoundError, RateLimitError, ServiceUnavailableError, StandardError, TooManyRedirectsError

Constant Summary collapse

OPTION_DEFAULTS =
{
  max_retries: 7,
  open_timeout: 10,
  read_timeout: 30,
  proxy_address: "127.0.0.1",
  proxy_port: 9090,
  user_agent: nil,
  skip_proxy: true,
  logger: nil
}
FILE_CONFIG =
Config.load.freeze
VERSION =
"0.4.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**kw_args) ⇒ RestRequestor

Returns a new instance of RestRequestor.



58
59
60
61
# File 'lib/rest_requestor.rb', line 58

def initialize(**kw_args)
  @options = OPTION_DEFAULTS.merge(FILE_CONFIG).merge(kw_args)
  warn_no_user_agent if options[:user_agent].to_s.strip.empty?
end

Instance Attribute Details

#elapsedObject (readonly)

Returns the value of attribute elapsed.



32
33
34
# File 'lib/rest_requestor.rb', line 32

def elapsed
  @elapsed
end

#optionsObject (readonly)

Returns the value of attribute options.



32
33
34
# File 'lib/rest_requestor.rb', line 32

def options
  @options
end

#respObject (readonly)

Returns the value of attribute resp.



32
33
34
# File 'lib/rest_requestor.rb', line 32

def resp
  @resp
end

Class Method Details

.ssl_cert_storeObject

Shared across instances; cert store is immutable after setup



48
49
50
51
52
53
54
55
56
# File 'lib/rest_requestor.rb', line 48

def self.ssl_cert_store
  @ssl_cert_store ||= begin
    store = OpenSSL::X509::Store.new
    store.set_default_paths
    # Prevent failures when CRL distribution points are unreachable
    store.flags = OpenSSL::X509::V_FLAG_PARTIAL_CHAIN
    store
  end
end

Instance Method Details

#request(uri, verb, j_params: {}, f_hash: {}, q_params: {}, auth: nil, headers: {}, body: nil, retries: 0) ⇒ Object



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
# File 'lib/rest_requestor.rb', line 63

def request(uri, verb, j_params: {}, f_hash: {}, q_params: {}, auth: nil, headers: {}, body: nil, retries: 0)
  unless [:get, :post, :put, :delete, :patch, :head].include?(verb)
    raise ArgumentError, "Verb must be one of :get, :post, :put, :delete, :patch, :head — got #{verb.inspect}"
  end
  uri = add_query_string(uri, q_params) if q_params && !q_params.empty?

  exec_req(uri, verb, j_params, f_hash, auth, headers, body)

  case resp.code.to_i
  when (200...300)
    return resp.code.to_i if verb == :head
    if resp["Content-Type"]&.include?("application/json")
      JSON.parse(resp.body)
    elsif resp.code.to_i == 200 && !resp.body.to_s.strip.empty?
      begin
        JSON.parse(resp.body.to_s)
      rescue JSON::ParserError
        200
      end
    else
      resp.code.to_i
    end
  when 404 then raise NotFoundError.new(uri, verb, resp, j_params)
  when 429
    # Caller must handle rate limits — retry semantics vary per service
    raise RateLimitError.new(uri, verb, resp, j_params)
  when 503
    if retries <= options[:max_retries]
      retries += 1
      waitfor = retry_delay(retries)
      options[:logger]&.info { "Retrying (#{retries}), pausing #{waitfor} seconds" }
      sleep waitfor
      # q_params already incorporated into uri above; omit here to avoid double-encoding
      request(uri, verb, j_params: j_params, f_hash: f_hash, auth: auth, headers: headers, body: body, retries: retries)
    else
      raise ServiceUnavailableError.new(uri, verb, resp, j_params)
    end
  else
    raise StandardError.new(uri, verb, resp, j_params)
  end
end

#resolve_redirects(url, limit = 10) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rest_requestor.rb', line 105

def resolve_redirects(url, limit = 10)
  uri = URI.parse(url)

  limit.times do
    http = build_http(uri)
    req = Net::HTTP::Head.new(uri.request_uri)
    req["User-Agent"] = options[:user_agent] unless options[:user_agent].to_s.strip.empty?
    response = http.request(req)
    return uri.to_s unless response.is_a?(Net::HTTPRedirection)

    uri = URI.join(uri, response["location"])
  end

  raise TooManyRedirectsError, "Too many redirects resolving #{url}"
end