Class: Pinnacle::Internal::Http::RawClient Private

Inherits:
Object
  • Object
show all
Defined in:
lib/pinnacle/internal/http/raw_client.rb

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.

Constant Summary collapse

RETRYABLE_STATUSES =

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.

Default HTTP status codes that trigger a retry

[408, 429, 500, 502, 503, 504, 521, 522, 524].freeze
INITIAL_RETRY_DELAY =

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.

Initial delay between retries in seconds

0.5
MAX_RETRY_DELAY =

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.

Maximum delay between retries in seconds

60.0
JITTER_FACTOR =

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.

Jitter factor for randomizing retry delays (20%)

0.2

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {}) ⇒ RawClient

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

Parameters:

  • base_url (String)

    The base url for the request.

  • max_retries (Integer) (defaults to: 2)

    The number of times to retry a failed request, defaults to 2.

  • timeout (Float) (defaults to: 60.0)

    The timeout for the request, defaults to 60.0 seconds.

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

    The headers for the request.



24
25
26
27
28
29
30
31
32
33
# File 'lib/pinnacle/internal/http/raw_client.rb', line 24

def initialize(base_url:, max_retries: 2, timeout: 60.0, headers: {})
  @base_url = base_url
  @max_retries = max_retries
  @timeout = timeout
  @default_headers = {
    "X-Fern-Language": "Ruby",
    "X-Fern-SDK-Name": "pinnacle",
    "X-Fern-SDK-Version": "0.0.1"
  }.merge(headers)
end

Instance Attribute Details

#base_urlString (readonly)

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 The base URL for requests.

Returns:

  • (String)

    The base URL for requests



18
19
20
# File 'lib/pinnacle/internal/http/raw_client.rb', line 18

def base_url
  @base_url
end

Instance Method Details

#add_jitter(delay) ⇒ Float

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.

Adds random jitter to a delay value.

Parameters:

  • delay (Float)

    The base delay in seconds.

Returns:

  • (Float)

    The delay with jitter applied.



118
119
120
121
# File 'lib/pinnacle/internal/http/raw_client.rb', line 118

def add_jitter(delay)
  jitter = delay * JITTER_FACTOR * (rand - 0.5) * 2
  [delay + jitter, 0].max
end

#build_http_request(url:, method:, headers: {}, body: nil) ⇒ HTTP::Request

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 The HTTP request.

Parameters:

  • url (URI::Generic)

    The url to the resource.

  • method (String)

    The HTTP method to use.

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

    The headers for the request.

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

    The body for the request.

Returns:

  • (HTTP::Request)

    The HTTP request.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/pinnacle/internal/http/raw_client.rb', line 147

def build_http_request(url:, method:, headers: {}, body: nil)
  request = Net::HTTPGenericRequest.new(
    method,
    !body.nil?,
    method != "HEAD",
    url
  )

  request_headers = @default_headers.merge(headers)
  request_headers.each { |name, value| request[name] = value }
  request.body = body if body

  request
end

#build_url(request) ⇒ URI::Generic

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 The URL.

Parameters:

Returns:

  • (URI::Generic)

    The URL.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/pinnacle/internal/http/raw_client.rb', line 125

def build_url(request)
  encoded_query = request.encode_query

  # If the path is already an absolute URL, use it directly
  if request.path.start_with?("http://", "https://")
    url = request.path
    url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any?
    return URI.parse(url)
  end

  path = request.path.start_with?("/") ? request.path[1..] : request.path
  base = request.base_url || @base_url
  url = "#{base.chomp("/")}/#{path}"
  url = "#{url}?#{encode_query(encoded_query)}" if encoded_query&.any?
  URI.parse(url)
end

#connect(url) ⇒ Net::HTTP

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 The HTTP connection.

Parameters:

  • url (URI::Generic)

    The url to connect to.

Returns:

  • (Net::HTTP)

    The HTTP connection.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/pinnacle/internal/http/raw_client.rb', line 170

