Class: Onetime::Transport

Inherits:
Object
  • Object
show all
Defined in:
lib/onetime/transport.rb

Overview

Zero-dependency HTTP transport built on stdlib Net::HTTP.

Responsibilities:

- build the request URL, headers and body (form for v1, JSON for v2)
- apply HTTP Basic auth when credentials are configured
- retry idempotent requests with exponential backoff
- parse the JSON response and map error statuses to exceptions

It intentionally has no knowledge of API versions or resources; callers pass fully-qualified paths (e.g. "/api/v2/secret/conceal").

Constant Summary collapse

IDEMPOTENT_METHODS =
%i[get head].freeze
RETRYABLE_STATUSES =
[429, 500, 502, 503, 504].freeze
RETRYABLE_ERRORS =
[
  Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH,
  Errno::ETIMEDOUT, EOFError, SocketError, IOError,
  Net::OpenTimeout, Net::ReadTimeout
].freeze
METHOD_CLASSES =
{
  get:    Net::HTTP::Get,
  post:   Net::HTTP::Post,
  patch:  Net::HTTP::Patch,
  put:    Net::HTTP::Put,
  delete: Net::HTTP::Delete,
  head:   Net::HTTP::Head,
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Transport

Returns a new instance of Transport.



40
41
42
# File 'lib/onetime/transport.rb', line 40

def initialize(config)
  @config = config
end

Class Method Details

.encode_form(hash) ⇒ Object

Encodes a Hash as application/x-www-form-urlencoded, expanding Array values into repeated key[]= pairs (Rack's array convention, which the v1 API relies on for recipient).



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/onetime/transport.rb', line 86

def self.encode_form(hash)
  pairs = []
  hash.each do |key, value|
    next if value.nil?

    if value.is_a?(Array)
      value.each { |v| pairs << ["#{key}[]", v.to_s] }
    else
      pairs << [key.to_s, value.to_s]
    end
  end
  URI.encode_www_form(pairs)
end

Instance Method Details

#request(method, path, query: nil, body: nil, form: nil, headers: {}, raise_on_error: true) ⇒ Onetime::Response

Execute an HTTP request.

Parameters:

  • method (Symbol)

    :get, :post, :patch, ...

  • path (String)

    fully-qualified path including the /api/vN prefix

  • query (Hash, nil) (defaults to: nil)

    query-string parameters

  • body (Hash, nil) (defaults to: nil)

    request body serialized as JSON

  • form (Hash, nil) (defaults to: nil)

    request body serialized as form-urlencoded

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

    extra request headers

  • raise_on_error (Boolean) (defaults to: true)

    raise APIError for status >= 400

Returns:



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
# File 'lib/onetime/transport.rb', line 54

def request(method, path, query: nil, body: nil, form: nil, headers: {}, raise_on_error: true)
  uri = build_uri(path, query)
  attempt = 0

  begin
    response = perform(method, uri, body: body, form: form, headers: headers)

    if response.http_status >= 400
      if retryable_status?(response.http_status) && retry_allowed?(method, attempt)
        attempt += 1
        backoff(attempt)
        raise Retry
      end
      raise Errors.from_response(response) if raise_on_error
    end

    response
  rescue Retry
    retry
  rescue *RETRYABLE_ERRORS => e
    if retry_allowed?(method, attempt)
      attempt += 1
      backoff(attempt)
      retry
    end
    raise wrap_transport_error(e)
  end
end