Class: Anypost::HttpClient Private

Inherits:
Object
  • Object
show all
Defined in:
lib/anypost/http_client.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Owns a Faraday connection and implements the request loop: header assembly, retries with full-jitter backoff, idempotency keys, and error mapping.

Constant Summary collapse

RETRYABLE_STATUS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

[429, 502, 503].freeze
MAX_BACKOFF_SECONDS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

8.0
BASE_BACKOFF_SECONDS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0.5

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, base_url:, timeout:, max_retries:, default_headers: {}, connection: nil, sleeper: nil, jitter: nil) ⇒ HttpClient

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of HttpClient.

Parameters:

  • sleeper (#call) (defaults to: nil)

    override the sleep between retries (tests)

  • jitter (#call) (defaults to: nil)

    override the [0,1) jitter factor (tests)



19
20
21
22
23
24
25
26
27
# File 'lib/anypost/http_client.rb', line 19

def initialize(api_key:, base_url:, timeout:, max_retries:, default_headers: {},
  connection: nil, sleeper: nil, jitter: nil)
  @api_key = api_key
  @max_retries = max_retries
  @default_headers = default_headers
  @connection = connection || build_connection(base_url, timeout)
  @sleeper = sleeper || ->(seconds) { sleep(seconds) if seconds.positive? }
  @jitter = jitter || -> { rand }
end

Class Method Details

.user_agentObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



71
72
73
# File 'lib/anypost/http_client.rb', line 71

def self.user_agent
  "anypost-ruby/#{Anypost::VERSION} Ruby/#{RUBY_VERSION}"
end

Instance Method Details

#request(method, path, body: nil, query: nil, idempotent: false, idempotency_key: nil, max_retries: nil, extra_headers: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Perform a request and return the decoded JSON body.



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
# File 'lib/anypost/http_client.rb', line 30

def request(method, path, body: nil, query: nil, idempotent: false,
  idempotency_key: nil, max_retries: nil, extra_headers: nil)
  retries = max_retries.nil? ? @max_retries : max_retries
  headers = build_headers(
    has_body: !body.nil?,
    idempotent: idempotent,
    idempotency_key: idempotency_key,
    max_retries: retries,
    extra_headers: extra_headers
  )
  payload = body.nil? ? nil : JSON.generate(body)
  params = clean_query(query)
  relative = path.sub(%r{\A/+}, "")

  attempt = 0
  loop do
    begin
      response = @connection.run_request(method, relative, payload, headers) do |req|
        req.params.update(params) unless params.empty?
      end
    rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
      raise APIConnectionError.new(connection_message(e), cause: e) unless attempt < retries

      @sleeper.call(backoff(attempt, nil))
      attempt += 1
      next
    end

    status = response.status
    return decode(response) if status >= 200 && status < 300

    if RETRYABLE_STATUS.include?(status) && attempt < retries
      @sleeper.call(backoff(attempt, response.headers))
      attempt += 1
      next
    end

    raise Errors.from_response(status, decode(response), response.headers)
  end
end