def connect(url)
  is_https = (url.scheme == "https")

  port = if url.port
           url.port
         elsif is_https
           Net::HTTP.https_default_port
         else
           Net::HTTP.http_default_port
         end

  http = Net::HTTP.new(url.host, port)
  http.use_ssl = is_https
  # NOTE: We handle retries at the application level with HTTP status code awareness,
  # so we set max_retries to 0 to disable Net::HTTP's built-in network-level retries.
  http.max_retries = 0
  http
end

#encode_query(query) ⇒ String?

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 The encoded query.

Parameters:

  • query (Hash)

    The query for the request.

Returns:

  • (String, nil)

    The encoded query.



164
165
166
# File 'lib/pinnacle/internal/http/raw_client.rb', line 164

def encode_query(query)
  query.to_h.empty? ? nil : URI.encode_www_form(query)
end

#inspectString

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:

  • (String)


190
191
192
# File 'lib/pinnacle/internal/http/raw_client.rb', line 190

def inspect
  "#<#{self.class.name}:0x#{object_id.to_s(16)} @base_url=#{@base_url.inspect}>"
end

#parse_retry_after(value) ⇒ Float?

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.

Parses the Retry-After header value.

Parameters:

  • value (String)

    The Retry-After header value (seconds or HTTP date).

Returns:

  • (Float, nil)

    The delay in seconds, or nil if parsing fails.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/pinnacle/internal/http/raw_client.rb', line 100

def parse_retry_after(value)
  # Try parsing as integer (seconds)
  seconds = Integer(value, exception: false)
  return seconds.to_f if seconds

  # Try parsing as HTTP date
  begin
    retry_time = Time.httpdate(value)
    delay = retry_time - Time.now
    delay.positive? ? delay : nil
  rescue ArgumentError
    nil
  end
end

#retry_delay(response, attempt) ⇒ Float

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.

Calculates the delay before the next retry attempt using exponential backoff with jitter. Respects Retry-After header if present.

Parameters:

  • response (Net::HTTPResponse)

    The HTTP response.

  • attempt (Integer)

    The current retry attempt (0-indexed).

Returns:

  • (Float)

    The delay in seconds before the next retry.



84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pinnacle/internal/http/raw_client.rb', line 84

def retry_delay(response, attempt)
  # Check for Retry-After header (can be seconds or HTTP date)
  retry_after = response["Retry-After"]
  if retry_after
    delay = parse_retry_after(retry_after)
    return [delay, MAX_RETRY_DELAY].min if delay&.positive?
  end

  # Exponential backoff with jitter: base_delay * 2^attempt
  base_delay = INITIAL_RETRY_DELAY * (2**attempt)
  add_jitter([base_delay, MAX_RETRY_DELAY].min)
end

#send(request) ⇒ HTTP::Response

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 The HTTP response.

Parameters:

Returns:

  • (HTTP::Response)

    The HTTP response.



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
# File 'lib/pinnacle/internal/http/raw_client.rb', line 37

def send(request)
  url = build_url(request)
  attempt = 0
  response = nil

  loop do
    http_request = build_http_request(
      url:,
      method: request.method,
      headers: request.encode_headers,
      body: request.encode_body
    )

    conn = connect(url)
    conn.open_timeout = @timeout
    conn.read_timeout = @timeout
    conn.write_timeout = @timeout
    conn.continue_timeout = @timeout

    response = conn.request(http_request)

    break unless should_retry?(response, attempt)

    delay = retry_delay(response, attempt)
    sleep(delay)
    attempt += 1
  end

  response
end

#should_retry?(response, attempt) ⇒ Boolean

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.

Determines if a request should be retried based on the response status code.

Parameters:

  • response (Net::HTTPResponse)

    The HTTP response.

  • attempt (Integer)

    The current retry attempt (0-indexed).

Returns:

  • (Boolean)

    Whether the request should be retried.



72
73
74
75
76
77
# File 'lib/pinnacle/internal/http/raw_client.rb', line 72

def should_retry?(response, attempt)
  return false if attempt >= @max_retries

  status = response.code.to_i
  RETRYABLE_STATUSES.include?(status)
end